mirror of https://github.com/Qiskit/qiskit.git
Isolate legacy code.
This PR factor out the decisions about when to use the new implementation for Qobj-based jobs (IBMQJob) or the previous one (IBMQJobPreQobj) and a bungh of module-level functions. For distinguishing between jobs sent using Qobj or the old format, the backend team included a `kind: "q-object" field in the job response. After conversations with the backend team, it is assumed that all the backends should answer with the new QobjResult format if the job was a Qobj-based job and with the previous format if the job was using the old format. The PR also extends `from_dict` capabilities to cast numpy types to Python types.
This commit is contained in:
parent
fcaf3d5649
commit
c67f33629b
|
@ -54,10 +54,7 @@ class IBMQBackend(BaseBackend):
|
|||
Returns:
|
||||
IBMQJob: an instance derived from BaseJob
|
||||
"""
|
||||
if self.configuration().get('allow_q_object'):
|
||||
job_class = IBMQJob
|
||||
else:
|
||||
job_class = IBMQJobPreQobj
|
||||
job_class = _job_class_from_backend_support(self)
|
||||
job = job_class(self._api, not self.configuration()['simulator'], qobj=qobj)
|
||||
job.submit()
|
||||
return job
|
||||
|
@ -216,12 +213,8 @@ class IBMQBackend(BaseBackend):
|
|||
filter=api_filter)
|
||||
job_list = []
|
||||
for job_info in job_info_list:
|
||||
job_class = self._job_info_to_job_class(job_info)
|
||||
job_class = _job_class_from_job_response(job_info)
|
||||
is_device = not bool(self._configuration.get('simulator'))
|
||||
if 'header' in job_info:
|
||||
backend_name = job_info['header'].get('backend_name')
|
||||
elif 'backend' in job_info: # old style job_info
|
||||
backend_name = job_info['backend'].get('name')
|
||||
job = job_class(self._api, is_device,
|
||||
job_id=job_info.get('id'),
|
||||
backend_name=backend_name,
|
||||
|
@ -229,15 +222,6 @@ class IBMQBackend(BaseBackend):
|
|||
job_list.append(job)
|
||||
return job_list
|
||||
|
||||
def _job_info_to_job_class(self, job_info):
|
||||
if 'result' in job_info:
|
||||
job_class = IBMQJob
|
||||
elif 'qasms' in job_info:
|
||||
job_class = IBMQJobPreQobj
|
||||
else:
|
||||
raise IBMQBackendError('unrecognised job record from API')
|
||||
return job_class
|
||||
|
||||
def retrieve_job(self, job_id):
|
||||
"""Attempt to get the specified job by job_id
|
||||
|
||||
|
@ -258,7 +242,7 @@ class IBMQBackend(BaseBackend):
|
|||
except ApiError as ex:
|
||||
raise IBMQBackendError('Failed to get job "{}":{}'
|
||||
.format(job_id, str(ex)))
|
||||
job_class = self._job_info_to_job_class(job_info)
|
||||
job_class = _job_class_from_job_response(job_info)
|
||||
is_device = not bool(self._configuration.get('simulator'))
|
||||
job = job_class(self._api, is_device,
|
||||
job_id=job_info.get('id'),
|
||||
|
@ -275,3 +259,13 @@ class IBMQBackendError(QISKitError):
|
|||
class IBMQBackendValueError(IBMQBackendError, ValueError):
|
||||
""" Value errors thrown within IBMQBackend """
|
||||
pass
|
||||
|
||||
|
||||
def _job_class_from_job_response(job_response):
|
||||
is_qobj = job_response.get('kind', None) == 'q-object'
|
||||
return IBMQJob if is_qobj else IBMQJobPreQobj
|
||||
|
||||
|
||||
def _job_class_from_backend_support(backend):
|
||||
support_qobj = backend.configuration().get('allow_q_object')
|
||||
return IBMQJob if support_qobj else IBMQJobPreQobj
|
||||
|
|
|
@ -114,7 +114,7 @@ class IBMQJob(BaseJob):
|
|||
_executor = futures.ThreadPoolExecutor()
|
||||
|
||||
def __init__(self, api, is_device, qobj=None, job_id=None, backend_name=None,
|
||||
creation_date=None, backend_allows_qobj=False):
|
||||
creation_date=None):
|
||||
"""IBMQJob init function.
|
||||
We can instantiate jobs from two sources: A QObj, and an already submitted job returned by
|
||||
the API servers.
|
||||
|
@ -126,7 +126,6 @@ class IBMQJob(BaseJob):
|
|||
job_id (String): The job ID of an already submitted job.
|
||||
backend_name(String): The name of the backend that run the job.
|
||||
creation_date(String): When the job was run.
|
||||
backend_allows_qobj (Bool): whether backend allows qobj input directly
|
||||
|
||||
Notes:
|
||||
It is mandatory to pass either ``qobj`` or ``job_id``. Passing a ``qobj``
|
||||
|
@ -139,7 +138,7 @@ class IBMQJob(BaseJob):
|
|||
if qobj is not None:
|
||||
validate_qobj_against_schema(qobj)
|
||||
|
||||
self._qobj = qobj_to_dict(qobj, version='1.0.0')
|
||||
self._qobj_payload = qobj_to_dict(qobj, version='1.0.0')
|
||||
# TODO: No need for this conversion, just use the new equivalent members above
|
||||
old_qobj = qobj_to_dict(qobj, version='0.0.1')
|
||||
self._job_data = {
|
||||
|
@ -170,7 +169,6 @@ class IBMQJob(BaseJob):
|
|||
self._creation_date = creation_date or current_utc_time()
|
||||
self._future = None
|
||||
self._api_error_msg = None
|
||||
self._backend_allows_qobj = backend_allows_qobj
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
def result(self, timeout=None, wait=5):
|
||||
|
@ -186,48 +184,33 @@ class IBMQJob(BaseJob):
|
|||
Raises:
|
||||
JobError: exception raised during job initialization
|
||||
"""
|
||||
job_response = self._wait_for_result(timeout=timeout, wait=wait)
|
||||
return self._result_from_job_response(job_response)
|
||||
|
||||
def _wait_for_result(self, timeout=None, wait=5):
|
||||
self._wait_for_submission()
|
||||
|
||||
try:
|
||||
job_data = self._wait_for_job(timeout=timeout, wait=wait)
|
||||
job_response = self._wait_for_job(timeout=timeout, wait=wait)
|
||||
except ApiError as api_err:
|
||||
raise JobError(str(api_err))
|
||||
if 'result' in job_data:
|
||||
return IBMQJob._result_from_api_response(job_data)
|
||||
elif 'qasms' in job_data:
|
||||
if self._is_device:
|
||||
_reorder_bits(job_data)
|
||||
return IBMQJobPreQobj._result_from_api_response(job_data,
|
||||
self.id(),
|
||||
self.backend_name(),
|
||||
self._is_device,
|
||||
self.status())
|
||||
else:
|
||||
raise JobError('unrecognized job data from API ({})'.format(self._id))
|
||||
|
||||
@staticmethod
|
||||
def _result_from_api_response(api_response):
|
||||
# Build the Result.
|
||||
status = self.status()
|
||||
if status is not JobStatus.DONE:
|
||||
raise JobError('Invalid job state. The job should be DONE but '
|
||||
'it is {}'.format(str(status)))
|
||||
|
||||
return job_response
|
||||
|
||||
def _result_from_job_response(self, job_response):
|
||||
experiment_results = []
|
||||
job_result = api_response['result']
|
||||
for resultobj in job_result['results']:
|
||||
qobj_exp_result_args = [resultobj.get(arg) for arg in
|
||||
QobjExperimentResult.REQUIRED_ARGS]
|
||||
qobj_exp_result_kwargs = {key: value for (key, value) in
|
||||
resultobj.items() if key not in
|
||||
QobjExperimentResult.REQUIRED_ARGS}
|
||||
result_json = job_response['qObjectResult']
|
||||
for experiment_result_json in result_json['results']:
|
||||
qobj_experiment_result = QobjExperimentResult(**experiment_result_json)
|
||||
experiment_results.append(qobj_experiment_result)
|
||||
|
||||
qobj_exp_result = QobjExperimentResult(*qobj_exp_result_args,
|
||||
**qobj_exp_result_kwargs)
|
||||
experiment_results.append(qobj_exp_result)
|
||||
qobj_result_args = [job_result.get(arg) for arg in
|
||||
QobjResult.REQUIRED_ARGS]
|
||||
qobj_result_kwargs = {key: value for (key, value) in
|
||||
job_result.items() if key not in
|
||||
QobjResult.REQUIRED_ARGS}
|
||||
qobj_result = QobjResult(*qobj_result_args, **qobj_result_kwargs)
|
||||
# replace job_result list of dict with list of Qobj ExperimentResult
|
||||
qobj_result.results = experiment_results
|
||||
return Result(qobj_result)
|
||||
result_kwargs = {**result_json, 'results': experiment_results}
|
||||
return Result(QobjResult(**result_kwargs))
|
||||
|
||||
def cancel(self):
|
||||
"""Attempt to cancel a job.
|
||||
|
@ -358,10 +341,10 @@ class IBMQJob(BaseJob):
|
|||
Returns:
|
||||
dict: A dictionary with the response of the submitted job
|
||||
"""
|
||||
backend_name = self._qobj['header']['backend_name']
|
||||
backend_name = self._backend_name
|
||||
|
||||
try:
|
||||
submit_info = self._api.run_job(self._qobj, backend=backend_name)
|
||||
submit_info = self._api.run_job(self._qobj_payload, backend=backend_name)
|
||||
# pylint: disable=broad-except
|
||||
except Exception as err:
|
||||
# Undefined error during submission:
|
||||
|
@ -482,66 +465,30 @@ class IBMQJobPreQobj(IBMQJob):
|
|||
self._id = submit_info.get('id')
|
||||
return submit_info
|
||||
|
||||
# pylint disable since this version of the function signature will hopefully
|
||||
# be removed later
|
||||
# pylint: disable=arguments-differ
|
||||
@staticmethod
|
||||
def _result_from_api_response(api_response, job_id, backend_name, is_device,
|
||||
job_status):
|
||||
# Build the Result.
|
||||
experiment_results = []
|
||||
if is_device and job_status == JobStatus.DONE:
|
||||
_reorder_bits(api_response)
|
||||
def _result_from_job_response(self, job_response):
|
||||
if self._is_device:
|
||||
_reorder_bits(job_response)
|
||||
|
||||
for circuit_result in api_response['qasms']:
|
||||
experiment_results = []
|
||||
for circuit_result in job_response['qasms']:
|
||||
this_result = {'data': circuit_result['data'],
|
||||
'name': circuit_result.get('name'),
|
||||
'compiled_circuit_qasm': circuit_result.get('qasm'),
|
||||
'status': circuit_result['status'],
|
||||
'success': circuit_result['status'] == 'DONE',
|
||||
'shots': api_response['shots']}
|
||||
'shots': job_response['shots']}
|
||||
if 'metadata' in circuit_result:
|
||||
this_result['metadata'] = circuit_result['metadata']
|
||||
experiment_results.append(this_result)
|
||||
|
||||
return result_from_old_style_dict({
|
||||
'id': job_id,
|
||||
'status': api_response['status'],
|
||||
'used_credits': api_response.get('usedCredits'),
|
||||
'id': self._id,
|
||||
'status': job_response['status'],
|
||||
'used_credits': job_response.get('usedCredits'),
|
||||
'result': experiment_results,
|
||||
'backend_name': backend_name,
|
||||
'success': api_response['status'] == 'DONE'
|
||||
}, [circuit_result['name'] for circuit_result in api_response['qasms']])
|
||||
|
||||
|
||||
def _result_from_api_response(api_response, job_id=None, backend_name=None,
|
||||
is_device=None):
|
||||
"""
|
||||
Decides whether job_data is in pre-qobj format and returns appropriate
|
||||
job instance
|
||||
|
||||
Args:
|
||||
api_response (dict): dict with the bare contents of the API.get_job request.
|
||||
job_id (str): job identity on frontend (for pre-qobj results)
|
||||
backend_name (str): backend name (for pre-qobj results)
|
||||
is_device (bool): whether backend is a real device
|
||||
|
||||
Raises:
|
||||
JobError: api response doesn't have 'result' or 'qasms' record
|
||||
|
||||
Returns:
|
||||
Result: qiskit.result.Result object
|
||||
"""
|
||||
if 'result' in api_response:
|
||||
return IBMQJob._result_from_api_response(api_response)
|
||||
elif 'qasms' in api_response:
|
||||
if is_device:
|
||||
_reorder_bits(api_response)
|
||||
return IBMQJobPreQobj._result_from_api_response(api_response, job_id,
|
||||
backend_name,
|
||||
is_device)
|
||||
else:
|
||||
raise JobError('unrecognized job data from API ({})'.format(job_id))
|
||||
'backend_name': self.backend_name(),
|
||||
'success': job_response['status'] == 'DONE'
|
||||
}, [circuit_result['name'] for circuit_result in job_response['qasms']])
|
||||
|
||||
|
||||
def _reorder_bits(job_data):
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
from types import SimpleNamespace
|
||||
|
||||
import numpy
|
||||
|
||||
from ._validation import QobjValidationError
|
||||
from ._utils import QobjType
|
||||
|
||||
|
@ -42,10 +44,16 @@ class QobjItem(SimpleNamespace):
|
|||
"""
|
||||
Return a valid representation of `obj` depending on its type.
|
||||
"""
|
||||
if isinstance(obj, list):
|
||||
if isinstance(obj, (list, tuple)):
|
||||
return [cls._expand_item(item) for item in obj]
|
||||
if isinstance(obj, QobjItem):
|
||||
return obj.as_dict()
|
||||
if isinstance(obj, numpy.integer):
|
||||
return int(obj)
|
||||
if isinstance(obj, numpy.float):
|
||||
return float(obj)
|
||||
if isinstance(obj, numpy.ndarray):
|
||||
return obj.tolist()
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
jsonschema>=2.6,<2.7
|
||||
IBMQuantumExperience>=2.0.1
|
||||
IBMQuantumExperience>=2.0.3
|
||||
matplotlib>=2.1
|
||||
networkx>=2.0
|
||||
numpy>=1.13
|
||||
|
|
|
@ -17,12 +17,12 @@ from setuptools.dist import Distribution
|
|||
|
||||
requirements = [
|
||||
"jsonschema>=2.6,<2.7",
|
||||
"IBMQuantumExperience>=1.9.8",
|
||||
"IBMQuantumExperience>=2.0.3",
|
||||
"matplotlib>=2.1",
|
||||
"networkx>=2.0",
|
||||
"numpy>=1.13",
|
||||
"ply>=3.10",
|
||||
"scipy>=0.19",
|
||||
"scipy>=0.19,!=0.19.1",
|
||||
"sympy>=1.0",
|
||||
"pillow>=4.2.1"
|
||||
]
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
"""IBMQJob Test."""
|
||||
|
||||
import os
|
||||
import time
|
||||
import unittest
|
||||
from concurrent import futures
|
||||
|
@ -45,14 +46,7 @@ class TestIBMQJob(JobTestCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
# create QuantumCircuit
|
||||
qr = QuantumRegister(2, 'q')
|
||||
cr = ClassicalRegister(2, 'c')
|
||||
qc = QuantumCircuit(qr, cr)
|
||||
qc.h(qr[0])
|
||||
qc.cx(qr[0], qr[1])
|
||||
qc.measure(qr, cr)
|
||||
self._qc = qc
|
||||
self._qc = _bell_circuit()
|
||||
|
||||
@requires_qe_access
|
||||
def test_run_simulator(self, qe_token, qe_url):
|
||||
|
@ -337,5 +331,48 @@ class TestIBMQJob(JobTestCase):
|
|||
job.submit()
|
||||
|
||||
|
||||
class TestQObjectBasedIBMQJob(JobTestCase):
|
||||
"""Test jobs supporting QObject."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self._testing_device = os.getenv('IBMQ_QOBJ_DEVICE', None)
|
||||
self._qe_token = os.getenv('IBMQ_TOKEN', None)
|
||||
self._qe_url = os.getenv('IBMQ_QOBJ_URL')
|
||||
if not self._testing_device or not self._qe_token or not self._qe_url:
|
||||
self.skipTest('No credentials or testing device available for '
|
||||
'testing Qobj capabilities.')
|
||||
|
||||
self._provider = IBMQProvider(self._qe_token, self._qe_url)
|
||||
self._backend = self._provider.get_backend(self._testing_device)
|
||||
|
||||
self._qc = _bell_circuit()
|
||||
|
||||
def test_qobject_enabled_job(self):
|
||||
"""Job should be an instance of IBMQJob."""
|
||||
qobj = transpiler.compile(self._qc, self._backend)
|
||||
job = self._backend.run(qobj)
|
||||
self.assertIsInstance(job, IBMQJob)
|
||||
|
||||
def test_qobject_result(self):
|
||||
"""Jobs can be retrieved."""
|
||||
qobj = transpiler.compile(self._qc, self._backend)
|
||||
job = self._backend.run(qobj)
|
||||
try:
|
||||
job.result()
|
||||
except JobError as err:
|
||||
self.fail(err)
|
||||
|
||||
|
||||
def _bell_circuit():
|
||||
qr = QuantumRegister(2, 'q')
|
||||
cr = ClassicalRegister(2, 'c')
|
||||
qc = QuantumCircuit(qr, cr)
|
||||
qc.h(qr[0])
|
||||
qc.cx(qr[0], qr[1])
|
||||
qc.measure(qr, cr)
|
||||
return qc
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
|
|
|
@ -15,7 +15,7 @@ import time
|
|||
from contextlib import suppress
|
||||
from IBMQuantumExperience import ApiError
|
||||
from qiskit.backends.jobstatus import JobStatus
|
||||
from qiskit.backends.ibmq.ibmqjob import IBMQJob
|
||||
from qiskit.backends.ibmq.ibmqjob import IBMQJobPreQobj, IBMQJob
|
||||
from qiskit.backends.ibmq.ibmqjob import API_FINAL_STATES
|
||||
from qiskit.backends import JobError, JobTimeoutError
|
||||
from .common import JobTestCase
|
||||
|
@ -258,13 +258,27 @@ class TestIBMQJobStates(JobTestCase):
|
|||
else:
|
||||
self.assertFalse(self._current_api.get_job.called)
|
||||
|
||||
def run_with_api(self, api):
|
||||
"""Creates a new `IBMQJob` instance running with the provided API
|
||||
# TODO: Once qobj results come by default from all the simulator
|
||||
# backends, move to integration tests in test_result.py
|
||||
def test_qobj_result(self):
|
||||
job = self.run_with_api(QObjResultAPI(), job_class=IBMQJob)
|
||||
|
||||
self.wait_for_initialization(job)
|
||||
self._current_api.progress()
|
||||
result = job.result()
|
||||
self.assertEqual(result.get_status(), 'COMPLETED')
|
||||
self.assertEqual(result.get_counts('Bell state'),
|
||||
{'0x0': 480, '0x3': 490, '0x1': 20, '0x2': 34})
|
||||
self.assertEqual(result.get_counts('Bell state XY'),
|
||||
{'0x0': 29, '0x3': 15, '0x1': 510, '0x2': 480})
|
||||
self.assertEqual(len(result), 2)
|
||||
|
||||
def run_with_api(self, api, job_class=IBMQJobPreQobj):
|
||||
"""Creates a new ``IBMQJobPreQobj`` instance running with the provided API
|
||||
object.
|
||||
"""
|
||||
self._current_api = api
|
||||
self._current_qjob = IBMQJob(api, False, qobj=new_fake_qobj(),
|
||||
backend_allows_qobj=True)
|
||||
self._current_qjob = job_class(api, False, qobj=new_fake_qobj())
|
||||
self._current_qjob.submit()
|
||||
return self._current_qjob
|
||||
|
||||
|
@ -450,7 +464,7 @@ class ThrowingGetJobAPI(BaseFakeAPI):
|
|||
return self._job_status[self._state]
|
||||
|
||||
def get_job(self, job_id):
|
||||
raise ApiError("Unexpected error")
|
||||
raise ApiError('Unexpected error')
|
||||
|
||||
|
||||
class CancellableAPI(BaseFakeAPI):
|
||||
|
@ -491,5 +505,50 @@ class ErroredCancellationAPI(BaseFakeAPI):
|
|||
return {'status': 'Error', 'error': 'test-error-while-cancelling'}
|
||||
|
||||
|
||||
# TODO: Remove once qobj results come by default from all the simulator
|
||||
# backends.
|
||||
class QObjResultAPI(BaseFakeAPI):
|
||||
"""Class for emulating a successfully-completed non-queued API."""
|
||||
|
||||
_job_status = [
|
||||
{'status': 'RUNNING'},
|
||||
{
|
||||
'status': 'COMPLETED',
|
||||
'qObjectResult': {
|
||||
'backend_name': 'ibmqx2',
|
||||
'backend_version': '1.1.1',
|
||||
'job_id': 'XC1323XG2',
|
||||
'qobj_id': 'Experiment1',
|
||||
'success': True,
|
||||
'status': 'COMPLETED',
|
||||
'results': [
|
||||
{
|
||||
'header': {'name': 'Bell state'},
|
||||
'shots': 1024,
|
||||
'status': 'DONE',
|
||||
'success': True,
|
||||
'data': {
|
||||
'counts': {
|
||||
'0x0': 480, '0x3': 490, '0x1': 20, '0x2': 34
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
'header': {'name': 'Bell state XY'},
|
||||
'shots': 1024,
|
||||
'status': 'DONE',
|
||||
'success': True,
|
||||
'data': {
|
||||
'counts': {
|
||||
'0x0': 29, '0x3': 15, '0x1': 510, '0x2': 480
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
|
|
Loading…
Reference in New Issue