Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

get jobs from IBM Q backend #501

Merged
merged 32 commits into from
Jun 20, 2018
Merged

get jobs from IBM Q backend #501

merged 32 commits into from
Jun 20, 2018

Conversation

ewinston
Copy link
Contributor

@ewinston ewinston commented May 24, 2018

This PR allows one to get IBMQJob objects for previously submitted jobs to an IBM Q backend.

job_list = backend.jobs()
job = backend.retrieve_job(job_id)

Description

TODO:

  • pass tests
  • skip keyword isn't implemented

Motivation and Context

How Has This Been Tested?

Screenshots (if appropriate):

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

@ewinston ewinston changed the title [WIP] get jobs from IBM Q backend get jobs from IBM Q backend Jun 1, 2018
@diego-plan9 diego-plan9 mentioned this pull request Jun 6, 2018
5 tasks
@diego-plan9 diego-plan9 added this to the 0.5.x milestone Jun 6, 2018
@ajavadia
Copy link
Member

@ewinston I think this requires an update to IBMQuantumExperience 1.9.2 (should go in requirements.txt)

self._result['result'] += other._result['result']
return self
else:
raise QISKitError('Result objects have different configs and cannot be combined.')
Copy link
Member

Choose a reason for hiding this comment

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

Don't we still need to do some checks in order to make sure the results can be combined, and if not raise this QISKitError? For example one of the checks is to make sure they come from the same backend. Previously this was done based on qobj, but I think the result has enough information for this check too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Previously it checked that the 'config' section of the qobj matched, however, perhaps we can just match the backend_name since I don't think 'shots' or 'max_credits' mismatch should raise an error.

else:
raise QISKitError('Result objects have different configs and cannot be combined.')
self._result['result'] += other._result['result']
return self

def __add__(self, other):
Copy link
Member

Choose a reason for hiding this comment

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

Does the docstring of this method need updating? I think result doesn't have a qobj_id anymore.

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
Copy link
Member

Choose a reason for hiding this comment

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

can we keep this compile instead of qcompile for consistency with the rest of the code base? it won't collide with the compile keyword after this import. it has been used as compile(), for example in wrapper.py.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought this raised a style or linting flag but I'll retry.

Args:
limit (int): number of jobs to retrieve
skip (int): starting index of retrieval
status (None or JobStatus): only get jobs with this status.
Copy link
Member

Choose a reason for hiding this comment

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

would it make sense to make this string for easier use? e.g. "running", "done", "queued", ..

if not, then I think this docstring should be updated with some examples, e.g. JobStatus.RUNNING, JobStatus.Done, JobStatus.QUEUED

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since this questions the use of enumerated types perhaps the docstring can just be updated for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, it looks easy to also allow the string representation.

@@ -33,7 +33,6 @@ class Result(object):

Internal::

qobj = { -- the quantum object that was complied --}
result = {
Copy link
Member

Choose a reason for hiding this comment

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

I think now that Result._qobj is removed, we can unpack Result._result, and not have so many levels of indirection. That means having Result._job_id, Result._status, Result._result, where the latter contains all the data.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This sounds ok to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe we can leave this for a subsequent PR since it's not directly related to this one?

Copy link
Member

Choose a reason for hiding this comment

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

yeah i think we should leave this, also because it's not clear how qobj and result may be represented together later.
I think the most important thing in this PR is to not give errors when metadata is unavailable from a past job.

if 'metadata' in circuit_result:
circ = circuit_result['metadata'].get('compiled_circuit')
else:
raise QISKitError('result object missing metadata for reordering bits')
Copy link
Member

Choose a reason for hiding this comment

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

This get raised when calling job.result() for all jobs that were run prior to the new metadata field, which seems quite recent. Should it at least return something, even though it could potentially miss some reorderings? Otherwise this PR is not immediately usable if one goes searching for past jobs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, we can return and log warning.

@diego-plan9
Copy link
Member

@ewinston I think this requires an update to IBMQuantumExperience 1.9.2 (should go in requirements.txt)

Yes - actually addressed in #558!

@ewinston ewinston force-pushed the backend_jobs branch 2 times, most recently from 8caca6e to 7ce53ea Compare June 12, 2018 19:31
@ajavadia
Copy link
Member

Can we provide an easy method to get the date/time of execution of a job? I think that's useful for filtering, when someone gets a long list of past executions. It could go in the status for DONE jobs, or it could be a separate method of IBMQJob.

@ajavadia
Copy link
Member

Other than that I think everything looks pretty great!

ajavadia
ajavadia previously approved these changes Jun 12, 2018
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)
Copy link
Member

Choose a reason for hiding this comment

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

Why do we have to make repeated API calls here? Seems like the API should be able to return all the jobs at once. If it is not, perhaps we should update the API.

