diff --git a/qiskit/_result.py b/qiskit/_result.py index 0d63a34fed3b..a204d82bd5ba 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. @@ -73,30 +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) - 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'] + 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 have different configs and cannot be combined.') + 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: @@ -110,7 +98,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 +140,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 +198,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 +207,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 +350,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 +375,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 +389,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 +406,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/__init__.py b/qiskit/backends/__init__.py index 71a65bcb1fa8..3bd501858a4a 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 .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 50675f4694af..1b50694d8567 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 @@ -139,3 +140,46 @@ 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 + 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 = [] + 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): + """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)) + 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 ad7586758ecc..db03c8c37ea3 100644 --- a/qiskit/backends/ibmq/ibmqjob.py +++ b/qiskit/backends/ibmq/ibmqjob.py @@ -15,14 +15,16 @@ import time import logging import pprint +import json +import datetime +import numpy -from IBMQuantumExperience import ApiError -from qiskit._compiler import compile_circuit 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 +from qiskit._compiler import compile_circuit logger = logging.getLogger(__name__) @@ -32,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. @@ -47,22 +55,81 @@ 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) - 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 + self.creation_date = datetime.datetime.utcnow().replace( + tzinfo=datetime.timezone.utc).isoformat() + + @classmethod + def from_api(cls, 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, + '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 + + Returns: + IBMQJob: an instance of this class + """ + 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._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') + return job_instance 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 + + 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(): + 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: - _reorder_bits(this_result) # TODO: remove this after Qobj + _reorder_bits(this_result) if this_result.get_status() == 'ERROR': self._status = JobStatus.ERROR else: @@ -82,7 +149,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) @@ -96,50 +163,83 @@ def cancel(self): @property def status(self): - if self._status == JobStatus.INITIALIZING: - stats = {'job_id': None, - 'status': self._status, - 'status_msg': 'job is begin initialized please wait a moment'} - return stats - job_result = self._api.get_job(self._job_id) - stats = {'job_id': self._job_id} - self._status = None - _status_msg = None - if 'status' not in job_result: - self._exception = QISKitError("get_job didn't return status: %s" % - (pprint.pformat(job_result))) - raise QISKitError("get_job didn't return status: %s" % - (pprint.pformat(job_result))) - elif job_result['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 None + + 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 None + + if 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': - self._status = JobStatus.QUEUED - if 'position' in job_result['infoQueue']: - stats['queue_position'] = job_result['infoQueue']['position'] - elif job_result['status'] == 'COMPLETED': + 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 - elif job_result['status'] == 'CANCELLED': + self._status_msg = self._status.value + + elif api_job['status'] == 'CANCELLED': self._status = JobStatus.CANCELLED - elif self.exception: + self._status_msg = self._status.value + + 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 job_result['status']: - # ERROR_CREATING_JOB or ERROR_RUNNING_JOB - self._status = JobStatus.ERROR - self._status_msg = job_result['status'] + else: self._status = JobStatus.ERROR - raise IBMQJobError('Unexpected behavior of {0}\n{1}'.format( - self.__class__.__name__, - pprint.pformat(job_result))) - 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 + 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): @@ -200,11 +300,18 @@ 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). """ - return self._job_id + # pylint: disable=invalid-name + 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 @property def backend_name(self): @@ -228,15 +335,23 @@ 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'] + # 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 if 'hpc' in qobj['config']: @@ -250,7 +365,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 " @@ -258,18 +372,24 @@ def _submit(self): self._backend_name)) submit_info = {} try: - submit_info = self._api.run_job(api_jobs, 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: + # 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._job_id = submit_info.get('id') + 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 return submit_info @@ -287,55 +407,60 @@ 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 - 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) - 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', + start_time = time.time() + 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': self._id, 'status': 'ERROR', 'result': 'QISkit Time Out'} - return Result(job_result, qobj) - time.sleep(wait) - timer += wait - logger.info('status = %s (%d seconds)', api_result['status'], timer) - api_result = self._api.get_job(job_id) + return Result(job_result) + logger.info('status = %s (%d seconds)', api_result['status'], + elapsed_time) if 'status' not in api_result: self._exception = QISKitError("get_job didn't return status: %s" % (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 = {'job_id': job_id, 'status': 'ERROR', + job_result = {'id': self._id, 'status': 'ERROR', 'result': api_result['status']} - return Result(job_result, qobj) + return Result(job_result) + + time.sleep(wait) + api_result = self._update_status() if self.cancelled: - job_result = {'job_id': job_id, 'status': 'CANCELLED', + job_result = {'id': self._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', + job_result = {'id': self._id, 'status': 'ERROR', 'result': str(self.exception)} - 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'], - '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) + return Result(job_result) + + 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'], + '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 = {'id': self._id, + 'status': api_result['status'], + 'used_credits': api_result.get('usedCredits'), + 'result': job_result_list} + job_result['backend_name'] = self.backend_name + return Result(job_result) class IBMQJobError(QISKitError): @@ -347,16 +472,19 @@ 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']): - + for circuit_result in result._result['result']: + if 'metadata' in circuit_result: + circ = circuit_result['metadata'].get('compiled_circuit') + else: + 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['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 @@ -369,13 +497,13 @@ def _reorder_bits(result): 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 = [] @@ -395,4 +523,14 @@ def _reorder_bits(result): 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() + return obj 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/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/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..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__) @@ -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_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_compiler.py b/test/python/test_compiler.py index 12df6723684e..c91a7b8e63ed 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 838da4168582..c8da16aea102 100644 --- a/test/python/test_ibmqjob.py +++ b/test/python/test_ibmqjob.py @@ -20,7 +20,8 @@ import qiskit._compiler from qiskit.backends.ibmq import IBMQProvider from qiskit.backends.ibmq.ibmqjob import IBMQJob, IBMQJobError -from qiskit.backends.basejob import JobStatus +from qiskit.backends.ibmq.ibmqbackend import IBMQBackendError +from qiskit.backends import JobStatus from .common import requires_qe_access, QiskitTestCase, slow_test @@ -55,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): @@ -132,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 ' @@ -146,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 @@ -192,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): @@ -237,10 +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) - 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) + 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' @@ -250,6 +265,32 @@ 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() + 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.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.id) + self.assertTrue(job.id == rjob.id) + self.assertTrue(job.result().get_counts() == rjob.result().get_counts()) + + 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) 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])) diff --git a/test/python/test_reordering.py b/test/python/test_reordering.py index 18a2ea4869ed..5b2ca743eb07 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 +import qiskit._compiler 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 = 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, + shots=shots) + 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() 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 = 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, + shots=shots) + 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() threshold = 0.2 * shots