In Qiskit we have an interface for backends and jobs that will be useful for running circuits and extending to third-party backends. In this tutorial, we will review the core components of Qiskit’s base backend framework, using the IBM Q provider as an example.
The interface has three parts: the provider, the backend, and the job:
The IBMQ Provider is an entity that provides access to a group of different backends (for example, backends available through IBM Q Experience or IBM Q Network).
The IBMQ provider inherits from BaseProvider and implements the methods:
backends()
: returns all backend objects known to the provider.get_backend(name)
: returns the named backend.The IBM Q provider has some extra functions for handling administrative tasks. The credentials can be saved to disk or used in a session and never saved.
enable_account(token, url)
: enable an account in the current sessiondisable_accounts(**kwargs)
: disable one or more accounts from current sessionsave_account(token, url)
: save an account to diskdelete_accounts(**kwargs)
: delete the account or accounts from diskload_accounts(**kwargs)
: load previously-saved account or accounts into sessionactive_accounts()
: list all accounts active in this sessionstored_accounts()
: list all accounts saved to disk
In [1]:
from qiskit import IBMQ
IBMQ.backends()
Out[1]:
Here we see that there are no backends. This is because no accounts have been loaded.
Let's start fresh and delete any accounts on disk. If no accounts are on disk this will error
In [2]:
IBMQ.delete_accounts()
verify that there are no accounts stored now
In [3]:
IBMQ.stored_accounts()
Out[3]:
To demonstrate that we can load multiple accounts using the IBMQ provider, here we use two files Qconfig_IBMQ_experience.py
and Qconfig_IBMQ_network.py
, which are just containers of the APItoken
and URL
.
APItoken = 'MY_API_TOKEN'
URL = 'THE_URL'
For the IBM Q experience the URL is not needed and is loaded by default in enable_account
and save_account
. For the IBM Q Network the url is found on your q-console account page. We don't recommend saving and using files like this. We recommend just inputting the APItoken
and URL
directly into the methods enable_account
and save_account
.
In [4]:
import Qconfig_IBMQ_network
import Qconfig_IBMQ_experience
To enable an account (useful for one-off use, or if you don't want to save to disk)
In [5]:
IBMQ.enable_account(Qconfig_IBMQ_experience.APItoken)
To see that accounts which are enabled for use
In [6]:
# uncomment to print to screen (it will show your token and url)
# IBMQ.active_accounts()
and backends which are available
In [7]:
IBMQ.backends()
Out[7]:
Disable that account (so we go back to no accounts active)
In [8]:
IBMQ.disable_accounts(token=Qconfig_IBMQ_experience.APItoken)
Now no backends are available
In [9]:
IBMQ.backends()
Out[9]:
Save two accounts: a public (IBM Q experience) and a premium (IBM Q network)
In [10]:
IBMQ.save_account(Qconfig_IBMQ_experience.APItoken)
IBMQ.save_account(Qconfig_IBMQ_network.APItoken, Qconfig_IBMQ_network.url)
Now they should show up as present on disk
In [11]:
# uncomment to print to screen (it will show your token and url)
# IBMQ.stored_accounts()
but no account active in current session yet
In [12]:
IBMQ.active_accounts()
Out[12]:
so IBMQ can't see any backends yet
In [13]:
IBMQ.backends()
Out[13]:
now load up every account stored to disk
In [14]:
IBMQ.load_accounts()
backends from two different accounts available for use
In [15]:
IBMQ.backends()
Out[15]:
now if you want to work with backends of a single account, you can do so via account filtering
In [16]:
IBMQ.backends(hub='ibm-q-internal')
Out[16]:
but you can also just disable account in the current session
In [17]:
IBMQ.disable_accounts(hub='ibm-q-internal')
so now only one account is active
In [18]:
# uncomment to print to screen (it will show your token and url)
# IBMQ.active_accounts()
and only that account's backends are available
In [19]:
IBMQ.backends()
Out[19]:
or from the start use the filtering to just load up that account you're interested in
In [20]:
IBMQ.disable_accounts()
IBMQ.load_accounts(hub=None)
IBMQ.backends()
Out[20]:
You may also optionally filter the set of returned backends, by passing arguments that query the backend's configuration
or status
or properties
. The filters are passed by conditions and for more general filters you can make advanced functions using the lambda function.
As a first example: only return currently operational devices
In [21]:
IBMQ.backends(operational=True, simulator=False)
Out[21]:
only return backends that are real devices, have more than 10 qubits and are operational
In [22]:
IBMQ.backends(filters=lambda x: x.configuration()['n_qubits'] <= 5 and
not x.configuration()['simulator'] and x.status()['operational']==True)
Out[22]:
Filter: show the least busy device (in terms of pending jobs in the queue)
In [23]:
from qiskit.backends.ibmq import least_busy
small_devices = IBMQ.backends(filters=lambda x: x.configuration()['n_qubits'] <= 5 and
not x.configuration()['simulator'])
least_busy(small_devices)
Out[23]:
The above filters can be combined as desired.
If you just want to get an instance of a particular backend, you can use the get_backend()
method.
In [24]:
IBMQ.get_backend('ibmq_16_melbourne')
Out[24]:
Backends represent either a simulator or a real quantum computer, and are responsible for running quantum circuits and returning results. They have a run
method which takes in a qobj
as input, which is a quantum object and the result of the compilation process, and returns a BaseJob object. This object allows asynchronous running of jobs for retrieving results from a backend when the job is completed.
At a minimum, backends use the following methods, inherited from BaseBackend:
provider
- returns the provider of the backendname()
- gets the name of the backend.status()
- gets the status of the backend.configuration()
- gets the configuration of the backend.properties()
- gets the properties of the backend.run()
- runs a qobj on the backend.For remote backends they must support the additional
jobs()
- returns a list of previous jobs executed by this user on this backend.retrieve_job()
- returns a job by a job_id.In future updates they will introduce the following commands
defaults()
- gives a data structure of typical default parameters.schema()
- gets a schema for the backendThere are some IBMQ only functions
hub
- returns the IBMQ hub for this backend.group
- returns the IBMQ group for this backend.project
- returns the IBMQ project for this backend.
In [25]:
backend = least_busy(small_devices)
Let's start with the backend.provider
, which returns a provider object
In [26]:
backend.provider
Out[26]:
Next is the name()
, which returns the name of the backend
In [27]:
backend.name()
Out[27]:
Next let's look at the status()
:
operational lets you know that the backend is taking jobs
pending_jobs lets you know how many jobs are in the queue
In [28]:
backend.status()
Out[28]:
The next is configuration()
In [29]:
backend.configuration()
Out[29]:
The next is properties()
method
In [30]:
backend.properties()
Out[30]:
The next is hub
, group
, and project
. For the IBM Q experience these will return None
In [31]:
backend.hub
In [32]:
backend.group
In [33]:
backend.project
To see your last 5 jobs ran on the backend use the jobs()
method of that backend
In [34]:
for ran_job in backend.jobs(limit=5):
print(str(ran_job.job_id()) + " " + str(ran_job.status()))
Then the job can be retreived using retrieve_job(job_id())
method
In [35]:
job = backend.retrieve_job(ran_job.job_id())
Job instances can be thought of as the “ticket” for a submitted job. They find out the execution’s state at a given point in time (for example, if the job is queued, running, or has failed) and also allow control over the job. They have the following methods:
status()
- returns the status of the job.backend()
- returns the backend the job was run on.job_id()
- gets the job_id.cancel()
- cancels the job.result()
- gets the results from the circuit run.IBMQ only functions
creation_date()
- gives the date at which the job was created.queue_position()
- gives the position of the job in the queue.error_message()
- gives the error message of failed jobs.Let's start with the status()
. This returns the job status and a message
In [36]:
job.status()
Out[36]:
To get a backend object from the job use the backend()
method
In [37]:
backend_temp = job.backend()
backend_temp
Out[37]:
To get the job_id use the job_id()
method
In [38]:
job.job_id()
Out[38]:
To get the result from the job use the result()
method
In [39]:
result = job.result()
counts = result.get_counts()
print(counts)
If you want to check the creation date use creation_date()
In [40]:
job.creation_date()
Out[40]:
Let's make an active example
In [41]:
from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister
from qiskit import compile
In [42]:
qr = QuantumRegister(3)
cr = ClassicalRegister(3)
circuit = QuantumCircuit(qr, cr)
circuit.x(qr[0])
circuit.x(qr[1])
circuit.ccx(qr[0], qr[1], qr[2])
circuit.cx(qr[0], qr[1])
circuit.measure(qr, cr)
Out[42]:
To compile this circuit for the backend use the compile function. It will make a qobj (quantum object) that can be run on the backend using the run(qobj)
method.
In [43]:
qobj = compile(circuit, backend=backend, shots=1024)
job = backend.run(qobj)
The status of this job can be checked with the status()
method
In [44]:
job.status()
Out[44]:
If you made a mistake and need to cancel the job use the cancel()
method.
In [45]:
import time
#time.sleep(10)
job.cancel()
Out[45]:
The status()
will show that the job cancelled.
In [46]:
job.status()
Out[46]:
To rerun the job and set up a loop to check the status and queue position you can use the queue_position()
method.
In [47]:
job = backend.run(qobj)
In [48]:
lapse = 0
interval = 60
while job.status().name != 'DONE':
print('Status @ {} seconds'.format(interval * lapse))
print(job.status())
print(job.queue_position())
time.sleep(interval)
lapse += 1
print(job.status())
result = job.result()
In [49]:
counts = result.get_counts()
print(counts)
In [ ]: