Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ The format is based on `Keep a Changelog`_.

Added
-----
- Retreive IBM Q jobs from server (#563, #585).
- Add German introductory documentation (``doc/de``) (#592).
- Add ``unregister()`` for removing previously registered providers (#584).
- Adding backend filtering by least busy (#575).
Expand Down
66 changes: 63 additions & 3 deletions qiskit/backends/ibmq/ibmqbackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from qiskit._util import _camel_case_to_snake_case
from qiskit.backends import BaseBackend
from qiskit.backends.ibmq.ibmqjob import IBMQJob
from qiskit.backends import JobStatus

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -141,18 +142,72 @@ def status(self):
"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
def jobs(self, limit=50, skip=0, status=None, db_filter=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 or str): only get jobs with this status,
where status is e.g. `JobStatus.RUNNING` or `'RUNNING'`
db_filter (dict): `loopback-based filter
<https://loopback.io/doc/en/lb2/Querying-data.html>`_.
This is an interface to a database ``where`` filter. Some
examples of its usage are:

Filter last five jobs with errors::

job_list = backend.jobs(limit=5, status=JobStatus.ERROR)

Filter last five jobs with counts=1024, and counts for
states ``00`` and ``11`` each exceeding 400::

cnts_filter = {'shots': 1024,
'qasms.result.data.counts.00': {'gt': 400},
'qasms.result.data.counts.11': {'gt': 400}}
job_list = backend.jobs(limit=5, db_filter=cnts_filter)

Filter last five jobs from 30 days ago::

past_date = datetime.datetime.now() - datetime.timedelta(days=30)
date_filter = {'creationDate': {'lt': past_date.isoformat()}}
job_list = backend.jobs(limit=5, db_filter=date_filter)

Returns:
list(IBMQJob): list of IBMQJob instances

Raises:
IBMQBackendValueError: status keyword value unrecognized
"""
backend_name = self.configuration['name']
api_filter = {}
if status:
if isinstance(status, str):
status = JobStatus[status]
if status == JobStatus.RUNNING:
this_filter = {'status': 'RUNNING',
'infoQueue': {'exists': False}}
elif status == JobStatus.QUEUED:
this_filter = {'status': 'RUNNING',
'infoQueue.status': 'PENDING_IN_QUEUE'}
elif status == JobStatus.CANCELLED:
this_filter = {'status': 'CANCELLED'}
elif status == JobStatus.DONE:
this_filter = {'status': 'COMPLETED'}
elif status == JobStatus.ERROR:
this_filter = {'status': {'regexp': '^ERROR'}}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

... and same as above, this is not used anywhere.

else:
raise IBMQBackendValueError('unrecongized value for "status" keyword '
'in job filter')
api_filter.update(this_filter)
if db_filter:
# filter ignores backend_name filter so we need to set it
api_filter['backend.name'] = backend_name
# status takes precendence over db_filter for same keys
api_filter = {**db_filter, **api_filter}
job_info_list = self._api.get_jobs(limit=limit, skip=skip,
backend=backend_name)
backend=backend_name,
filter=api_filter)
job_list = []
for job_info in job_info_list:
is_device = not bool(self._configuration.get('simulator'))
Expand Down Expand Up @@ -183,3 +238,8 @@ def retrieve_job(self, job_id):
class IBMQBackendError(QISKitError):
"""IBM Q Backend Errors"""
pass


class IBMQBackendValueError(IBMQBackendError, ValueError):
""" Value errors thrown within IBMQBackend """
pass
41 changes: 41 additions & 0 deletions test/python/test_ibmqjob.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import time
import unittest
from concurrent import futures
import datetime

import numpy
from scipy.stats import chi2_contingency
Expand Down Expand Up @@ -291,6 +292,46 @@ def test_retrieve_job_error(self):
backend = _least_busy(backends)
self.assertRaises(IBMQBackendError, backend.retrieve_job, 'BAD_JOB_ID')

def test_get_jobs_filter_job_status(self):
backends = self._provider.available_backends()
backend = _least_busy(backends)
job_list = backend.jobs(limit=5, skip=0, status=JobStatus.DONE)
self.log.info('found %s matching jobs', len(job_list))
for i, job in enumerate(job_list):
self.log.info('match #%d: %s', i, job.result()._result['status'])
self.assertTrue(job.status['status'] == JobStatus.DONE)

def test_get_jobs_filter_counts(self):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What about first sending the jobs before testing them? I'm only worried about what happens if there's no such jobs in the server, so the test always passes and is not testing anything. (... maybe am I being to much skeptical? :))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I agree. I think I did this because of the trouble with assuring a job status for testing. It's a good argument for mocking the server.

# TODO: consider generalizing backend name
backend = self._provider.get_backend('ibmq_qasm_simulator')
my_filter = {'backend.name': 'ibmq_qasm_simulator',
'shots': 1024,
'qasms.result.data.counts.00': {'lt': 500}}
self.log.info('searching for at most 5 jobs with 1024 shots, a count '
'for "00" of < 500, on the ibmq_qasm_simulator backend')
job_list = backend.jobs(limit=5, skip=0, db_filter=my_filter)
self.log.info('found %s matching jobs', len(job_list))
for i, job in enumerate(job_list):
self.log.info('match #%d', i)
result = job.result()
self.assertTrue(any(cresult['data']['counts']['00'] < 500
for cresult in result._result['result']))
for circuit_name in result.get_names():
self.log.info('\tcircuit_name: %s', circuit_name)
counts = result.get_counts(circuit_name)
self.log.info('\t%s', str(counts))

def test_get_jobs_filter_date(self):
backends = self._provider.available_backends()
backend = _least_busy(backends)
past_day_30 = datetime.datetime.now() - datetime.timedelta(days=30)
my_filter = {'creationDate': {'lt': past_day_30.isoformat()}}
job_list = backend.jobs(limit=5, db_filter=my_filter)
self.log.info('found %s matching jobs', len(job_list))
for i, job in enumerate(job_list):
self.log.info('match #%d: %s', i, job.creation_date)
self.assertTrue(job.creation_date < past_day_30.isoformat())


if __name__ == '__main__':
unittest.main(verbosity=2)