From bcac889d8c1370cfac5dd8b4bcc691637ba64152 Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Thu, 17 May 2018 23:54:06 -0400 Subject: [PATCH 01/31] job cancellation improvements for hubs --- test/python/test_ibmqjob.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/python/test_ibmqjob.py b/test/python/test_ibmqjob.py index 838da4168582..feed9a0f8cfc 100644 --- a/test/python/test_ibmqjob.py +++ b/test/python/test_ibmqjob.py @@ -23,6 +23,8 @@ from qiskit.backends.basejob import JobStatus from .common import requires_qe_access, QiskitTestCase, slow_test +USING_HUB = False + def lowest_pending_jobs(backends): """Returns the backend with lowest pending jobs.""" From cd825f4410510a4670e8d59ddf0acc73a4d311d2 Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Wed, 9 May 2018 16:58:33 -0400 Subject: [PATCH 02/31] add alternate ibmqjob init method; from_api() --- qiskit/backends/ibmq/ibmqbackend.py | 22 ++++++++++++++++++ qiskit/backends/ibmq/ibmqjob.py | 36 +++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/qiskit/backends/ibmq/ibmqbackend.py b/qiskit/backends/ibmq/ibmqbackend.py index 50675f4694af..8cc37bafd294 100644 --- a/qiskit/backends/ibmq/ibmqbackend.py +++ b/qiskit/backends/ibmq/ibmqbackend.py @@ -139,3 +139,25 @@ def status(self): raise LookupError( "Couldn't get backend status: {0}".format(ex)) return status + + def jobs(self, limit=50, skip=0): + """Attempt to get the jobs submitted to the backend + + Args: + limit (int): number of jobs to retrieve + skip (int): starting index of retrieval + + Return: + list(IBMQJob): list of IBMQJob instances + """ + backend_name = self.configuration['name'] + job_list = [] + base_index = 0 + job_info_list = self._api.get_jobs(limit=limit, skip=base_index) + while len(job_list) < limit or len(job_info_list) < limit: + base_index += skip + job_info_list = self._api.get_jobs(limit=limit, skip=base_index) + for job_info in job_info_list: + if job_info.get('backend').get('name') == backend_name: + job_list.append(IBMQJob.from_api(job_info)) + return job_list diff --git a/qiskit/backends/ibmq/ibmqjob.py b/qiskit/backends/ibmq/ibmqjob.py index ad7586758ecc..1707551f1916 100644 --- a/qiskit/backends/ibmq/ibmqjob.py +++ b/qiskit/backends/ibmq/ibmqjob.py @@ -56,6 +56,42 @@ def __init__(self, q_job, api, is_device): self._exception = None self._is_device = is_device + @classmethod + def from_api(self, job_info, api, is_device): + """Instantiates job using information returned from + IBMQuantumExperience about a particular job. + + Args: + job_info (dict): This is the information about a job returned from + the API. It has the simplified structure: + + {'backend': {'id', 'backend id string', + 'name', 'ibmqx4'}, + 'id': 'job id string', + 'qasms': [{'executionId': 'id string', + 'qasm': 'qasm string'}, + ] + 'status': 'status string', + 'seed': '1', + 'shots': 1024, + 'status': 'status string', + 'usedCredits': 3, + 'userId': 'user id'} + api (IBMQuantumExperience): IBM Q API + is_device (bool): whether backend is a real device # TODO: remove this after Qobj + """ + super().__init__() + self._status = JobStatus.QUEUED + self._backend_name = job_info.get('backend').get('name') + self._api = api + self._job_id = job_info.get('id') + # update status (need _api and _job_id) + self.status + self._status_msg = None + self._cancelled = False + self._exception = None + self._is_device = is_device + def result(self, timeout=None, wait=5): # pylint: disable=arguments-differ while self._status == JobStatus.INITIALIZING: From 93aa3782c752088624ce8e47c2d3882dae7c44db Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Mon, 21 May 2018 18:13:13 -0400 Subject: [PATCH 03/31] add retrieve_job for ibmq backends --- qiskit/backends/ibmq/ibmqbackend.py | 23 ++++++++++++++--- qiskit/backends/ibmq/ibmqjob.py | 38 ++++++++++++++++------------- test/python/test_compiler.py | 5 ++-- test/python/test_ibmqjob.py | 25 +++++++++++++++++-- 4 files changed, 67 insertions(+), 24 deletions(-) diff --git a/qiskit/backends/ibmq/ibmqbackend.py b/qiskit/backends/ibmq/ibmqbackend.py index 8cc37bafd294..acfe780373f3 100644 --- a/qiskit/backends/ibmq/ibmqbackend.py +++ b/qiskit/backends/ibmq/ibmqbackend.py @@ -11,6 +11,7 @@ """ import logging +from qiskit import QISKitError from qiskit._util import _camel_case_to_snake_case from qiskit.backends import BaseBackend from qiskit.backends.ibmq.ibmqjob import IBMQJob @@ -21,7 +22,7 @@ class IBMQBackend(BaseBackend): """Backend class interfacing with the Quantum Experience remotely. """ - + def __init__(self, configuration, api=None): """Initialize remote backend for IBM Quantum Experience. @@ -155,9 +156,25 @@ def jobs(self, limit=50, skip=0): base_index = 0 job_info_list = self._api.get_jobs(limit=limit, skip=base_index) while len(job_list) < limit or len(job_info_list) < limit: - base_index += skip + base_index += limit job_info_list = self._api.get_jobs(limit=limit, skip=base_index) for job_info in job_info_list: if job_info.get('backend').get('name') == backend_name: - job_list.append(IBMQJob.from_api(job_info)) + is_device = not bool(self._configuration.get('simulator')) + job = IBMQJob.from_api(job_info, self._api, is_device) + if len(job_list) < limit: + job_list.append(job) return job_list + + def retrieve_job(self, job_id): + """Attempt to get the specified job by job_id""" + job_info = self._api.get_job(job_id) + if 'error' in job_info: + raise IBMQBackendError('failed to get job id "{}"'.format(job_id)) + is_device = not bool(self._configuration.get('simulator')) + job = IBMQJob.from_api(job_info, self._api, is_device) + return job + +class IBMQBackendError(QISKitError): + """IBM Q Backend Errors""" + pass diff --git a/qiskit/backends/ibmq/ibmqjob.py b/qiskit/backends/ibmq/ibmqjob.py index 1707551f1916..546c01420cdb 100644 --- a/qiskit/backends/ibmq/ibmqjob.py +++ b/qiskit/backends/ibmq/ibmqjob.py @@ -18,11 +18,13 @@ from IBMQuantumExperience import ApiError from qiskit._compiler import compile_circuit +from qiskit import QuantumJob from qiskit.backends import BaseJob from qiskit.backends.basejob import JobStatus from qiskit._qiskiterror import QISKitError from qiskit._result import Result from qiskit._resulterror import ResultError +from forkable import ForkablePdb logger = logging.getLogger(__name__) @@ -35,6 +37,9 @@ class IBMQJob(BaseJob): """ _executor = futures.ThreadPoolExecutor() + def __init__(self, arg, api, is_device): + pass + def __init__(self, q_job, api, is_device): """IBMQJob init function. @@ -57,7 +62,7 @@ def __init__(self, q_job, api, is_device): self._is_device = is_device @classmethod - def from_api(self, job_info, api, is_device): + def from_api(cls, job_info, api, is_device): """Instantiates job using information returned from IBMQuantumExperience about a particular job. @@ -80,21 +85,25 @@ def from_api(self, job_info, api, is_device): api (IBMQuantumExperience): IBM Q API is_device (bool): whether backend is a real device # TODO: remove this after Qobj """ - super().__init__() - self._status = JobStatus.QUEUED - self._backend_name = job_info.get('backend').get('name') - self._api = api - self._job_id = job_info.get('id') + job_instance = cls.__new__(cls) + job_instance._status = JobStatus.QUEUED + job_instance._backend_name = job_info.get('backend').get('name') + job_instance._api = api + job_instance._job_id = job_info.get('id') # update status (need _api and _job_id) - self.status - self._status_msg = None - self._cancelled = False - self._exception = None - self._is_device = is_device + job_instance.status + job_instance._status_msg = None + job_instance._cancelled = False + job_instance._exception = None + job_instance._is_device = is_device + return job_instance def result(self, timeout=None, wait=5): # pylint: disable=arguments-differ while self._status == JobStatus.INITIALIZING: + if self._future_submit.exception(): + raise IBMQJobError('error submitting job: {}'.format( + repr(self._future_submit.exception()))) time.sleep(0.1) this_result = self._wait_for_job(timeout=timeout, wait=wait) if self._is_device and self.done: @@ -286,7 +295,6 @@ def _submit(self): } except (KeyError, TypeError): hpc = None - backend_name = qobj['config']['backend_name'] if backend_name != self._backend_name: raise QISKitError("inconsistent qobj backend " @@ -294,11 +302,7 @@ def _submit(self): self._backend_name)) submit_info = {} try: - submit_info = self._api.run_job(api_jobs, backend_name, - shots=qobj['config']['shots'], - max_credits=qobj['config']['max_credits'], - seed=seed0, - hpc=hpc) + submit_info = self._api.run_job(api_jobs, backend=backend_name) except ApiError as err: self._status = JobStatus.ERROR self._exception = err diff --git a/test/python/test_compiler.py b/test/python/test_compiler.py index d806eaf56c3f..c4f8813f2e44 100644 --- a/test/python/test_compiler.py +++ b/test/python/test_compiler.py @@ -216,8 +216,9 @@ def test_compile_run_remote(self, QE_TOKEN, QE_URL, hub=None, group=None, projec qc.cx(qubit_reg[0], qubit_reg[1]) qc.measure(qubit_reg, clbit_reg) qobj = qiskit._compiler.compile(qc, backend) - result = backend.run(qiskit.QuantumJob(qobj, backend=backend, - preformatted=True)).result() + job = backend.run(qiskit.QuantumJob(qobj, backend=backend, + preformatted=True)) + result = job.result(timeout=20) self.assertIsInstance(result, Result) @requires_qe_access diff --git a/test/python/test_ibmqjob.py b/test/python/test_ibmqjob.py index feed9a0f8cfc..8483e5a499f3 100644 --- a/test/python/test_ibmqjob.py +++ b/test/python/test_ibmqjob.py @@ -20,11 +20,10 @@ import qiskit._compiler from qiskit.backends.ibmq import IBMQProvider from qiskit.backends.ibmq.ibmqjob import IBMQJob, IBMQJobError +from qiskit.backends.ibmq.ibmqbackend import IBMQBackendError from qiskit.backends.basejob import JobStatus from .common import requires_qe_access, QiskitTestCase, slow_test -USING_HUB = False - def lowest_pending_jobs(backends): """Returns the backend with lowest pending jobs.""" @@ -252,6 +251,28 @@ def test_get_backend_name(self): job = backend.run(quantum_job) self.assertTrue(job.backend_name == backend_name) + def test_get_jobs_from_backend(self): + backends = self._provider.available_backends({'simulator': False}) + backend = lowest_pending_jobs(backends) + job_list = backend.jobs(limit=5, skip=0) + self.log.info('found %s jobs on backend %s', len(job_list), backend.name) + for job in job_list: + self.log.info('status: %s', job.status) + self.assertTrue(job_list) + + def test_retrieve_job(self): + backends = self._provider.available_backends({'simulator': False}) + backend = lowest_pending_jobs(backends) + job_list = backend.jobs(limit=1, skip=0) + job_id = job_list[0].job_id + job = backend.retrieve_job(job_id) + self.assertTrue(job_id == job.job_id) + + def test_retrieve_job_error(self): + backends = self._provider.available_backends({'simulator': False}) + backend = lowest_pending_jobs(backends) + self.assertRaises(IBMQBackendError, backend.retrieve_job, 'BAD_JOB_ID') + if __name__ == '__main__': unittest.main(verbosity=2) From 3df6680a4a95de1fa5a9f1161fb9eab7d9fc6af6 Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Tue, 29 May 2018 13:59:22 -0400 Subject: [PATCH 04/31] add name to pre-qobj job submission --- qiskit/backends/ibmq/ibmqjob.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/qiskit/backends/ibmq/ibmqjob.py b/qiskit/backends/ibmq/ibmqjob.py index 546c01420cdb..752c3f2b14cc 100644 --- a/qiskit/backends/ibmq/ibmqjob.py +++ b/qiskit/backends/ibmq/ibmqjob.py @@ -273,15 +273,18 @@ def _submit(self): qobj = self._qobj api_jobs = [] for circuit in qobj['circuits']: + job = {} if (('compiled_circuit_qasm' not in circuit) or (circuit['compiled_circuit_qasm'] is None)): compiled_circuit = compile_circuit(circuit['circuit']) circuit['compiled_circuit_qasm'] = compiled_circuit.qasm(qeflag=True) if isinstance(circuit['compiled_circuit_qasm'], bytes): - api_jobs.append({'qasm': circuit['compiled_circuit_qasm'].decode()}) + job['qasm'] = circuit['compiled_circuit_qasm'].decode() else: - api_jobs.append({'qasm': circuit['compiled_circuit_qasm']}) - + job['qasm'] = circuit['compiled_circuit_qasm'] + if 'name' in circuit: + job['name'] = circuit['name'] + api_jobs.append(job) seed0 = qobj['circuits'][0]['config']['seed'] hpc = None if 'hpc' in qobj['config']: From e00a1b54f4888c8d3ecea9226378d55391077910 Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Wed, 30 May 2018 12:56:39 -0400 Subject: [PATCH 05/31] fix bug in ibmq job submission --- qiskit/backends/ibmq/ibmqjob.py | 9 ++++++++- test/python/test_backends.py | 1 + test/python/test_ibmqjob.py | 14 ++++++-------- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/qiskit/backends/ibmq/ibmqjob.py b/qiskit/backends/ibmq/ibmqjob.py index 752c3f2b14cc..8f69dfa9ed47 100644 --- a/qiskit/backends/ibmq/ibmqjob.py +++ b/qiskit/backends/ibmq/ibmqjob.py @@ -249,6 +249,9 @@ def job_id(self): """ Return backend determined job_id (also available in status method). """ + while not self._job_id: + # job is initializing and hasn't gotten a job_id yet. + time.sleep(0.1) return self._job_id @property @@ -305,7 +308,11 @@ def _submit(self): self._backend_name)) submit_info = {} try: - submit_info = self._api.run_job(api_jobs, backend=backend_name) + submit_info = self._api.run_job(api_jobs, backend=backend_name, + shots=qobj['config']['shots'], + max_credits=qobj['config']['max_credits'], + seed=seed0, + hpc=hpc) except ApiError as err: self._status = JobStatus.ERROR self._exception = err diff --git a/test/python/test_backends.py b/test/python/test_backends.py index 4f293d856551..8a1154f23fb5 100644 --- a/test/python/test_backends.py +++ b/test/python/test_backends.py @@ -111,6 +111,7 @@ def test_remote_backend_status(self, QE_TOKEN, QE_URL, remotes = ibmq_provider.available_backends({'local': False}) remotes = remove_backends_from_list(remotes) for backend in remotes: + self.log.info(backend.status) status = backend.status schema_path = self._get_resource_path( 'deprecated/backends/backend_status_schema_py.json', path=Path.SCHEMAS) diff --git a/test/python/test_ibmqjob.py b/test/python/test_ibmqjob.py index 8483e5a499f3..48e6dfa0a896 100644 --- a/test/python/test_ibmqjob.py +++ b/test/python/test_ibmqjob.py @@ -238,8 +238,6 @@ def test_job_id(self): qobj = qiskit._compiler.compile(self._qc, backend) quantum_job = QuantumJob(qobj, backend, preformatted=True) job = backend.run(quantum_job) - while job.status['status'] == JobStatus.INITIALIZING: - time.sleep(0.1) self.log.info('job_id: %s', job.job_id) self.assertTrue(job.job_id is not None) @@ -261,12 +259,12 @@ def test_get_jobs_from_backend(self): self.assertTrue(job_list) def test_retrieve_job(self): - backends = self._provider.available_backends({'simulator': False}) - backend = lowest_pending_jobs(backends) - job_list = backend.jobs(limit=1, skip=0) - job_id = job_list[0].job_id - job = backend.retrieve_job(job_id) - self.assertTrue(job_id == job.job_id) + backend = self._provider.get_backend('ibmq_qasm_simulator') + qobj = qiskit._compiler.compile(self._qc, backend) + quantum_job = QuantumJob(qobj, backend, preformatted=True) + job = backend.run(quantum_job) + rjob = backend.retrieve_job(job.job_id) + self.assertTrue(job.job_id == rjob.job_id) def test_retrieve_job_error(self): backends = self._provider.available_backends({'simulator': False}) From a49ea945270fa165fbf6c981952914f122b1f235 Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Wed, 30 May 2018 15:49:03 -0400 Subject: [PATCH 06/31] add circuit names to non-qobj ibmq job submission --- qiskit/backends/ibmq/ibmqbackend.py | 19 +++++++++++--- qiskit/backends/ibmq/ibmqjob.py | 22 +++++++++------- test/python/test_ibmqjob.py | 40 ++++++++++++++++++++--------- 3 files changed, 56 insertions(+), 25 deletions(-) diff --git a/qiskit/backends/ibmq/ibmqbackend.py b/qiskit/backends/ibmq/ibmqbackend.py index acfe780373f3..070b0839e4ff 100644 --- a/qiskit/backends/ibmq/ibmqbackend.py +++ b/qiskit/backends/ibmq/ibmqbackend.py @@ -22,7 +22,7 @@ class IBMQBackend(BaseBackend): """Backend class interfacing with the Quantum Experience remotely. """ - + def __init__(self, configuration, api=None): """Initialize remote backend for IBM Quantum Experience. @@ -148,12 +148,12 @@ def jobs(self, limit=50, skip=0): limit (int): number of jobs to retrieve skip (int): starting index of retrieval - Return: + Returns: list(IBMQJob): list of IBMQJob instances """ backend_name = self.configuration['name'] job_list = [] - base_index = 0 + base_index = skip job_info_list = self._api.get_jobs(limit=limit, skip=base_index) while len(job_list) < limit or len(job_info_list) < limit: base_index += limit @@ -167,7 +167,17 @@ def jobs(self, limit=50, skip=0): return job_list def retrieve_job(self, job_id): - """Attempt to get the specified job by job_id""" + """Attempt to get the specified job by job_id + + Args: + job_id (str): the job id of the job to retrieve + + Returns: + IBMQJob: class instance + + Raises: + IBMQBackendError: if retrieval failed + """ job_info = self._api.get_job(job_id) if 'error' in job_info: raise IBMQBackendError('failed to get job id "{}"'.format(job_id)) @@ -175,6 +185,7 @@ def retrieve_job(self, job_id): job = IBMQJob.from_api(job_info, self._api, is_device) return job + class IBMQBackendError(QISKitError): """IBM Q Backend Errors""" pass diff --git a/qiskit/backends/ibmq/ibmqjob.py b/qiskit/backends/ibmq/ibmqjob.py index 8f69dfa9ed47..19e1eef6c4b7 100644 --- a/qiskit/backends/ibmq/ibmqjob.py +++ b/qiskit/backends/ibmq/ibmqjob.py @@ -18,13 +18,11 @@ from IBMQuantumExperience import ApiError from qiskit._compiler import compile_circuit -from qiskit import QuantumJob from qiskit.backends import BaseJob from qiskit.backends.basejob import JobStatus from qiskit._qiskiterror import QISKitError from qiskit._result import Result from qiskit._resulterror import ResultError -from forkable import ForkablePdb logger = logging.getLogger(__name__) @@ -37,9 +35,6 @@ class IBMQJob(BaseJob): """ _executor = futures.ThreadPoolExecutor() - def __init__(self, arg, api, is_device): - pass - def __init__(self, q_job, api, is_device): """IBMQJob init function. @@ -84,6 +79,9 @@ def from_api(cls, job_info, api, is_device): 'userId': 'user id'} api (IBMQuantumExperience): IBM Q API is_device (bool): whether backend is a real device # TODO: remove this after Qobj + + Returns: + IBMQJob: an instance of this class """ job_instance = cls.__new__(cls) job_instance._status = JobStatus.QUEUED @@ -91,6 +89,7 @@ def from_api(cls, job_info, api, is_device): job_instance._api = api job_instance._job_id = job_info.get('id') # update status (need _api and _job_id) + # pylint: disable=pointless-statement job_instance.status job_instance._status_msg = None job_instance._cancelled = False @@ -375,10 +374,15 @@ def _wait_for_job(self, timeout=60, wait=5): return Result(job_result, qobj) api_result = self._api.get_job(job_id) job_result_return = [] - for index in range(len(api_result['qasms'])): - job_result_return.append({'data': api_result['qasms'][index]['data'], - 'status': api_result['qasms'][index]['status']}) - job_result = {'job_id': job_id, 'status': api_result['status'], + for circuit_result in api_result['qasms']: + job_result_return.append( + {'data': circuit_result['data'], + 'name': circuit_result.get('name'), + 'compiled_circuit_qasm': circuit_result.get('qasm'), + 'status': circuit_result['status']}) + job_result = {'job_id': job_id, + 'status': api_result['status'], + 'used_credits': api_result.get('usedCredits'), 'result': job_result_return} logger.info('Got a result for qobj: %s from remote backend %s with job id: %s', qobj["id"], qobj['config']['backend_name'], diff --git a/test/python/test_ibmqjob.py b/test/python/test_ibmqjob.py index 48e6dfa0a896..56e66bc185be 100644 --- a/test/python/test_ibmqjob.py +++ b/test/python/test_ibmqjob.py @@ -56,22 +56,38 @@ def setUpClass(cls, QE_TOKEN, QE_URL, hub=None, group=None, project=None): def test_run_simulator(self): backend = self._provider.get_backend('ibmq_qasm_simulator') - qobj = qiskit._compiler.compile(self._qc, backend) + qr = QuantumRegister(2, 'q') + cr = ClassicalRegister(2, 'c') + qc = QuantumCircuit(qr, cr, name='hadamard') + qc.h(qr) + qc.measure(qr, cr) + qobj = qiskit._compiler.compile([self._qc, qc], backend) shots = qobj['config']['shots'] quantum_job = QuantumJob(qobj, backend, preformatted=True) job = backend.run(quantum_job) result = job.result() - counts_qx = result.get_counts(result.get_names()[0]) - counts_ex = {'00': shots/2, '11': shots/2} - states = counts_qx.keys() | counts_ex.keys() + counts_qx1 = result.get_counts(result.get_names()[0]) + counts_qx2 = result.get_counts('hadamard') + counts_ex1 = {'00': shots/2, '11': shots/2} + counts_ex2 = {'00': shots/4, '11': shots/4, '10': shots/4, + '01': shots/4} + states1 = counts_qx1.keys() | counts_ex1.keys() + states2 = counts_qx2.keys() | counts_ex2.keys() # contingency table - ctable = numpy.array([[counts_qx.get(key, 0) for key in states], - [counts_ex.get(key, 0) for key in states]]) - self.log.info('states: %s', str(states)) - self.log.info('ctable: %s', str(ctable)) - contingency = chi2_contingency(ctable) - self.log.info('chi2_contingency: %s', str(contingency)) - self.assertGreater(contingency[1], 0.01) + ctable1 = numpy.array([[counts_qx1.get(key, 0) for key in states1], + [counts_ex1.get(key, 0) for key in states1]]) + ctable2 = numpy.array([[counts_qx2.get(key, 0) for key in states2], + [counts_ex2.get(key, 0) for key in states2]]) + self.log.info('states1: %s', str(states1)) + self.log.info('states2: %s', str(states2)) + self.log.info('ctable1: %s', str(ctable1)) + self.log.info('ctable2: %s', str(ctable2)) + contingency1 = chi2_contingency(ctable1) + contingency2 = chi2_contingency(ctable2) + self.log.info('chi2_contingency1: %s', str(contingency1)) + self.log.info('chi2_contingency2: %s', str(contingency2)) + self.assertGreater(contingency1[1], 0.01) + self.assertGreater(contingency2[1], 0.01) @slow_test def test_run_device(self): @@ -271,6 +287,6 @@ def test_retrieve_job_error(self): backend = lowest_pending_jobs(backends) self.assertRaises(IBMQBackendError, backend.retrieve_job, 'BAD_JOB_ID') - + if __name__ == '__main__': unittest.main(verbosity=2) From 5909a0d44b4952dd8976e7a00e2d0d07b3fe19ab Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Thu, 31 May 2018 14:31:05 -0400 Subject: [PATCH 07/31] remove qobj from result the last place left is Result._reorder_bits --- qiskit/_result.py | 54 +++++++------------ qiskit/backends/ibmq/ibmqjob.py | 33 ++++++------ qiskit/backends/local/qasm_simulator_cpp.py | 4 +- qiskit/backends/local/qasm_simulator_py.py | 2 +- .../local/statevector_simulator_py.py | 2 +- qiskit/backends/local/unitary_simulator_py.py | 4 +- qiskit/tools/file_io.py | 7 +-- test/python/_dummybackend.py | 4 +- test/python/test_ibmqjob.py | 7 ++- test/python/test_quantumprogram.py | 2 +- 10 files changed, 52 insertions(+), 67 deletions(-) diff --git a/qiskit/_result.py b/qiskit/_result.py index 0d63a34fed3b..7457dadcd139 100644 --- a/qiskit/_result.py +++ b/qiskit/_result.py @@ -24,7 +24,6 @@ class Result(object): Internal:: - qobj = { -- the quantum object that was complied --} result = { "job_id": --job-id (string), #This string links the result with the job that computes it, @@ -45,9 +44,8 @@ class Result(object): } """ - def __init__(self, qobj_result, qobj): + def __init__(self, qobj_result): self._result = qobj_result - self._qobj = qobj def __str__(self): """Get the status of the run. @@ -76,20 +74,8 @@ def __iadd__(self, other): # TODO: reevaluate if moving equality to Backend themselves (part of # a bigger problem - backend instances will not persist between # sessions) - my_config = copy.deepcopy(self._qobj['config']) - other_config = copy.deepcopy(other._qobj['config']) - my_backend = my_config.pop('backend_name') - other_backend = other_config.pop('backend_name') - - if my_config == other_config and my_backend == other_backend: - if isinstance(self._qobj['id'], str): - self._qobj['id'] = [self._qobj['id']] - self._qobj['id'].append(other._qobj['id']) - self._qobj['circuits'] += other._qobj['circuits'] - self._result['result'] += other._result['result'] - return self - else: - raise QISKitError('Result objects have different configs and cannot be combined.') + self._result['result'] += other._result['result'] + return self def __add__(self, other): """Combine Result objects. @@ -110,7 +96,7 @@ def _is_error(self): return self._result['status'] == 'ERROR' def get_status(self): - """Return whole qobj result status.""" + """Return whole result status.""" return self._result['status'] def circuit_statuses(self): @@ -152,10 +138,9 @@ def get_ran_qasm(self, name): QISKitError: if the circuit was not found. """ try: - qobj = self._qobj - for index in range(len(qobj["circuits"])): - if qobj["circuits"][index]['name'] == name: - return qobj["circuits"][index]["compiled_circuit_qasm"] + for exp_result in self._result['result']: + if exp_result.get('name') == name: + return exp_result['compiled_circuit_qasm'] except KeyError: pass raise QISKitError('No qasm for circuit "{0}"'.format(name)) @@ -211,9 +196,8 @@ def get_data(self, circuit=None): circuit = circuit.name if circuit is None: - circuits = list([i['name'] for i in self._qobj['circuits']]) - if len(circuits) == 1: - circuit = circuits[0] + if len(self._result['result']) == 1: + return self._result['result'][0]['data'] else: raise QISKitError("You have to select a circuit when there is more than" "one available") @@ -221,10 +205,9 @@ def get_data(self, circuit=None): if not isinstance(circuit, str): circuit = str(circuit) try: - qobj = self._qobj - for index in range(len(qobj['circuits'])): - if qobj['circuits'][index]['name'] == circuit: - return self._result['result'][index]['data'] + for circuit_result in self._result['result']: + if circuit_result.get('name') == circuit: + return circuit_result['data'] except (KeyError, TypeError): pass raise QISKitError('No data for circuit "{0}"'.format(circuit)) @@ -365,7 +348,7 @@ def get_names(self): Returns: List: A list of circuit names. """ - return [c['name'] for c in self._qobj['circuits']] + return [c.get('name') for c in self._result['result']] def average_data(self, name, observable): """Compute the mean value of an diagonal observable. @@ -390,12 +373,13 @@ def average_data(self, name, observable): temp += counts[key] * observable[key] / tot return temp - def get_qubitpol_vs_xval(self, xvals_dict=None): + def get_qubitpol_vs_xval(self, nqubits, xvals_dict=None): """Compute the polarization of each qubit for all circuits and pull out each circuits xval into an array. Assumes that each circuit has the same number of qubits and that all qubits are measured. Args: + nqubits (int): number of qubits xvals_dict (dict): xvals for each circuit {'circuitname1': xval1,...}. If this is none then the xvals list is just left as an array of zeros @@ -403,9 +387,8 @@ def get_qubitpol_vs_xval(self, xvals_dict=None): qubit_pol: mxn double array where m is the number of circuit, n the number of qubits xvals: mx1 array of the circuit xvals """ - ncircuits = len(self._qobj['circuits']) + ncircuits = len(self._result['result']) # Is this the best way to get the number of qubits? - nqubits = self._qobj['circuits'][0]['compiled_circuit']['header']['number_of_qubits'] qubitpol = numpy.zeros([ncircuits, nqubits], dtype=float) xvals = numpy.zeros([ncircuits], dtype=float) @@ -421,10 +404,11 @@ def get_qubitpol_vs_xval(self, xvals_dict=None): # go through each circuit and for eqch qubit and apply the operators using "average_data" for circuit_ind in range(ncircuits): + circuit_name = self._result['result'][circuit_ind]['name'] if xvals_dict: - xvals[circuit_ind] = xvals_dict[self._qobj['circuits'][circuit_ind]['name']] + xvals[circuit_ind] = xvals_dict[circuit_name] for qubit_ind in range(nqubits): qubitpol[circuit_ind, qubit_ind] = self.average_data( - self._qobj['circuits'][circuit_ind]['name'], z_dicts[qubit_ind]) + circuit_name, z_dicts[qubit_ind]) return qubitpol, xvals diff --git a/qiskit/backends/ibmq/ibmqjob.py b/qiskit/backends/ibmq/ibmqjob.py index 19e1eef6c4b7..3d2054e475c6 100644 --- a/qiskit/backends/ibmq/ibmqjob.py +++ b/qiskit/backends/ibmq/ibmqjob.py @@ -286,6 +286,8 @@ def _submit(self): job['qasm'] = circuit['compiled_circuit_qasm'] if 'name' in circuit: job['name'] = circuit['name'] + # n_qubits = circuit['compiled_circuit']['header']['number_of_qubits'] + # job['number_of_qubits'] = n_qubits api_jobs.append(job) seed0 = qobj['circuits'][0]['config']['seed'] hpc = None @@ -336,18 +338,18 @@ def _wait_for_job(self, timeout=60, wait=5): Raises: QISKitError: job didn't return status or reported error in status """ - qobj = self._q_job.qobj + # qobj = self._q_job.qobj job_id = self.job_id - logger.info('Running qobj: %s on remote backend %s with job id: %s', - qobj["id"], qobj['config']['backend_name'], - job_id) + # logger.info('Running qobj: %s on remote backend %s with job id: %s', + # qobj["id"], qobj['config']['backend_name'], + # job_id) timer = 0 api_result = self._api.get_job(job_id) while not (self.done or self.cancelled or self.exception): if timeout is not None and timer >= timeout: job_result = {'job_id': job_id, 'status': 'ERROR', 'result': 'QISkit Time Out'} - return Result(job_result, qobj) + return Result(job_result) time.sleep(wait) timer += wait logger.info('status = %s (%d seconds)', api_result['status'], timer) @@ -362,16 +364,16 @@ def _wait_for_job(self, timeout=60, wait=5): api_result['status'] == 'ERROR_RUNNING_JOB'): job_result = {'job_id': job_id, 'status': 'ERROR', 'result': api_result['status']} - return Result(job_result, qobj) + return Result(job_result) if self.cancelled: job_result = {'job_id': job_id, 'status': 'CANCELLED', 'result': 'job cancelled'} - return Result(job_result, qobj) + return Result(job_result) elif self.exception: job_result = {'job_id': job_id, 'status': 'ERROR', 'result': str(self.exception)} - return Result(job_result, qobj) + return Result(job_result) api_result = self._api.get_job(job_id) job_result_return = [] for circuit_result in api_result['qasms']: @@ -384,12 +386,11 @@ def _wait_for_job(self, timeout=60, wait=5): 'status': api_result['status'], 'used_credits': api_result.get('usedCredits'), 'result': job_result_return} - logger.info('Got a result for qobj: %s from remote backend %s with job id: %s', - qobj["id"], qobj['config']['backend_name'], - job_id) - job_result['name'] = qobj['id'] - job_result['backend'] = qobj['config']['backend_name'] - return Result(job_result, qobj) + # logger.info('Got a result for qobj: %s from remote backend %s with job id: %s', + # qobj["id"], qobj['config']['backend_name'], + # job_id) + job_result['backend_name'] = self.backend_name + return Result(job_result) class IBMQJobError(QISKitError): @@ -401,8 +402,8 @@ 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']): - + import pdb;pdb.set_trace() + for idx, circ in enumerate(result._result['result']['qasms']): # device_qubit -> device_clbit (how it should have been) measure_dict = {op['qubits'][0]: op['clbits'][0] for op in circ['compiled_circuit']['operations'] diff --git a/qiskit/backends/local/qasm_simulator_cpp.py b/qiskit/backends/local/qasm_simulator_cpp.py index 06bec0d4d26f..7492784d05b8 100644 --- a/qiskit/backends/local/qasm_simulator_cpp.py +++ b/qiskit/backends/local/qasm_simulator_cpp.py @@ -78,7 +78,7 @@ def _run_job(self, q_job): qobj = q_job.qobj self._validate(qobj) result = run(qobj, self._configuration['exe']) - return Result(result, qobj) + return Result(result) def _validate(self, qobj): if qobj['config']['shots'] == 1: @@ -147,7 +147,7 @@ def _run_job(self, q_job): qobj['config'] = {'simulator': 'clifford'} result = run(qobj, self._configuration['exe']) - return Result(result, qobj) + return Result(result) def _validate(self): return diff --git a/qiskit/backends/local/qasm_simulator_py.py b/qiskit/backends/local/qasm_simulator_py.py index 02c5c15557d5..499873095aea 100644 --- a/qiskit/backends/local/qasm_simulator_py.py +++ b/qiskit/backends/local/qasm_simulator_py.py @@ -290,7 +290,7 @@ def _run_job(self, q_job): 'status': 'COMPLETED', 'success': True, 'time_taken': (end - start)} - return Result(result, qobj) + return Result(result) def run_circuit(self, circuit): """Run a circuit and return a single Result. diff --git a/qiskit/backends/local/statevector_simulator_py.py b/qiskit/backends/local/statevector_simulator_py.py index 8a78531c8f20..313047f93b1e 100644 --- a/qiskit/backends/local/statevector_simulator_py.py +++ b/qiskit/backends/local/statevector_simulator_py.py @@ -79,7 +79,7 @@ def _run_job(self, q_job): # Remove snapshot dict if empty if snapshots == {}: res['data'].pop('snapshots', None) - return Result(result, qobj) + return Result(result) def _validate(self, qobj): """Semantic validations of the qobj which cannot be done via schemas. diff --git a/qiskit/backends/local/unitary_simulator_py.py b/qiskit/backends/local/unitary_simulator_py.py index ad59bf708f92..29b56b0e8c2b 100644 --- a/qiskit/backends/local/unitary_simulator_py.py +++ b/qiskit/backends/local/unitary_simulator_py.py @@ -166,8 +166,7 @@ def _run_job(self, q_job): result_list.append(self.run_circuit(circuit)) job_id = str(uuid.uuid4()) return Result( - {'job_id': job_id, 'result': result_list, 'status': 'COMPLETED'}, - qobj) + {'job_id': job_id, 'result': result_list, 'status': 'COMPLETED'}) def run_circuit(self, circuit): """Apply the single-qubit gate.""" @@ -175,6 +174,7 @@ def run_circuit(self, circuit): self._number_of_qubits = ccircuit['header']['number_of_qubits'] result = {} result['data'] = {} + result['name'] = circuit.get('name') self._unitary_state = np.identity(2**(self._number_of_qubits), dtype=complex) for operation in ccircuit['operations']: diff --git a/qiskit/tools/file_io.py b/qiskit/tools/file_io.py index 99f8822fae2e..31d6463af385 100644 --- a/qiskit/tools/file_io.py +++ b/qiskit/tools/file_io.py @@ -138,7 +138,6 @@ def load_result_from_file(filename): master_dict = json.load(load_file) try: - qobj = master_dict['qobj'] qresult_dict = master_dict['result'] convert_json_to_qobj(qresult_dict) metadata = master_dict['metadata'] @@ -146,7 +145,7 @@ def load_result_from_file(filename): raise QISKitError('File %s does not have the proper dictionary ' 'structure') - qresult = qiskit.Result(qresult_dict, qobj) + qresult = qiskit.Result(qresult_dict) return qresult, metadata @@ -172,8 +171,7 @@ def default(self, o): def save_result_to_file(resultobj, filename, metadata=None): - """Save a result (qobj + result) and optional metatdata - to a single dictionary file. + """Save a result and optional metatdata to a single dictionary file. Args: resultobj (Result): Result to save @@ -189,7 +187,6 @@ def save_result_to_file(resultobj, filename, metadata=None): String: full file path """ master_dict = { - 'qobj': copy.deepcopy(resultobj._qobj), 'result': copy.deepcopy(resultobj._result) } if metadata is None: diff --git a/test/python/_dummybackend.py b/test/python/_dummybackend.py index 8c42800f799c..c1e69eac9df4 100644 --- a/test/python/_dummybackend.py +++ b/test/python/_dummybackend.py @@ -66,14 +66,14 @@ def __init__(self, configuration=None, time_alive=10): def run(self, q_job): return DummyJob(self.run_job, q_job) + # pylint: disable=unused-argument def run_job(self, q_job): """ Main dummy simulator loop """ job_id = str(uuid.uuid4()) - qobj = q_job.qobj time.sleep(self.time_alive) - return Result({'job_id': job_id, 'result': [], 'status': 'COMPLETED'}, qobj) + return Result({'job_id': job_id, 'result': [], 'status': 'COMPLETED'}) class DummyJob(BaseJob): diff --git a/test/python/test_ibmqjob.py b/test/python/test_ibmqjob.py index 56e66bc185be..ebac54041b43 100644 --- a/test/python/test_ibmqjob.py +++ b/test/python/test_ibmqjob.py @@ -266,13 +266,15 @@ def test_get_backend_name(self): self.assertTrue(job.backend_name == backend_name) def test_get_jobs_from_backend(self): - backends = self._provider.available_backends({'simulator': False}) + backends = self._provider.available_backends() backend = lowest_pending_jobs(backends) job_list = backend.jobs(limit=5, skip=0) self.log.info('found %s jobs on backend %s', len(job_list), backend.name) for job in job_list: self.log.info('status: %s', job.status) - self.assertTrue(job_list) + result = job.result() + for circuit_name in result.get_names(): + self.log.info(result.get_data(circuit_name=circuit_name)) def test_retrieve_job(self): backend = self._provider.get_backend('ibmq_qasm_simulator') @@ -281,6 +283,7 @@ def test_retrieve_job(self): job = backend.run(quantum_job) rjob = backend.retrieve_job(job.job_id) self.assertTrue(job.job_id == rjob.job_id) + self.assertTrue(job.result().get_counts() == rjob.result().get_counts()) def test_retrieve_job_error(self): backends = self._provider.available_backends({'simulator': False}) diff --git a/test/python/test_quantumprogram.py b/test/python/test_quantumprogram.py index bccbd26138e9..3cd14af70688 100644 --- a/test/python/test_quantumprogram.py +++ b/test/python/test_quantumprogram.py @@ -1511,7 +1511,7 @@ def test_qubitpol(self): result = q_program.execute(circuits, backend='local_qasm_simulator') - yvals, xvals = result.get_qubitpol_vs_xval(xvals_dict=xvals_dict) + yvals, xvals = result.get_qubitpol_vs_xval(2, xvals_dict=xvals_dict) self.assertTrue(np.array_equal(yvals, [[-1, -1], [1, -1]])) self.assertTrue(np.array_equal(xvals, [0, 1])) From f4e98982abb6150432eb28a402cf3506746d281b Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Fri, 1 Jun 2018 17:40:02 -0400 Subject: [PATCH 08/31] add temporary hack for bit reordering --- qiskit/backends/ibmq/ibmqbackend.py | 17 +++++++----- qiskit/backends/ibmq/ibmqjob.py | 37 ++++++++++++++++++++------ test/python/test_ibmqjob.py | 10 +++----- test/python/test_reordering.py | 40 +++++++++++++++++++++-------- 4 files changed, 73 insertions(+), 31 deletions(-) diff --git a/qiskit/backends/ibmq/ibmqbackend.py b/qiskit/backends/ibmq/ibmqbackend.py index 070b0839e4ff..2b947d0e7295 100644 --- a/qiskit/backends/ibmq/ibmqbackend.py +++ b/qiskit/backends/ibmq/ibmqbackend.py @@ -141,28 +141,31 @@ def status(self): "Couldn't get backend status: {0}".format(ex)) return status - def jobs(self, limit=50, skip=0): + def jobs(self, limit=50, skip=0, status=None): """Attempt to get the jobs submitted to the backend Args: limit (int): number of jobs to retrieve skip (int): starting index of retrieval - + status (None or JobStatus): only get jobs with this status. Returns: list(IBMQJob): list of IBMQJob instances """ backend_name = self.configuration['name'] job_list = [] base_index = skip - job_info_list = self._api.get_jobs(limit=limit, skip=base_index) + job_info_list = self._api.get_jobs(limit=limit, skip=base_index, + backend=backend_name) while len(job_list) < limit or len(job_info_list) < limit: base_index += limit job_info_list = self._api.get_jobs(limit=limit, skip=base_index) for job_info in job_info_list: - if job_info.get('backend').get('name') == backend_name: - is_device = not bool(self._configuration.get('simulator')) - job = IBMQJob.from_api(job_info, self._api, is_device) - if len(job_list) < limit: + is_device = not bool(self._configuration.get('simulator')) + job = IBMQJob.from_api(job_info, self._api, is_device) + if len(job_list) < limit: + if status is None: + job_list.append(job) + elif job.status.get('status') == status: job_list.append(job) return job_list diff --git a/qiskit/backends/ibmq/ibmqjob.py b/qiskit/backends/ibmq/ibmqjob.py index 3d2054e475c6..062aff0e71df 100644 --- a/qiskit/backends/ibmq/ibmqjob.py +++ b/qiskit/backends/ibmq/ibmqjob.py @@ -17,12 +17,12 @@ import pprint from IBMQuantumExperience import ApiError -from qiskit._compiler import compile_circuit from qiskit.backends import BaseJob from qiskit.backends.basejob import JobStatus from qiskit._qiskiterror import QISKitError from qiskit._result import Result from qiskit._resulterror import ResultError +from qiskit._compiler import compile_circuit logger = logging.getLogger(__name__) @@ -55,6 +55,7 @@ def __init__(self, q_job, api, is_device): self._cancelled = False self._exception = None self._is_device = is_device + self._from_api = False @classmethod def from_api(cls, job_info, api, is_device): @@ -88,16 +89,31 @@ def from_api(cls, job_info, api, is_device): job_instance._backend_name = job_info.get('backend').get('name') job_instance._api = api job_instance._job_id = job_info.get('id') + job_instance._exception = None # needs to be before status call below # update status (need _api and _job_id) # pylint: disable=pointless-statement job_instance.status job_instance._status_msg = None job_instance._cancelled = False - job_instance._exception = None job_instance._is_device = is_device + job_instance._from_api = True return job_instance - def result(self, timeout=None, wait=5): + def result(self, timeout=None, wait=5, qobj=None): + """Return the result from the job. + + Args: + timeout (int): number of seconds to wait for job + wait (int): time between queries to IBM Q server + qobj (dict): temporarily used to correct bit ordering until we + have Qobj object + + Returns: + Result: Result object + + Raises: + IBMQJobError: exception raised during job initialization + """ # pylint: disable=arguments-differ while self._status == JobStatus.INITIALIZING: if self._future_submit.exception(): @@ -106,7 +122,11 @@ def result(self, timeout=None, wait=5): time.sleep(0.1) this_result = self._wait_for_job(timeout=timeout, wait=wait) if self._is_device and self.done: - _reorder_bits(this_result) # TODO: remove this after Qobj + if qobj: + reorder_bits(this_result, qobj) # TODO: remove this after Qobj + else: + logger.warning('possible bit reordering cannot be applied ' + 'without qobj dictionary in call to "result()"') if this_result.get_status() == 'ERROR': self._status = JobStatus.ERROR else: @@ -345,7 +365,8 @@ def _wait_for_job(self, timeout=60, wait=5): # job_id) timer = 0 api_result = self._api.get_job(job_id) - while not (self.done or self.cancelled or self.exception): + while not (self.done or self.cancelled or self.exception or + self._status == JobStatus.ERROR): if timeout is not None and timer >= timeout: job_result = {'job_id': job_id, 'status': 'ERROR', 'result': 'QISkit Time Out'} @@ -398,12 +419,12 @@ class IBMQJobError(QISKitError): pass -def _reorder_bits(result): +def reorder_bits(result, qobj): """temporary fix for ibmq backends. for every ran circuit, get reordering information from qobj and apply reordering on result""" - import pdb;pdb.set_trace() - for idx, circ in enumerate(result._result['result']['qasms']): + for idx, circ in enumerate(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'] diff --git a/test/python/test_ibmqjob.py b/test/python/test_ibmqjob.py index ebac54041b43..2f4fc27bd25f 100644 --- a/test/python/test_ibmqjob.py +++ b/test/python/test_ibmqjob.py @@ -105,7 +105,7 @@ def test_run_device(self): if job.exception: raise job.exception self.log.info(job.status) - result = job.result() + result = job.result(qobj=qobj) counts_qx = result.get_counts(result.get_names()[0]) counts_ex = {'00': shots/2, '11': shots/2} states = counts_qx.keys() | counts_ex.keys() @@ -156,7 +156,7 @@ def test_run_async_simulator(self): '{0} s'.format(timeout)) time.sleep(0.2) - result_array = [job.result() for job in job_array] + result_array = [job.result(qobj=qobj) for job in job_array] self.log.info('got back all job results') # Ensure all jobs have finished. self.assertTrue(all([job.done for job in job_array])) @@ -202,7 +202,7 @@ def test_run_async_device(self): self.assertTrue(num_jobs - num_error - num_done > 0) # Wait for all the results. - result_array = [job.result() for job in job_array] + result_array = [job.result(qobj=qobj) for job in job_array] # Ensure all jobs have finished. self.assertTrue(all([job.done for job in job_array])) @@ -272,9 +272,7 @@ def test_get_jobs_from_backend(self): self.log.info('found %s jobs on backend %s', len(job_list), backend.name) for job in job_list: self.log.info('status: %s', job.status) - result = job.result() - for circuit_name in result.get_names(): - self.log.info(result.get_data(circuit_name=circuit_name)) + self.assertTrue(isinstance(job.job_id, str)) def test_retrieve_job(self): backend = self._provider.get_backend('ibmq_qasm_simulator') diff --git a/test/python/test_reordering.py b/test/python/test_reordering.py index 18a2ea4869ed..0f7696045aac 100644 --- a/test/python/test_reordering.py +++ b/test/python/test_reordering.py @@ -11,7 +11,9 @@ import unittest import qiskit -from qiskit.wrapper import register, available_backends, get_backend, execute +from qiskit import QuantumJob +from qiskit.wrapper import register, available_backends, get_backend +from qiskit.wrapper import compile as qcompile from .common import requires_qe_access, QiskitTestCase, slow_test @@ -34,10 +36,12 @@ class TestBitReordering(QiskitTestCase): @requires_qe_access def test_basic_reordering(self, QE_TOKEN, QE_URL, hub=None, group=None, project=None): """a simple reordering within a 2-qubit register""" - sim, real = self._get_backends(QE_TOKEN, QE_URL, hub, group, project) + sim_backend_name, real_backend_name = self._get_backends( + QE_TOKEN, QE_URL, hub, group, project) + sim = get_backend(sim_backend_name) + real = get_backend(real_backend_name) if not sim or not real: raise unittest.SkipTest('no remote device available') - q = qiskit.QuantumRegister(2) c = qiskit.ClassicalRegister(2) circ = qiskit.QuantumCircuit(q, c) @@ -46,8 +50,14 @@ def test_basic_reordering(self, QE_TOKEN, QE_URL, hub=None, group=None, project= circ.measure(q[1], c[0]) shots = 2000 - result_real = execute(circ, real, {"shots": shots}).result(timeout=600) - result_sim = execute(circ, sim, {"shots": shots}).result() + qobj_real = qcompile(circ, real, shots=shots) + qobj_sim = qcompile(circ, sim, shots=shots) + q_job_real = QuantumJob(qobj_real, backend=real, preformatted=True, + shots=shots) + q_job_sim = QuantumJob(qobj_sim, backend=sim, preformatted=True, + shots=shots) + result_real = real.run(q_job_real).result(timeout=600, qobj=qobj_real) + result_sim = sim.run(q_job_sim).result(timeout=600) counts_real = result_real.get_counts() counts_sim = result_sim.get_counts() self.log.info(counts_real) @@ -57,11 +67,15 @@ def test_basic_reordering(self, QE_TOKEN, QE_URL, hub=None, group=None, project= @slow_test @requires_qe_access - def test_multi_register_reordering(self, QE_TOKEN, QE_URL, hub=None, group=None, project=None): + def test_multi_register_reordering(self, QE_TOKEN, QE_URL, + hub=None, group=None, project=None): """a more complicated reordering across 3 registers of different sizes""" - sim, real = self._get_backends(QE_TOKEN, QE_URL, hub, group, project) - if not sim or not real: + sim_backend_name, real_backend_name = self._get_backends( + QE_TOKEN, QE_URL, hub, group, project) + if not sim_backend_name or not real_backend_name: raise unittest.SkipTest('no remote device available') + sim = get_backend(sim_backend_name) + real = get_backend(real_backend_name) q0 = qiskit.QuantumRegister(2) q1 = qiskit.QuantumRegister(2) @@ -83,8 +97,14 @@ def test_multi_register_reordering(self, QE_TOKEN, QE_URL, hub=None, group=None, circ.measure(q2[0], c1[1]) shots = 4000 - result_real = execute(circ, real, {"shots": shots}).result(timeout=600) - result_sim = execute(circ, sim, {"shots": shots}).result() + qobj_real = qcompile(circ, real, shots=shots) + qobj_sim = qcompile(circ, sim, shots=shots) + q_job_real = QuantumJob(qobj_real, backend=real, preformatted=True, + shots=shots) + q_job_sim = QuantumJob(qobj_sim, backend=sim, preformatted=True, + shots=shots) + result_real = real.run(q_job_real).result(timeout=600, qobj=qobj_real) + result_sim = sim.run(q_job_sim).result(timeout=600) counts_real = result_real.get_counts() counts_sim = result_sim.get_counts() threshold = 0.2 * shots From 5a77bd9f2dcaf0e5e40de8356456ae43dcf289f2 Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Tue, 5 Jun 2018 16:28:16 -0400 Subject: [PATCH 09/31] remove qobj keyword from ibmqjob.result() --- qiskit/backends/ibmq/ibmqjob.py | 71 +++++++++++++++++++-------------- test/python/test_ibmqjob.py | 2 +- 2 files changed, 42 insertions(+), 31 deletions(-) diff --git a/qiskit/backends/ibmq/ibmqjob.py b/qiskit/backends/ibmq/ibmqjob.py index 062aff0e71df..0418f460df32 100644 --- a/qiskit/backends/ibmq/ibmqjob.py +++ b/qiskit/backends/ibmq/ibmqjob.py @@ -15,8 +15,9 @@ import time import logging import pprint +import json +import numpy -from IBMQuantumExperience import ApiError from qiskit.backends import BaseJob from qiskit.backends.basejob import JobStatus from qiskit._qiskiterror import QISKitError @@ -99,14 +100,12 @@ def from_api(cls, job_info, api, is_device): job_instance._from_api = True return job_instance - def result(self, timeout=None, wait=5, qobj=None): + def result(self, timeout=None, wait=5): """Return the result from the job. Args: timeout (int): number of seconds to wait for job wait (int): time between queries to IBM Q server - qobj (dict): temporarily used to correct bit ordering until we - have Qobj object Returns: Result: Result object @@ -122,11 +121,7 @@ def result(self, timeout=None, wait=5, qobj=None): time.sleep(0.1) this_result = self._wait_for_job(timeout=timeout, wait=wait) if self._is_device and self.done: - if qobj: - reorder_bits(this_result, qobj) # TODO: remove this after Qobj - else: - logger.warning('possible bit reordering cannot be applied ' - 'without qobj dictionary in call to "result()"') + _reorder_bits(this_result) if this_result.get_status() == 'ERROR': self._status = JobStatus.ERROR else: @@ -187,7 +182,7 @@ def status(self): self._status = JobStatus.DONE elif job_result['status'] == 'CANCELLED': self._status = JobStatus.CANCELLED - elif self.exception: + elif self.exception or self._future_submit.exception(): self._status = JobStatus.ERROR if self._future_submit.exception(): self._exception = self._future_submit.exception() @@ -306,8 +301,11 @@ def _submit(self): job['qasm'] = circuit['compiled_circuit_qasm'] if 'name' in circuit: job['name'] = circuit['name'] - # n_qubits = circuit['compiled_circuit']['header']['number_of_qubits'] - # job['number_of_qubits'] = n_qubits + # convert numpy types for json serialization + compiled_circuit = json.loads( + json.dumps(circuit['compiled_circuit'], + default=_numpy_type_converter)) + job['metadata'] = {'compiled_circuit': compiled_circuit} api_jobs.append(job) seed0 = qobj['circuits'][0]['config']['seed'] hpc = None @@ -334,7 +332,8 @@ def _submit(self): max_credits=qobj['config']['max_credits'], seed=seed0, hpc=hpc) - except ApiError as err: + # pylint: disable=broad-except + except Exception as err: self._status = JobStatus.ERROR self._exception = err if 'error' in submit_info: @@ -396,17 +395,19 @@ def _wait_for_job(self, timeout=60, wait=5): 'result': str(self.exception)} return Result(job_result) api_result = self._api.get_job(job_id) - job_result_return = [] + job_result_list = [] for circuit_result in api_result['qasms']: - job_result_return.append( - {'data': circuit_result['data'], - 'name': circuit_result.get('name'), - 'compiled_circuit_qasm': circuit_result.get('qasm'), - 'status': circuit_result['status']}) + this_result = {'data': circuit_result['data'], + 'name': circuit_result.get('name'), + 'compiled_circuit_qasm': circuit_result.get('qasm'), + 'status': circuit_result['status']} + if 'metadata' in circuit_result: + this_result['metadata'] = circuit_result['metadata'] + job_result_list.append(this_result) job_result = {'job_id': job_id, 'status': api_result['status'], 'used_credits': api_result.get('usedCredits'), - 'result': job_result_return} + 'result': job_result_list} # logger.info('Got a result for qobj: %s from remote backend %s with job id: %s', # qobj["id"], qobj['config']['backend_name'], # job_id) @@ -419,20 +420,21 @@ class IBMQJobError(QISKitError): pass -def reorder_bits(result, 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(qobj['circuits']): - + for circuit_result in result._result['result']: + if 'metadata' in circuit_result: + circ = circuit_result['metadata'].get('compiled_circuit') + else: + raise QISKitError('result object missing metadata for reordering bits') # device_qubit -> device_clbit (how it should have been) measure_dict = {op['qubits'][0]: op['clbits'][0] - for op in circ['compiled_circuit']['operations'] + for op in circ['operations'] if op['name'] == 'measure'} - - res = result._result['result'][idx] counts_dict_new = {} - for item in res['data']['counts'].items(): + for item in circuit_result['data']['counts'].items(): # fix clbit ordering to what it should have been bits = list(item[0]) bits.reverse() # lsb in 0th position @@ -445,13 +447,13 @@ def reorder_bits(result, qobj): reordered_bits.reverse() # only keep the clbits specified by circuit, not everything on device - num_clbits = circ['compiled_circuit']['header']['number_of_clbits'] + num_clbits = circ['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'] + cregs = circ['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 = [] @@ -471,4 +473,13 @@ def reorder_bits(result, qobj): else: counts_dict_new[compact_key] += count - res['data']['counts'] = counts_dict_new + circuit_result['data']['counts'] = counts_dict_new + + +def _numpy_type_converter(obj): + if isinstance(obj, numpy.integer): + return int(obj) + elif isinstance(obj, numpy.floating): # pylint: disable=no-member + return float(obj) + elif isinstance(obj, numpy.ndarray): + return obj.tolist() diff --git a/test/python/test_ibmqjob.py b/test/python/test_ibmqjob.py index 2f4fc27bd25f..501cbfab398d 100644 --- a/test/python/test_ibmqjob.py +++ b/test/python/test_ibmqjob.py @@ -105,7 +105,7 @@ def test_run_device(self): if job.exception: raise job.exception self.log.info(job.status) - result = job.result(qobj=qobj) + result = job.result() counts_qx = result.get_counts(result.get_names()[0]) counts_ex = {'00': shots/2, '11': shots/2} states = counts_qx.keys() | counts_ex.keys() From 641beccdca3fc4734b2080db3e8154a5a86dd9ea Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Wed, 6 Jun 2018 17:01:25 -0400 Subject: [PATCH 10/31] changed job timer. --- qiskit/backends/ibmq/ibmqjob.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/qiskit/backends/ibmq/ibmqjob.py b/qiskit/backends/ibmq/ibmqjob.py index 0418f460df32..b5324d710acb 100644 --- a/qiskit/backends/ibmq/ibmqjob.py +++ b/qiskit/backends/ibmq/ibmqjob.py @@ -362,17 +362,18 @@ def _wait_for_job(self, timeout=60, wait=5): # logger.info('Running qobj: %s on remote backend %s with job id: %s', # qobj["id"], qobj['config']['backend_name'], # job_id) - timer = 0 + start_time = time.time() api_result = self._api.get_job(job_id) while not (self.done or self.cancelled or self.exception or self._status == JobStatus.ERROR): - if timeout is not None and timer >= timeout: + elapsed_time = time.time() - start_time + if timeout is not None and elapsed_time >= timeout: job_result = {'job_id': job_id, 'status': 'ERROR', 'result': 'QISkit Time Out'} return Result(job_result) time.sleep(wait) - timer += wait - logger.info('status = %s (%d seconds)', api_result['status'], timer) + logger.info('status = %s (%d seconds)', api_result['status'], + elapsed_time) api_result = self._api.get_job(job_id) if 'status' not in api_result: @@ -483,3 +484,5 @@ def _numpy_type_converter(obj): return float(obj) elif isinstance(obj, numpy.ndarray): return obj.tolist() + else: + return obj From 8284926357f2be77e9e45eacd1c0337e62a868bf Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Wed, 6 Jun 2018 17:11:18 -0400 Subject: [PATCH 11/31] linting --- qiskit/backends/ibmq/ibmqjob.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qiskit/backends/ibmq/ibmqjob.py b/qiskit/backends/ibmq/ibmqjob.py index b5324d710acb..f7ac065ede35 100644 --- a/qiskit/backends/ibmq/ibmqjob.py +++ b/qiskit/backends/ibmq/ibmqjob.py @@ -484,5 +484,4 @@ def _numpy_type_converter(obj): return float(obj) elif isinstance(obj, numpy.ndarray): return obj.tolist() - else: - return obj + return obj From a72d9ec2f70ac34a45ab0d98413b75670b91be8a Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Thu, 7 Jun 2018 16:16:03 -0400 Subject: [PATCH 12/31] simplify import of JobStatus --- qiskit/backends/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qiskit/backends/__init__.py b/qiskit/backends/__init__.py index 71a65bcb1fa8..58a6126b55c6 100644 --- a/qiskit/backends/__init__.py +++ b/qiskit/backends/__init__.py @@ -9,3 +9,4 @@ from .basebackend import BaseBackend from .baseprovider import BaseProvider from .basejob import BaseJob +from .basejob import JobStatus From f437e0adbcf23beaabc337d13540b0c6fd8a0bb4 Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Mon, 11 Jun 2018 15:15:33 -0400 Subject: [PATCH 13/31] dissallow += results from different backends --- qiskit/_result.py | 14 ++++++++------ qiskit/backends/__init__.py | 2 +- qiskit/backends/basejob.py | 11 ----------- qiskit/backends/ibmq/ibmqbackend.py | 5 ++++- qiskit/backends/ibmq/ibmqjob.py | 2 +- qiskit/backends/local/localjob.py | 2 +- test/python/_dummybackend.py | 2 +- test/python/test_ibmqjob.py | 6 +++--- test/python/test_reordering.py | 14 +++++++------- 9 files changed, 26 insertions(+), 32 deletions(-) diff --git a/qiskit/_result.py b/qiskit/_result.py index 7457dadcd139..a204d82bd5ba 100644 --- a/qiskit/_result.py +++ b/qiskit/_result.py @@ -71,18 +71,20 @@ def __iadd__(self, other): Raises: QISKitError: if the Results cannot be combined. """ - # TODO: reevaluate if moving equality to Backend themselves (part of + # todo: reevaluate if moving equality to Backend themselves (part of # a bigger problem - backend instances will not persist between # sessions) - self._result['result'] += other._result['result'] - return self + this_backend = self._result.get('backend_name') + other_backend = other._result.get('backend_name') + if this_backend == other_backend: + self._result['result'] += other._result['result'] + return self + else: + raise QISKitError('Result objects from different backends cannot be combined.') def __add__(self, other): """Combine Result objects. - Note that the qobj id of the returned result will be the same as the - first result. - Arg: other (Result): a Result object to combine. Returns: diff --git a/qiskit/backends/__init__.py b/qiskit/backends/__init__.py index 58a6126b55c6..3bd501858a4a 100644 --- a/qiskit/backends/__init__.py +++ b/qiskit/backends/__init__.py @@ -9,4 +9,4 @@ from .basebackend import BaseBackend from .baseprovider import BaseProvider from .basejob import BaseJob -from .basejob import JobStatus +from .jobstatus import JobStatus diff --git a/qiskit/backends/basejob.py b/qiskit/backends/basejob.py index 467edac35ee6..92a5ca665afa 100644 --- a/qiskit/backends/basejob.py +++ b/qiskit/backends/basejob.py @@ -12,7 +12,6 @@ """ from abc import ABC, abstractmethod -import enum class BaseJob(ABC): @@ -58,13 +57,3 @@ def done(self): def cancelled(self): """True if call was successfully cancelled""" pass - - -class JobStatus(enum.Enum): - """Class for job status enumerated type.""" - INITIALIZING = 'job is being initialized' - QUEUED = 'job is queued' - RUNNING = 'job is actively running' - CANCELLED = 'job has been cancelled' - DONE = 'job has successfully run' - ERROR = 'job incurred error' diff --git a/qiskit/backends/ibmq/ibmqbackend.py b/qiskit/backends/ibmq/ibmqbackend.py index 2b947d0e7295..72b0ece404e9 100644 --- a/qiskit/backends/ibmq/ibmqbackend.py +++ b/qiskit/backends/ibmq/ibmqbackend.py @@ -147,7 +147,8 @@ def jobs(self, limit=50, skip=0, status=None): Args: limit (int): number of jobs to retrieve skip (int): starting index of retrieval - status (None or JobStatus): only get jobs with this status. + status (None or JobStatus or str): only get jobs with this status, + where status is e.g. JobStatus.RUNNING or 'RUNNING'. Returns: list(IBMQJob): list of IBMQJob instances """ @@ -156,6 +157,8 @@ def jobs(self, limit=50, skip=0, status=None): base_index = skip job_info_list = self._api.get_jobs(limit=limit, skip=base_index, backend=backend_name) + if isinstance(status, str): + status = JobStatus[status] while len(job_list) < limit or len(job_info_list) < limit: base_index += limit job_info_list = self._api.get_jobs(limit=limit, skip=base_index) diff --git a/qiskit/backends/ibmq/ibmqjob.py b/qiskit/backends/ibmq/ibmqjob.py index f7ac065ede35..51f32539a855 100644 --- a/qiskit/backends/ibmq/ibmqjob.py +++ b/qiskit/backends/ibmq/ibmqjob.py @@ -19,7 +19,7 @@ import numpy from qiskit.backends import BaseJob -from qiskit.backends.basejob import JobStatus +from qiskit.backends.jobstatus import JobStatus from qiskit._qiskiterror import QISKitError from qiskit._result import Result from qiskit._resulterror import ResultError diff --git a/qiskit/backends/local/localjob.py b/qiskit/backends/local/localjob.py index 381b701cc95c..80a21f94dd3b 100644 --- a/qiskit/backends/local/localjob.py +++ b/qiskit/backends/local/localjob.py @@ -12,7 +12,7 @@ import sys from qiskit.backends import BaseJob -from qiskit.backends.basejob import JobStatus +from qiskit.backends import JobStatus from qiskit import QISKitError logger = logging.getLogger(__name__) diff --git a/test/python/_dummybackend.py b/test/python/_dummybackend.py index c1e69eac9df4..aeb657c6f441 100644 --- a/test/python/_dummybackend.py +++ b/test/python/_dummybackend.py @@ -19,7 +19,7 @@ from qiskit import Result from qiskit.backends import BaseBackend from qiskit.backends import BaseJob -from qiskit.backends.basejob import JobStatus +from qiskit.backends import JobStatus from qiskit.backends.baseprovider import BaseProvider logger = logging.getLogger(__name__) diff --git a/test/python/test_ibmqjob.py b/test/python/test_ibmqjob.py index 501cbfab398d..e1f0eb68cf4d 100644 --- a/test/python/test_ibmqjob.py +++ b/test/python/test_ibmqjob.py @@ -21,7 +21,7 @@ from qiskit.backends.ibmq import IBMQProvider from qiskit.backends.ibmq.ibmqjob import IBMQJob, IBMQJobError from qiskit.backends.ibmq.ibmqbackend import IBMQBackendError -from qiskit.backends.basejob import JobStatus +from qiskit.backends import JobStatus from .common import requires_qe_access, QiskitTestCase, slow_test @@ -156,7 +156,7 @@ def test_run_async_simulator(self): '{0} s'.format(timeout)) time.sleep(0.2) - result_array = [job.result(qobj=qobj) for job in job_array] + result_array = [job.result() for job in job_array] self.log.info('got back all job results') # Ensure all jobs have finished. self.assertTrue(all([job.done for job in job_array])) @@ -202,7 +202,7 @@ def test_run_async_device(self): self.assertTrue(num_jobs - num_error - num_done > 0) # Wait for all the results. - result_array = [job.result(qobj=qobj) for job in job_array] + result_array = [job.result() for job in job_array] # Ensure all jobs have finished. self.assertTrue(all([job.done for job in job_array])) diff --git a/test/python/test_reordering.py b/test/python/test_reordering.py index 0f7696045aac..3e02833a3611 100644 --- a/test/python/test_reordering.py +++ b/test/python/test_reordering.py @@ -13,7 +13,7 @@ import qiskit from qiskit import QuantumJob from qiskit.wrapper import register, available_backends, get_backend -from qiskit.wrapper import compile as qcompile +from qiskit.wrapper import compile from .common import requires_qe_access, QiskitTestCase, slow_test @@ -50,13 +50,13 @@ def test_basic_reordering(self, QE_TOKEN, QE_URL, hub=None, group=None, project= circ.measure(q[1], c[0]) shots = 2000 - qobj_real = qcompile(circ, real, shots=shots) - qobj_sim = qcompile(circ, sim, shots=shots) + qobj_real = compile(circ, real, shots=shots) + qobj_sim = compile(circ, sim, shots=shots) q_job_real = QuantumJob(qobj_real, backend=real, preformatted=True, shots=shots) q_job_sim = QuantumJob(qobj_sim, backend=sim, preformatted=True, shots=shots) - result_real = real.run(q_job_real).result(timeout=600, qobj=qobj_real) + result_real = real.run(q_job_real).result(timeout=600) result_sim = sim.run(q_job_sim).result(timeout=600) counts_real = result_real.get_counts() counts_sim = result_sim.get_counts() @@ -97,13 +97,13 @@ def test_multi_register_reordering(self, QE_TOKEN, QE_URL, circ.measure(q2[0], c1[1]) shots = 4000 - qobj_real = qcompile(circ, real, shots=shots) - qobj_sim = qcompile(circ, sim, shots=shots) + qobj_real = compile(circ, real, shots=shots) + qobj_sim = compile(circ, sim, shots=shots) q_job_real = QuantumJob(qobj_real, backend=real, preformatted=True, shots=shots) q_job_sim = QuantumJob(qobj_sim, backend=sim, preformatted=True, shots=shots) - result_real = real.run(q_job_real).result(timeout=600, qobj=qobj_real) + result_real = real.run(q_job_real).result(timeout=600) result_sim = sim.run(q_job_sim).result(timeout=600) counts_real = result_real.get_counts() counts_sim = result_sim.get_counts() From 3d5904e1f6b6eb740d86f43f735062f4cb590a9e Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Mon, 11 Jun 2018 16:31:04 -0400 Subject: [PATCH 14/31] moved JobStatus to it's own file. --- qiskit/backends/jobstatus.py | 20 ++++++++++++++++++++ test/python/test_reordering.py | 5 ----- 2 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 qiskit/backends/jobstatus.py diff --git a/qiskit/backends/jobstatus.py b/qiskit/backends/jobstatus.py new file mode 100644 index 000000000000..02acaa22c510 --- /dev/null +++ b/qiskit/backends/jobstatus.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- + +# Copyright 2017, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +"""This module defines an enumerated type for the state of backend jobs""" + +import enum + + +class JobStatus(enum.Enum): + """Class for job status enumerated type.""" + INITIALIZING = 'job is being initialized' + QUEUED = 'job is queued' + RUNNING = 'job is actively running' + CANCELLED = 'job has been cancelled' + DONE = 'job has successfully run' + ERROR = 'job incurred error' diff --git a/test/python/test_reordering.py b/test/python/test_reordering.py index 3e02833a3611..02993e05bd05 100644 --- a/test/python/test_reordering.py +++ b/test/python/test_reordering.py @@ -13,7 +13,6 @@ import qiskit from qiskit import QuantumJob from qiskit.wrapper import register, available_backends, get_backend -from qiskit.wrapper import compile from .common import requires_qe_access, QiskitTestCase, slow_test @@ -50,8 +49,6 @@ def test_basic_reordering(self, QE_TOKEN, QE_URL, hub=None, group=None, project= circ.measure(q[1], c[0]) shots = 2000 - qobj_real = compile(circ, real, shots=shots) - qobj_sim = compile(circ, sim, shots=shots) q_job_real = QuantumJob(qobj_real, backend=real, preformatted=True, shots=shots) q_job_sim = QuantumJob(qobj_sim, backend=sim, preformatted=True, @@ -97,8 +94,6 @@ def test_multi_register_reordering(self, QE_TOKEN, QE_URL, circ.measure(q2[0], c1[1]) shots = 4000 - qobj_real = compile(circ, real, shots=shots) - qobj_sim = compile(circ, sim, shots=shots) q_job_real = QuantumJob(qobj_real, backend=real, preformatted=True, shots=shots) q_job_sim = QuantumJob(qobj_sim, backend=sim, preformatted=True, From c9fb95f20e6395919a206e9fcbe7b6ee33e87e26 Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Mon, 11 Jun 2018 16:49:41 -0400 Subject: [PATCH 15/31] bug fix --- qiskit/backends/ibmq/ibmqbackend.py | 1 + test/python/test_reordering.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/qiskit/backends/ibmq/ibmqbackend.py b/qiskit/backends/ibmq/ibmqbackend.py index 72b0ece404e9..cb70abd9f7a7 100644 --- a/qiskit/backends/ibmq/ibmqbackend.py +++ b/qiskit/backends/ibmq/ibmqbackend.py @@ -14,6 +14,7 @@ from qiskit import QISKitError from qiskit._util import _camel_case_to_snake_case from qiskit.backends import BaseBackend +from qiskit.backends import JobStatus from qiskit.backends.ibmq.ibmqjob import IBMQJob logger = logging.getLogger(__name__) diff --git a/test/python/test_reordering.py b/test/python/test_reordering.py index 02993e05bd05..3e02833a3611 100644 --- a/test/python/test_reordering.py +++ b/test/python/test_reordering.py @@ -13,6 +13,7 @@ import qiskit from qiskit import QuantumJob from qiskit.wrapper import register, available_backends, get_backend +from qiskit.wrapper import compile from .common import requires_qe_access, QiskitTestCase, slow_test @@ -49,6 +50,8 @@ def test_basic_reordering(self, QE_TOKEN, QE_URL, hub=None, group=None, project= circ.measure(q[1], c[0]) shots = 2000 + qobj_real = compile(circ, real, shots=shots) + qobj_sim = compile(circ, sim, shots=shots) q_job_real = QuantumJob(qobj_real, backend=real, preformatted=True, shots=shots) q_job_sim = QuantumJob(qobj_sim, backend=sim, preformatted=True, @@ -94,6 +97,8 @@ def test_multi_register_reordering(self, QE_TOKEN, QE_URL, circ.measure(q2[0], c1[1]) shots = 4000 + qobj_real = compile(circ, real, shots=shots) + qobj_sim = compile(circ, sim, shots=shots) q_job_real = QuantumJob(qobj_real, backend=real, preformatted=True, shots=shots) q_job_sim = QuantumJob(qobj_sim, backend=sim, preformatted=True, From 97abf8277e31cbfcae0eef84c8fd03912622c1f3 Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Mon, 11 Jun 2018 17:01:28 -0400 Subject: [PATCH 16/31] linting --- test/python/test_reordering.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/python/test_reordering.py b/test/python/test_reordering.py index 3e02833a3611..5b2ca743eb07 100644 --- a/test/python/test_reordering.py +++ b/test/python/test_reordering.py @@ -13,7 +13,7 @@ import qiskit from qiskit import QuantumJob from qiskit.wrapper import register, available_backends, get_backend -from qiskit.wrapper import compile +import qiskit._compiler from .common import requires_qe_access, QiskitTestCase, slow_test @@ -50,8 +50,8 @@ def test_basic_reordering(self, QE_TOKEN, QE_URL, hub=None, group=None, project= circ.measure(q[1], c[0]) shots = 2000 - qobj_real = compile(circ, real, shots=shots) - qobj_sim = compile(circ, sim, shots=shots) + qobj_real = qiskit._compiler.compile(circ, real, shots=shots) + qobj_sim = qiskit._compiler.compile(circ, sim, shots=shots) q_job_real = QuantumJob(qobj_real, backend=real, preformatted=True, shots=shots) q_job_sim = QuantumJob(qobj_sim, backend=sim, preformatted=True, @@ -97,8 +97,8 @@ def test_multi_register_reordering(self, QE_TOKEN, QE_URL, circ.measure(q2[0], c1[1]) shots = 4000 - qobj_real = compile(circ, real, shots=shots) - qobj_sim = compile(circ, sim, shots=shots) + qobj_real = qiskit._compiler.compile(circ, real, shots=shots) + qobj_sim = qiskit._compiler.compile(circ, sim, shots=shots) q_job_real = QuantumJob(qobj_real, backend=real, preformatted=True, shots=shots) q_job_sim = QuantumJob(qobj_sim, backend=sim, preformatted=True, From 0afb90de48ffcc15d64e98bfe4dd96fc5d6b1edc Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Wed, 13 Jun 2018 00:15:10 -0400 Subject: [PATCH 17/31] fix bug in backend.jobs This bug would cause the method to miss first "limit" number of jobs. --- qiskit/backends/ibmq/ibmqbackend.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qiskit/backends/ibmq/ibmqbackend.py b/qiskit/backends/ibmq/ibmqbackend.py index cb70abd9f7a7..272a7a0575c1 100644 --- a/qiskit/backends/ibmq/ibmqbackend.py +++ b/qiskit/backends/ibmq/ibmqbackend.py @@ -162,7 +162,6 @@ def jobs(self, limit=50, skip=0, status=None): status = JobStatus[status] while len(job_list) < limit or len(job_info_list) < limit: base_index += limit - job_info_list = self._api.get_jobs(limit=limit, skip=base_index) for job_info in job_info_list: is_device = not bool(self._configuration.get('simulator')) job = IBMQJob.from_api(job_info, self._api, is_device) @@ -171,6 +170,8 @@ def jobs(self, limit=50, skip=0, status=None): job_list.append(job) elif job.status.get('status') == status: job_list.append(job) + job_info_list = self._api.get_jobs(limit=limit, skip=base_index, + backend=backend_name) return job_list def retrieve_job(self, job_id): From 7516e7a89d5f056ad6cd02aa98e52a6622da9f0c Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Wed, 13 Jun 2018 01:17:45 -0400 Subject: [PATCH 18/31] allow retrieving old jobs without metadata Previously retrieving jobs without metadata would raise an exception. Now a warning is issued. Also, the property "creationDate" was added to ibmq jobs. --- qiskit/backends/ibmq/ibmqjob.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/qiskit/backends/ibmq/ibmqjob.py b/qiskit/backends/ibmq/ibmqjob.py index 51f32539a855..fc580966618b 100644 --- a/qiskit/backends/ibmq/ibmqjob.py +++ b/qiskit/backends/ibmq/ibmqjob.py @@ -78,6 +78,7 @@ def from_api(cls, job_info, api, is_device): 'shots': 1024, 'status': 'status string', 'usedCredits': 3, + 'creationDate': '2018-06-13T04:31:13.175Z' 'userId': 'user id'} api (IBMQuantumExperience): IBM Q API is_device (bool): whether backend is a real device # TODO: remove this after Qobj @@ -98,6 +99,7 @@ def from_api(cls, job_info, api, is_device): job_instance._cancelled = False job_instance._is_device = is_device job_instance._from_api = True + job_instance.creationDate = job_info.get('creationDate') return job_instance def result(self, timeout=None, wait=5): @@ -340,6 +342,7 @@ def _submit(self): self._status = JobStatus.ERROR self._exception = IBMQJobError(str(submit_info['error'])) self._job_id = submit_info.get('id') + self.creationDate = submit_info.get('creationDate') self._status = JobStatus.QUEUED return submit_info @@ -429,7 +432,9 @@ def _reorder_bits(result): if 'metadata' in circuit_result: circ = circuit_result['metadata'].get('compiled_circuit') else: - raise QISKitError('result object missing metadata for reordering bits') + logger.warning('result object missing metadata for reordering' + ' bits: bits may be out of order') + return # device_qubit -> device_clbit (how it should have been) measure_dict = {op['qubits'][0]: op['clbits'][0] for op in circ['operations'] From 674af67a1fad52f41d99dea301ceb6752e977c2b Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Wed, 13 Jun 2018 01:44:03 -0400 Subject: [PATCH 19/31] minor improvement to backend.jobs --- qiskit/backends/ibmq/ibmqbackend.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/qiskit/backends/ibmq/ibmqbackend.py b/qiskit/backends/ibmq/ibmqbackend.py index 272a7a0575c1..c3a0c8225273 100644 --- a/qiskit/backends/ibmq/ibmqbackend.py +++ b/qiskit/backends/ibmq/ibmqbackend.py @@ -156,12 +156,12 @@ def jobs(self, limit=50, skip=0, status=None): backend_name = self.configuration['name'] job_list = [] base_index = skip - job_info_list = self._api.get_jobs(limit=limit, skip=base_index, - backend=backend_name) + job_info_list = [] if isinstance(status, str): status = JobStatus[status] while len(job_list) < limit or len(job_info_list) < limit: - base_index += limit + job_info_list = self._api.get_jobs(limit=limit, skip=base_index, + backend=backend_name) for job_info in job_info_list: is_device = not bool(self._configuration.get('simulator')) job = IBMQJob.from_api(job_info, self._api, is_device) @@ -170,8 +170,7 @@ def jobs(self, limit=50, skip=0, status=None): job_list.append(job) elif job.status.get('status') == status: job_list.append(job) - job_info_list = self._api.get_jobs(limit=limit, skip=base_index, - backend=backend_name) + base_index += limit return job_list def retrieve_job(self, job_id): From 2b79d6bb513ec751f89920c42ff68d7c7ce1ee20 Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Wed, 13 Jun 2018 01:46:59 -0400 Subject: [PATCH 20/31] linting --- qiskit/backends/ibmq/ibmqjob.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qiskit/backends/ibmq/ibmqjob.py b/qiskit/backends/ibmq/ibmqjob.py index fc580966618b..d57117e76f09 100644 --- a/qiskit/backends/ibmq/ibmqjob.py +++ b/qiskit/backends/ibmq/ibmqjob.py @@ -57,6 +57,7 @@ def __init__(self, q_job, api, is_device): self._exception = None self._is_device = is_device self._from_api = False + self.creation_date = None @classmethod def from_api(cls, job_info, api, is_device): @@ -99,7 +100,7 @@ def from_api(cls, job_info, api, is_device): job_instance._cancelled = False job_instance._is_device = is_device job_instance._from_api = True - job_instance.creationDate = job_info.get('creationDate') + job_instance.creation_date = job_info.get('creationDate') return job_instance def result(self, timeout=None, wait=5): @@ -342,7 +343,7 @@ def _submit(self): self._status = JobStatus.ERROR self._exception = IBMQJobError(str(submit_info['error'])) self._job_id = submit_info.get('id') - self.creationDate = submit_info.get('creationDate') + self.creation_date = submit_info.get('creationDate') self._status = JobStatus.QUEUED return submit_info From 0cfd4cb52ca7e2b8d1ae0bf749a4e161aead049b Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Thu, 14 Jun 2018 18:39:35 -0400 Subject: [PATCH 21/31] speed up backend.jobs(). job_id->id Previously backend.jobs() more api calls than it needed. Also, ibmqjob.job_id is now ibmqjob.id --- qiskit/backends/ibmq/ibmqjob.py | 57 +++++++++++++++++---------------- test/python/test_ibmqjob.py | 19 ++++++----- 2 files changed, 41 insertions(+), 35 deletions(-) diff --git a/qiskit/backends/ibmq/ibmqjob.py b/qiskit/backends/ibmq/ibmqjob.py index d57117e76f09..efaa39a0ffd9 100644 --- a/qiskit/backends/ibmq/ibmqjob.py +++ b/qiskit/backends/ibmq/ibmqjob.py @@ -17,6 +17,7 @@ import pprint import json import numpy +import datetime from qiskit.backends import BaseJob from qiskit.backends.jobstatus import JobStatus @@ -48,7 +49,7 @@ def __init__(self, q_job, api, is_device): self._q_job = q_job self._qobj = q_job.qobj self._api = api - self._job_id = None # this must be before creating the future + self._id = None # this must be before creating the future self._backend_name = self._qobj.get('config').get('backend_name') self._status = JobStatus.INITIALIZING self._future_submit = self._executor.submit(self._submit) @@ -57,7 +58,8 @@ def __init__(self, q_job, api, is_device): self._exception = None self._is_device = is_device self._from_api = False - self.creation_date = None + self.creation_date = datetime.datetime.utcnow().replace( + tzinfo=datetime.timezone.utc).isoformat() @classmethod def from_api(cls, job_info, api, is_device): @@ -91,11 +93,11 @@ def from_api(cls, job_info, api, is_device): job_instance._status = JobStatus.QUEUED job_instance._backend_name = job_info.get('backend').get('name') job_instance._api = api - job_instance._job_id = job_info.get('id') + job_instance._id = job_info.get('id') job_instance._exception = None # needs to be before status call below - # update status (need _api and _job_id) + # update status (need _api and _id) # pylint: disable=pointless-statement - job_instance.status + #job_instance.status(job_info=job_info) job_instance._status_msg = None job_instance._cancelled = False job_instance._is_device = is_device @@ -144,7 +146,7 @@ def cancel(self): hub = self._api.config['hub'] group = self._api.config['group'] project = self._api.config['project'] - response = self._api.cancel_job(self._job_id, hub, group, project) + response = self._api.cancel_job(self._id, hub, group, project) if 'error' in response: err_msg = response.get('error', '') self._exception = QISKitError('Error cancelling job: %s' % err_msg) @@ -159,12 +161,12 @@ def cancel(self): @property def status(self): if self._status == JobStatus.INITIALIZING: - stats = {'job_id': None, + stats = {'id': None, 'status': self._status, - 'status_msg': 'job is begin initialized please wait a moment'} + 'status_msg': 'job is being initialized please wait a moment'} return stats - job_result = self._api.get_job(self._job_id) - stats = {'job_id': self._job_id} + job_result = self._api.get_job(self._id) + stats = {'id': self._id} self._status = None _status_msg = None if 'status' not in job_result: @@ -262,14 +264,14 @@ def _is_commercial(self): return config.get('hub') and config.get('group') and config.get('project') @property - def job_id(self): + def id(self): """ - Return backend determined job_id (also available in status method). + Return backend determined id (also available in status method). """ - while not self._job_id: - # job is initializing and hasn't gotten a job_id yet. + while not self._id: + # job is initializing and hasn't gotten a id yet. time.sleep(0.1) - return self._job_id + return self._id @property def backend_name(self): @@ -342,7 +344,7 @@ def _submit(self): if 'error' in submit_info: self._status = JobStatus.ERROR self._exception = IBMQJobError(str(submit_info['error'])) - self._job_id = submit_info.get('id') + self._id = submit_info.get('id') self.creation_date = submit_info.get('creationDate') self._status = JobStatus.QUEUED return submit_info @@ -362,23 +364,23 @@ def _wait_for_job(self, timeout=60, wait=5): QISKitError: job didn't return status or reported error in status """ # qobj = self._q_job.qobj - job_id = self.job_id + id = self.id # logger.info('Running qobj: %s on remote backend %s with job id: %s', # qobj["id"], qobj['config']['backend_name'], - # job_id) + # id) + api_result = None start_time = time.time() - api_result = self._api.get_job(job_id) while not (self.done or self.cancelled or self.exception or self._status == JobStatus.ERROR): elapsed_time = time.time() - start_time if timeout is not None and elapsed_time >= timeout: - job_result = {'job_id': job_id, 'status': 'ERROR', + job_result = {'id': id, 'status': 'ERROR', 'result': 'QISkit Time Out'} return Result(job_result) + api_result = self._api.get_job(id) time.sleep(wait) logger.info('status = %s (%d seconds)', api_result['status'], elapsed_time) - api_result = self._api.get_job(job_id) if 'status' not in api_result: self._exception = QISKitError("get_job didn't return status: %s" % @@ -387,19 +389,20 @@ def _wait_for_job(self, timeout=60, wait=5): (pprint.pformat(api_result))) if (api_result['status'] == 'ERROR_CREATING_JOB' or api_result['status'] == 'ERROR_RUNNING_JOB'): - job_result = {'job_id': job_id, 'status': 'ERROR', + job_result = {'id': id, 'status': 'ERROR', 'result': api_result['status']} return Result(job_result) if self.cancelled: - job_result = {'job_id': job_id, 'status': 'CANCELLED', + job_result = {'id': id, 'status': 'CANCELLED', 'result': 'job cancelled'} return Result(job_result) elif self.exception: - job_result = {'job_id': job_id, 'status': 'ERROR', + job_result = {'id': id, 'status': 'ERROR', 'result': str(self.exception)} return Result(job_result) - api_result = self._api.get_job(job_id) + if api_result is None: + api_result = self._api.get_job(id) job_result_list = [] for circuit_result in api_result['qasms']: this_result = {'data': circuit_result['data'], @@ -409,13 +412,13 @@ def _wait_for_job(self, timeout=60, wait=5): if 'metadata' in circuit_result: this_result['metadata'] = circuit_result['metadata'] job_result_list.append(this_result) - job_result = {'job_id': job_id, + job_result = {'id': id, 'status': api_result['status'], 'used_credits': api_result.get('usedCredits'), 'result': job_result_list} # logger.info('Got a result for qobj: %s from remote backend %s with job id: %s', # qobj["id"], qobj['config']['backend_name'], - # job_id) + # id) job_result['backend_name'] = self.backend_name return Result(job_result) diff --git a/test/python/test_ibmqjob.py b/test/python/test_ibmqjob.py index e1f0eb68cf4d..c8da16aea102 100644 --- a/test/python/test_ibmqjob.py +++ b/test/python/test_ibmqjob.py @@ -149,7 +149,7 @@ def test_run_async_simulator(self): break for job in job_array: self.log.info('%s %s %s %s', job.status['status'], job.running, - check, job.job_id) + check, job.id) self.log.info('-'*20 + ' ' + str(time.time()-start_time)) if time.time() - start_time > timeout: raise TimeoutError('failed to see multiple running jobs after ' @@ -163,7 +163,7 @@ def test_run_async_simulator(self): self.assertTrue(all([result.get_status() == 'COMPLETED' for result in result_array])) # Ensure job ids are unique. - job_ids = [job.job_id for job in job_array] + job_ids = [job.id for job in job_array] self.assertEqual(sorted(job_ids), sorted(list(set(job_ids)))) @slow_test @@ -209,7 +209,7 @@ def test_run_async_device(self): self.assertTrue(all([result.get_status() == 'COMPLETED' for result in result_array])) # Ensure job ids are unique. - job_ids = [job.job_id for job in job_array] + job_ids = [job.id for job in job_array] self.assertEqual(sorted(job_ids), sorted(list(set(job_ids)))) def test_cancel(self): @@ -254,8 +254,8 @@ def test_job_id(self): qobj = qiskit._compiler.compile(self._qc, backend) quantum_job = QuantumJob(qobj, backend, preformatted=True) job = backend.run(quantum_job) - self.log.info('job_id: %s', job.job_id) - self.assertTrue(job.job_id is not None) + self.log.info('job_id: %s', job.id) + self.assertTrue(job.id is not None) def test_get_backend_name(self): backend_name = 'ibmq_qasm_simulator' @@ -268,19 +268,22 @@ def test_get_backend_name(self): def test_get_jobs_from_backend(self): backends = self._provider.available_backends() backend = lowest_pending_jobs(backends) + start_time = time.time() job_list = backend.jobs(limit=5, skip=0) + self.log.info('time to get jobs: %0.3f s', time.time() - start_time) self.log.info('found %s jobs on backend %s', len(job_list), backend.name) for job in job_list: self.log.info('status: %s', job.status) - self.assertTrue(isinstance(job.job_id, str)) + self.assertTrue(isinstance(job.id, str)) + self.log.info('time to get job statuses: %0.3f s', time.time() - start_time) def test_retrieve_job(self): backend = self._provider.get_backend('ibmq_qasm_simulator') qobj = qiskit._compiler.compile(self._qc, backend) quantum_job = QuantumJob(qobj, backend, preformatted=True) job = backend.run(quantum_job) - rjob = backend.retrieve_job(job.job_id) - self.assertTrue(job.job_id == rjob.job_id) + rjob = backend.retrieve_job(job.id) + self.assertTrue(job.id == rjob.id) self.assertTrue(job.result().get_counts() == rjob.result().get_counts()) def test_retrieve_job_error(self): From 00cd5968fc768edf538b25bfab25b07098475bd9 Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Thu, 14 Jun 2018 18:46:32 -0400 Subject: [PATCH 22/31] linting --- qiskit/backends/ibmq/ibmqjob.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/qiskit/backends/ibmq/ibmqjob.py b/qiskit/backends/ibmq/ibmqjob.py index efaa39a0ffd9..c5a6b3e833ad 100644 --- a/qiskit/backends/ibmq/ibmqjob.py +++ b/qiskit/backends/ibmq/ibmqjob.py @@ -95,9 +95,6 @@ def from_api(cls, job_info, api, is_device): job_instance._api = api job_instance._id = job_info.get('id') job_instance._exception = None # needs to be before status call below - # update status (need _api and _id) - # pylint: disable=pointless-statement - #job_instance.status(job_info=job_info) job_instance._status_msg = None job_instance._cancelled = False job_instance._is_device = is_device From d7d1a4f070f1c7678ce875660f2973d0244ff63f Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Fri, 15 Jun 2018 14:07:48 -0400 Subject: [PATCH 23/31] incorporating some suggestions from @atilag --- qiskit/backends/ibmq/ibmqjob.py | 49 +++++++++++++++++---------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/qiskit/backends/ibmq/ibmqjob.py b/qiskit/backends/ibmq/ibmqjob.py index c5a6b3e833ad..fa205d93dd68 100644 --- a/qiskit/backends/ibmq/ibmqjob.py +++ b/qiskit/backends/ibmq/ibmqjob.py @@ -57,7 +57,6 @@ def __init__(self, q_job, api, is_device): self._cancelled = False self._exception = None self._is_device = is_device - self._from_api = False self.creation_date = datetime.datetime.utcnow().replace( tzinfo=datetime.timezone.utc).isoformat() @@ -98,7 +97,6 @@ def from_api(cls, job_info, api, is_device): job_instance._status_msg = None job_instance._cancelled = False job_instance._is_device = is_device - job_instance._from_api = True job_instance.creation_date = job_info.get('creationDate') return job_instance @@ -162,46 +160,56 @@ def status(self): 'status': self._status, 'status_msg': 'job is being initialized please wait a moment'} return stats - job_result = self._api.get_job(self._id) + api_job = self._api.get_job(self._id) stats = {'id': self._id} self._status = None _status_msg = None - if 'status' not in job_result: + if 'status' not in api_job: self._exception = QISKitError("get_job didn't return status: %s" % - (pprint.pformat(job_result))) + (pprint.pformat(api_job))) raise QISKitError("get_job didn't return status: %s" % - (pprint.pformat(job_result))) - elif job_result['status'] == 'RUNNING': + (pprint.pformat(api_job))) + elif api_job['status'] == 'RUNNING': self._status = JobStatus.RUNNING # we may have some other information here - if 'infoQueue' in job_result: - if 'status' in job_result['infoQueue']: - if job_result['infoQueue']['status'] == 'PENDING_IN_QUEUE': + if 'infoQueue' in api_job: + if 'status' in api_job['infoQueue']: + if api_job['infoQueue']['status'] == 'PENDING_IN_QUEUE': self._status = JobStatus.QUEUED - if 'position' in job_result['infoQueue']: - stats['queue_position'] = job_result['infoQueue']['position'] - elif job_result['status'] == 'COMPLETED': + if 'position' in api_job['infoQueue']: + stats['queue_position'] = api_job['infoQueue']['position'] + elif api_job['status'] == 'COMPLETED': self._status = JobStatus.DONE - elif job_result['status'] == 'CANCELLED': + elif api_job['status'] == 'CANCELLED': self._status = JobStatus.CANCELLED elif self.exception or self._future_submit.exception(): self._status = JobStatus.ERROR if self._future_submit.exception(): self._exception = self._future_submit.exception() self._status_msg = str(self.exception) - elif 'ERROR' in job_result['status']: + elif 'ERROR' in api_job['status']: # ERROR_CREATING_JOB or ERROR_RUNNING_JOB self._status = JobStatus.ERROR - self._status_msg = job_result['status'] + self._status_msg = api_job['status'] else: self._status = JobStatus.ERROR raise IBMQJobError('Unexpected behavior of {0}\n{1}'.format( self.__class__.__name__, - pprint.pformat(job_result))) + pprint.pformat(api_job))) stats['status'] = self._status stats['status_msg'] = _status_msg return stats + def _is_job_queued(self, api_job): + is_queued, position = False, None + if 'infoQueue' in api_job: + if 'status' in api_job['infoQueue']: + queue_status = api_job['infoQueue']['status'] + is_queued = queue_status == 'PENDING_IN_QUEUE' + if 'position' in api_job['infoQueue']: + position = api_job['infoQueue']['position'] + return is_queued, position + @property def queued(self): """ @@ -360,11 +368,7 @@ def _wait_for_job(self, timeout=60, wait=5): Raises: QISKitError: job didn't return status or reported error in status """ - # qobj = self._q_job.qobj id = self.id - # logger.info('Running qobj: %s on remote backend %s with job id: %s', - # qobj["id"], qobj['config']['backend_name'], - # id) api_result = None start_time = time.time() while not (self.done or self.cancelled or self.exception or @@ -413,9 +417,6 @@ def _wait_for_job(self, timeout=60, wait=5): 'status': api_result['status'], 'used_credits': api_result.get('usedCredits'), 'result': job_result_list} - # logger.info('Got a result for qobj: %s from remote backend %s with job id: %s', - # qobj["id"], qobj['config']['backend_name'], - # id) job_result['backend_name'] = self.backend_name return Result(job_result) From 10d0bc2c46be730c6dc1bafcc42e8a51e2de0db9 Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Fri, 15 Jun 2018 15:32:51 -0400 Subject: [PATCH 24/31] improvements to ibmqjob.status This commit incorporates improvements from PR #515 by @delapuente to handling job status such that it makes fewer api calls. Co-authored-by: salva@unoyunodiez.com --- qiskit/backends/ibmq/ibmqjob.py | 124 ++++++++++++++++++++------------ 1 file changed, 77 insertions(+), 47 deletions(-) diff --git a/qiskit/backends/ibmq/ibmqjob.py b/qiskit/backends/ibmq/ibmqjob.py index fa205d93dd68..97807e0f0f03 100644 --- a/qiskit/backends/ibmq/ibmqjob.py +++ b/qiskit/backends/ibmq/ibmqjob.py @@ -34,8 +34,14 @@ class IBMQJob(BaseJob): Attributes: _executor (futures.Executor): executor to handle asynchronous jobs + _final_states (list(JobStatus)): terminal states of async jobs """ _executor = futures.ThreadPoolExecutor() + _final_states = [ + JobStatus.DONE, + JobStatus.CANCELLED, + JobStatus.ERROR + ] def __init__(self, q_job, api, is_device): """IBMQJob init function. @@ -53,7 +59,8 @@ def __init__(self, q_job, api, is_device): self._backend_name = self._qobj.get('config').get('backend_name') self._status = JobStatus.INITIALIZING self._future_submit = self._executor.submit(self._submit) - self._status_msg = None + self._status_msg = 'Job is initializing. Please, wait a moment.' + self._queue_position = None self._cancelled = False self._exception = None self._is_device = is_device @@ -95,6 +102,7 @@ def from_api(cls, job_info, api, is_device): job_instance._id = job_info.get('id') job_instance._exception = None # needs to be before status call below job_instance._status_msg = None + job_instance._queue_position = None job_instance._cancelled = False job_instance._is_device = is_device job_instance.creation_date = job_info.get('creationDate') @@ -155,50 +163,69 @@ def cancel(self): @property def status(self): - if self._status == JobStatus.INITIALIZING: - stats = {'id': None, - 'status': self._status, - 'status_msg': 'job is being initialized please wait a moment'} - return stats - api_job = self._api.get_job(self._id) - stats = {'id': self._id} - self._status = None - _status_msg = None - if 'status' not in api_job: - self._exception = QISKitError("get_job didn't return status: %s" % - (pprint.pformat(api_job))) - raise QISKitError("get_job didn't return status: %s" % - (pprint.pformat(api_job))) - elif api_job['status'] == 'RUNNING': + self._update_status() + stats = { + 'job_id': self._id, + 'status': self._status, + 'status_msg': self._status_msg + } + if self._queue_position: + stats['queue_position'] = self._queue_position + # Reset once consumed to allow _update_status to regenerate the + # value if needed. + self._queue_position = None + return stats + + def _update_status(self): + """Query the API to update the status.""" + if (self._status in self._final_states or + self._status == JobStatus.INITIALIZING): + return + + try: + api_job = self._api.get_job(self.id) + if 'status' not in api_job: + raise QISKitError('get_job didn\'t return status: %s' % + pprint.pformat(api_job)) + # pylint: disable=broad-except + except Exception as err: + self._status = JobStatus.ERROR + self._exception = err + self._status_msg = '{}'.format(err) + return + + if 'RUNNING' == api_job['status']: self._status = JobStatus.RUNNING - # we may have some other information here - if 'infoQueue' in api_job: - if 'status' in api_job['infoQueue']: - if api_job['infoQueue']['status'] == 'PENDING_IN_QUEUE': - self._status = JobStatus.QUEUED - if 'position' in api_job['infoQueue']: - stats['queue_position'] = api_job['infoQueue']['position'] - elif api_job['status'] == 'COMPLETED': + queued, queue_position = self._is_job_queued(api_job) + if queued: + self._status = JobStatus.QUEUED + if queue_position: + self._queue_position = queue_position + + elif 'COMPLETED' == api_job['status']: self._status = JobStatus.DONE - elif api_job['status'] == 'CANCELLED': + + elif 'CANCELLED' == api_job['status']: self._status = JobStatus.CANCELLED + + elif 'ERROR' in api_job['status']: + # ERROR_CREATING_JOB or ERROR_RUNNING_JOB + self._status = JobStatus.ERROR + self._status_msg = api_job['status'] + elif self.exception or self._future_submit.exception(): self._status = JobStatus.ERROR if self._future_submit.exception(): self._exception = self._future_submit.exception() self._status_msg = str(self.exception) - elif 'ERROR' in api_job['status']: - # ERROR_CREATING_JOB or ERROR_RUNNING_JOB - self._status = JobStatus.ERROR - self._status_msg = api_job['status'] + else: self._status = JobStatus.ERROR - raise IBMQJobError('Unexpected behavior of {0}\n{1}'.format( - self.__class__.__name__, - pprint.pformat(api_job))) - stats['status'] = self._status - stats['status_msg'] = _status_msg - return stats + self._exception = IBMQJobError( + 'Unrecognized result: \n{}'.format(pprint.pformat(api_job))) + self._status_msg = '{}'.format(self._exception) + + return api_job def _is_job_queued(self, api_job): is_queued, position = False, None @@ -368,18 +395,14 @@ def _wait_for_job(self, timeout=60, wait=5): Raises: QISKitError: job didn't return status or reported error in status """ - id = self.id - api_result = None start_time = time.time() - while not (self.done or self.cancelled or self.exception or - self._status == JobStatus.ERROR): + api_result = self._update_status() + while self._status not in self._final_states: elapsed_time = time.time() - start_time if timeout is not None and elapsed_time >= timeout: - job_result = {'id': id, 'status': 'ERROR', + job_result = {'id': self.id, 'status': 'ERROR', 'result': 'QISkit Time Out'} return Result(job_result) - api_result = self._api.get_job(id) - time.sleep(wait) logger.info('status = %s (%d seconds)', api_result['status'], elapsed_time) @@ -388,22 +411,29 @@ def _wait_for_job(self, timeout=60, wait=5): (pprint.pformat(api_result))) raise QISKitError("get_job didn't return status: %s" % (pprint.pformat(api_result))) + if (api_result['status'] == 'ERROR_CREATING_JOB' or api_result['status'] == 'ERROR_RUNNING_JOB'): - job_result = {'id': id, 'status': 'ERROR', + job_result = {'id': self.id, 'status': 'ERROR', 'result': api_result['status']} return Result(job_result) + time.sleep(wait) + api_result = self._update_status() + if self.cancelled: - job_result = {'id': id, 'status': 'CANCELLED', + job_result = {'id': self.id, 'status': 'CANCELLED', 'result': 'job cancelled'} return Result(job_result) + elif self.exception: - job_result = {'id': id, 'status': 'ERROR', + job_result = {'id': self.id, 'status': 'ERROR', 'result': str(self.exception)} return Result(job_result) + if api_result is None: - api_result = self._api.get_job(id) + api_result = self._api.get_job(self.id) + job_result_list = [] for circuit_result in api_result['qasms']: this_result = {'data': circuit_result['data'], @@ -413,7 +443,7 @@ def _wait_for_job(self, timeout=60, wait=5): if 'metadata' in circuit_result: this_result['metadata'] = circuit_result['metadata'] job_result_list.append(this_result) - job_result = {'id': id, + job_result = {'id': self.id, 'status': api_result['status'], 'used_credits': api_result.get('usedCredits'), 'result': job_result_list} From 2e72457bddd13eed018e5dc16541cd5e44325a64 Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Fri, 15 Jun 2018 15:58:32 -0400 Subject: [PATCH 25/31] linting --- qiskit/backends/ibmq/ibmqjob.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/backends/ibmq/ibmqjob.py b/qiskit/backends/ibmq/ibmqjob.py index 97807e0f0f03..e26c6c2fbb32 100644 --- a/qiskit/backends/ibmq/ibmqjob.py +++ b/qiskit/backends/ibmq/ibmqjob.py @@ -433,7 +433,7 @@ def _wait_for_job(self, timeout=60, wait=5): if api_result is None: api_result = self._api.get_job(self.id) - + job_result_list = [] for circuit_result in api_result['qasms']: this_result = {'data': circuit_result['data'], From 49edb417c99199047d145d40f9f0eb9c38be3297 Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Fri, 15 Jun 2018 16:38:54 -0400 Subject: [PATCH 26/31] linting --- qiskit/backends/ibmq/ibmqjob.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/backends/ibmq/ibmqjob.py b/qiskit/backends/ibmq/ibmqjob.py index e26c6c2fbb32..5183f75ed670 100644 --- a/qiskit/backends/ibmq/ibmqjob.py +++ b/qiskit/backends/ibmq/ibmqjob.py @@ -194,7 +194,7 @@ def _update_status(self): self._status_msg = '{}'.format(err) return - if 'RUNNING' == api_job['status']: + if api_job['status'] == 'RUNNING': self._status = JobStatus.RUNNING queued, queue_position = self._is_job_queued(api_job) if queued: @@ -202,10 +202,10 @@ def _update_status(self): if queue_position: self._queue_position = queue_position - elif 'COMPLETED' == api_job['status']: + elif api_job['status'] == 'COMPLETED': self._status = JobStatus.DONE - elif 'CANCELLED' == api_job['status']: + elif api_job['status'] == 'CANCELLED': self._status = JobStatus.CANCELLED elif 'ERROR' in api_job['status']: From 8581dcb89842a2dbd07a7416a881e044019744f2 Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Fri, 15 Jun 2018 16:52:20 -0400 Subject: [PATCH 27/31] linting --- qiskit/backends/ibmq/ibmqjob.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/qiskit/backends/ibmq/ibmqjob.py b/qiskit/backends/ibmq/ibmqjob.py index 5183f75ed670..ef955530b06a 100644 --- a/qiskit/backends/ibmq/ibmqjob.py +++ b/qiskit/backends/ibmq/ibmqjob.py @@ -16,8 +16,8 @@ import logging import pprint import json -import numpy import datetime +import numpy from qiskit.backends import BaseJob from qiskit.backends.jobstatus import JobStatus @@ -180,7 +180,7 @@ def _update_status(self): """Query the API to update the status.""" if (self._status in self._final_states or self._status == JobStatus.INITIALIZING): - return + return None try: api_job = self._api.get_job(self.id) @@ -192,7 +192,7 @@ def _update_status(self): self._status = JobStatus.ERROR self._exception = err self._status_msg = '{}'.format(err) - return + return None if api_job['status'] == 'RUNNING': self._status = JobStatus.RUNNING @@ -300,6 +300,7 @@ def id(self): """ Return backend determined id (also available in status method). """ + # pylint: disable=invalid-name while not self._id: # job is initializing and hasn't gotten a id yet. time.sleep(0.1) From 11d859b0899a99ca1f82c18574cf9eabd0f60f3f Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Fri, 15 Jun 2018 17:46:27 -0400 Subject: [PATCH 28/31] update job status_msg for status of RUNNING or QUEUED or DONE the status_msg would indicate "initializing" --- qiskit/backends/ibmq/ibmqjob.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qiskit/backends/ibmq/ibmqjob.py b/qiskit/backends/ibmq/ibmqjob.py index ef955530b06a..0a929eb48c1c 100644 --- a/qiskit/backends/ibmq/ibmqjob.py +++ b/qiskit/backends/ibmq/ibmqjob.py @@ -196,17 +196,21 @@ def _update_status(self): if api_job['status'] == 'RUNNING': self._status = JobStatus.RUNNING + self._status_msg = self._status.value queued, queue_position = self._is_job_queued(api_job) if queued: self._status = JobStatus.QUEUED + self._status_msg = self._status.value if queue_position: self._queue_position = queue_position elif api_job['status'] == 'COMPLETED': self._status = JobStatus.DONE + self._status_msg = self._status.value elif api_job['status'] == 'CANCELLED': self._status = JobStatus.CANCELLED + self._status_msg = self._status.value elif 'ERROR' in api_job['status']: # ERROR_CREATING_JOB or ERROR_RUNNING_JOB From a426d1bd204f61b5153b88bd161653a064d2aba3 Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Fri, 15 Jun 2018 22:55:48 -0400 Subject: [PATCH 29/31] fix hanging job.status on server error when calling job.status for a server with a suspended queue the command would hang. --- qiskit/backends/ibmq/ibmqjob.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/qiskit/backends/ibmq/ibmqjob.py b/qiskit/backends/ibmq/ibmqjob.py index 0a929eb48c1c..db03c8c37ea3 100644 --- a/qiskit/backends/ibmq/ibmqjob.py +++ b/qiskit/backends/ibmq/ibmqjob.py @@ -305,7 +305,10 @@ def id(self): Return backend determined id (also available in status method). """ # pylint: disable=invalid-name - while not self._id: + while self._id is None and self._status not in self._final_states: + if self._future_submit.exception(): + self._status = JobStatus.ERROR + self._exception = self._future_submit.exception() # job is initializing and hasn't gotten a id yet. time.sleep(0.1) return self._id @@ -377,10 +380,14 @@ def _submit(self): # pylint: disable=broad-except except Exception as err: self._status = JobStatus.ERROR + self._status_msg = str(err) self._exception = err + return None if 'error' in submit_info: self._status = JobStatus.ERROR - self._exception = IBMQJobError(str(submit_info['error'])) + self._status_msg = str(submit_info['error']) + self._exception = IBMQJobError(self._status_msg) + return submit_info self._id = submit_info.get('id') self.creation_date = submit_info.get('creationDate') self._status = JobStatus.QUEUED @@ -405,7 +412,7 @@ def _wait_for_job(self, timeout=60, wait=5): while self._status not in self._final_states: elapsed_time = time.time() - start_time if timeout is not None and elapsed_time >= timeout: - job_result = {'id': self.id, 'status': 'ERROR', + job_result = {'id': self._id, 'status': 'ERROR', 'result': 'QISkit Time Out'} return Result(job_result) logger.info('status = %s (%d seconds)', api_result['status'], @@ -419,7 +426,7 @@ def _wait_for_job(self, timeout=60, wait=5): if (api_result['status'] == 'ERROR_CREATING_JOB' or api_result['status'] == 'ERROR_RUNNING_JOB'): - job_result = {'id': self.id, 'status': 'ERROR', + job_result = {'id': self._id, 'status': 'ERROR', 'result': api_result['status']} return Result(job_result) @@ -427,17 +434,17 @@ def _wait_for_job(self, timeout=60, wait=5): api_result = self._update_status() if self.cancelled: - job_result = {'id': self.id, 'status': 'CANCELLED', + job_result = {'id': self._id, 'status': 'CANCELLED', 'result': 'job cancelled'} return Result(job_result) elif self.exception: - job_result = {'id': self.id, 'status': 'ERROR', + job_result = {'id': self._id, 'status': 'ERROR', 'result': str(self.exception)} return Result(job_result) if api_result is None: - api_result = self._api.get_job(self.id) + api_result = self._api.get_job(self._id) job_result_list = [] for circuit_result in api_result['qasms']: @@ -448,7 +455,7 @@ def _wait_for_job(self, timeout=60, wait=5): if 'metadata' in circuit_result: this_result['metadata'] = circuit_result['metadata'] job_result_list.append(this_result) - job_result = {'id': self.id, + job_result = {'id': self._id, 'status': api_result['status'], 'used_credits': api_result.get('usedCredits'), 'result': job_result_list} From c2d103310bbe642e48db705b8159ade6da2eaedd Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Mon, 18 Jun 2018 14:32:22 -0400 Subject: [PATCH 30/31] remove job status filtering database filtering was introduced in API 1.9.4, which has much better performance, however it doesn't seem to filter on status properly for real devices. --- qiskit/backends/ibmq/ibmqbackend.py | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/qiskit/backends/ibmq/ibmqbackend.py b/qiskit/backends/ibmq/ibmqbackend.py index c3a0c8225273..2426b4eaf19a 100644 --- a/qiskit/backends/ibmq/ibmqbackend.py +++ b/qiskit/backends/ibmq/ibmqbackend.py @@ -142,35 +142,23 @@ def status(self): "Couldn't get backend status: {0}".format(ex)) return status - def jobs(self, limit=50, skip=0, status=None): + def jobs(self, limit=50, skip=0): """Attempt to get the jobs submitted to the backend Args: limit (int): number of jobs to retrieve skip (int): starting index of retrieval - status (None or JobStatus or str): only get jobs with this status, - where status is e.g. JobStatus.RUNNING or 'RUNNING'. Returns: list(IBMQJob): list of IBMQJob instances """ backend_name = self.configuration['name'] + job_info_list = self._api.get_jobs(limit=limit, skip=skip, + backend=backend_name) job_list = [] - base_index = skip - job_info_list = [] - if isinstance(status, str): - status = JobStatus[status] - while len(job_list) < limit or len(job_info_list) < limit: - job_info_list = self._api.get_jobs(limit=limit, skip=base_index, - backend=backend_name) - for job_info in job_info_list: - is_device = not bool(self._configuration.get('simulator')) - job = IBMQJob.from_api(job_info, self._api, is_device) - if len(job_list) < limit: - if status is None: - job_list.append(job) - elif job.status.get('status') == status: - job_list.append(job) - base_index += limit + for job_info in job_info_list: + is_device = not bool(self._configuration.get('simulator')) + job = IBMQJob.from_api(job_info, self._api, is_device) + job_list.append(job) return job_list def retrieve_job(self, job_id): From cee799c081ae76ca989d69e6cf89b0ee841cc6a7 Mon Sep 17 00:00:00 2001 From: Erick Winston Date: Mon, 18 Jun 2018 14:52:44 -0400 Subject: [PATCH 31/31] linting --- qiskit/backends/ibmq/ibmqbackend.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/backends/ibmq/ibmqbackend.py b/qiskit/backends/ibmq/ibmqbackend.py index 2426b4eaf19a..1b50694d8567 100644 --- a/qiskit/backends/ibmq/ibmqbackend.py +++ b/qiskit/backends/ibmq/ibmqbackend.py @@ -14,7 +14,6 @@ from qiskit import QISKitError from qiskit._util import _camel_case_to_snake_case from qiskit.backends import BaseBackend -from qiskit.backends import JobStatus from qiskit.backends.ibmq.ibmqjob import IBMQJob logger = logging.getLogger(__name__)