backend_name = self.configuration['name']
job_list = []
base_index = skip
job_info_list = self._api.get_jobs(limit=limit, skip=base_index,
Copy link
Member

Choose a reason for hiding this comment

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

a bit of code restructuring:
I think the 2 calls to the API here can be made just one. Place it in the first line of the while loop. Then if all the jobs are retrieved in the first call, the while loop terminates. The way it is written right now, at least 2 calls are made every time.

@ajavadia
Copy link
Member

@ewinston this looks good to merge to me. Can you please confirm that the TODO section of the original PR description is done now?

ajavadia
ajavadia previously approved these changes Jun 13, 2018
@ewinston
Copy link
Contributor Author

@ajavadia yes I believe these are done now.

@ajavadia
Copy link
Member

The new speed improvement is really good. Mine went from 13 secs to 0.9 secs

job_instance._status_msg = None
job_instance._cancelled = False
job_instance._is_device = is_device
job_instance._from_api = True
Copy link
Member

Choose a reason for hiding this comment

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

Is this flag being used?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed, thanks

# 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)
Copy link
Member

Choose a reason for hiding this comment

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

I would encourage to at least change these loops for the ._wait_for_submmiting() method.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since this seems like a performance improvement but it risks breaking things perhaps I can leave this for a separate PR?

Copy link
Member

Choose a reason for hiding this comment

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

Ok

@@ -97,12 +158,12 @@ def cancel(self):
@property
def status(self):
if self._status == JobStatus.INITIALIZING:
Copy link
Member

Choose a reason for hiding this comment

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

With the _wait_for_submitting() there's no need for this check as well

return self._job_id
while not self._id:
# job is initializing and hasn't gotten a id yet.
time.sleep(0.1)
Copy link
Member

Choose a reason for hiding this comment

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

_wait_for_submitting() :)

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',
# qobj = self._q_job.qobj
Copy link
Member

Choose a reason for hiding this comment

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

nit: Remove commented code. There's no need for this having git :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

logger.info('status = %s (%d seconds)', api_result['status'], timer)
api_result = self._api.get_job(job_id)
logger.info('status = %s (%d seconds)', api_result['status'],
elapsed_time)

if 'status' not in api_result:
Copy link
Member

Choose a reason for hiding this comment

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

Ok, seems like there is the possibility of a job having the status of CANCELLED, so it has not results. We should be treating this status in the conditionals as well.

# id)
api_result = None
start_time = time.time()
while not (self.done or self.cancelled or self.exception or
Copy link
Member

Choose a reason for hiding this comment

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

This is sending at least one more request.

'result': api_result['status']}
return Result(job_result, qobj)
return Result(job_result)

if self.cancelled:
Copy link
Member

Choose a reason for hiding this comment

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

This will send another request to the server, I would just check against api_result['status']. There's no way a job can be canceled by the user while is calling result, as this is a blocking call.

'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',
Copy link
Member

Choose a reason for hiding this comment

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

nit: Remove commented code (or leave the trace there :))

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):
Copy link
Member

Choose a reason for hiding this comment

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

I would make a test for a Job with multiple circuits.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

test_run_simulator does this

Copy link
Member

Choose a reason for hiding this comment

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

Ok!

ewinston added 8 commits June 15, 2018 15:41
This bug would cause the method to miss first "limit" number of jobs.
Previously retrieving jobs without metadata would raise an exception.
Now a warning is issued.

Also, the property "creationDate" was added to ibmq jobs.
Previously backend.jobs() more api calls than it needed.

Also, ibmqjob.job_id is now ibmqjob.id
This commit incorporates improvements from PR Qiskit#515 by @delapuente to handling job status such that it makes fewer api calls.

Co-authored-by: [email protected]
ewinston added 5 commits June 15, 2018 15:58
for status of RUNNING or QUEUED or DONE the status_msg would indicate "initializing"
when calling job.status for a server with a suspended queue the command
would hang.
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,
Copy link
Member

Choose a reason for hiding this comment

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

With the latest version of IBMQuantumExperience package (1.9.4), we can pass a filter to the get_jobs(), so we can filter by backend name and job status, making this whole algorithm way simpler and reducing the potential huge number of requests to 1.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's great! Thanks for getting that through.

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.
@ewinston ewinston removed this from the 0.6 milestone Jun 19, 2018
@atilag
Copy link
Member

atilag commented Jun 19, 2018

After talking with @ewinston we are going to leave jobs() filtering for a future release, when the related server bug is fixed. In the mean time, we will only filter jobs by backend name.

@atilag atilag requested a review from delapuente as a code owner June 19, 2018 21:11
@diego-plan9 diego-plan9 added this to the 0.6 milestone Jun 20, 2018
@atilag atilag merged commit 572db84 into Qiskit:master Jun 20, 2018
lia-approves pushed a commit to edasgupta/qiskit-terra that referenced this pull request Jul 30, 2019
* add jobs() method to retrieve all jobs from a backend
* job cancellation improvements for hubs
* add retrieve_job() method for ibmq backends
* add circuit names to non-qobj ibmq job submission
* remove qobj from Result
* add temporary hack for bit reordering
* allow retrieving old jobs without metadata
* property "creationDate" was added to ibmq jobs.
@ewinston ewinston deleted the backend_jobs branch April 6, 2021 16:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants