Qiskit IBMQ account

This is an overview of how to use your Qiskit IBMQ account to access the systems and simulators available on IBM Quantum Experience. Additionally, it will detail how to interact with, and retrieve, jobs associated with executing circuits on these backends.

In this tutorial, we will review the core components of Qiskit’s base backend framework, using the IBMQ account as an example.

The interface has four main components: the account, providers, backends, and jobs:

  • account: Gives access to one or more ‘providers’ based on the account’s permissions.

  • provider: Provides access to quantum devices and simulators, collectively called ‘backends’, and additional services tailored to a specific backend instance.

  • backend: A quantum device or simulator capable of running quantum circuits or pulse schedules.

  • job: A local reference to a collection of quantum circuits or pulse schedules submitted to a given backend.

The Account

The Qiskit IBMQ account object is the local reference for accessing your IBM Quantum Experience account, and all of the providers, backends, etc., that are available to you.

The IBMQ account has functions for handling administrative tasks. The credentials can be saved to disk, or used in a session and never saved.

  • enable_account(TOKEN, HUB, GROUP, PROJECT): Enable your account in the current session and optionally specify a default provider to return.

  • save_account(TOKEN, HUB, GROUP, PROJECT): Save your account to disk for future use, and optionally specify a default provider to return when loading your account.

  • load_account(): Load account using stored credentials.

  • disable_account(): Disable your account in the current session.

  • stored_account(): List the account stored to disk.

  • active_account(): List the account currently in the session.

  • delete_account(): Delete the saved account from disk.

A provider offers access to quantum systems, simulators, and additional services. To see all the providers available with your IBM Quantum Experience account:

[1]:
from qiskit import IBMQ

# IBMQ.save_account(TOKEN)
IBMQ.load_account() # Load account from disk
IBMQ.providers()    # List all available providers
/usr/local/lib/python3.7/site-packages/traitlets/traitlets.py:2945: FutureWarning: --rc={'figure.dpi': 96} for dict-traits is deprecated in traitlets 5.0. You can pass --rc <key=value> ... multiple times to add items to a dict.
  FutureWarning,
/usr/local/lib/python3.7/site-packages/qiskit/providers/ibmq/ibmqfactory.py:192: UserWarning: Timestamps in IBMQ backend properties, jobs, and job results are all now in local time instead of UTC.
  warnings.warn('Timestamps in IBMQ backend properties, jobs, and job results '
[1]:
[<AccountProvider for IBMQ(hub='ibm-q', group='open', project='main')>]

where we have assumed that the user has stored their IBM Quantum Experience account information locally ahead of time using IBMQ.save_account(TOKEN). TOKEN here is the API token you obtain from your IBM Quantum Experience account.

Note: The use of provider instances is the default way of retrieving backends from Qiskit 0.11 onwards - if you have been using earlier versions of Qiskit, check the “Updating from previous versions” section for more detailed instructions on updating and using the different options.

The above example shows two different providers. All IBMQ providers are specified by a hub, group, and project. The provider given by hub='ibm-q', group='open', project='main' is the provider that gives access to the public IBM Quantum devices available to all IBM Quantum Experience users. The second is an example of a provider that is only unlocked for a specific set of users. Members of the IBM Q Network may see one or more providers (with names different than those shown above) depending on the access level granted to them.

To access a given provider one should use the get_provider() method of the IBMQ account, filtering by hub, group, or project:

[2]:
IBMQ.get_provider(hub='ibm-q')
[2]:
<AccountProvider for IBMQ(hub='ibm-q', group='open', project='main')>
[3]:
IBMQ.get_provider(group='open')
[3]:
<AccountProvider for IBMQ(hub='ibm-q', group='open', project='main')>

Finally, as a convenience, calling IBMQ.load_account() or IBMQ.enable_account() will return the default public provider instance <AccountProvider for IBMQ(hub='ibm-q', group='open', project='main')>.

The Provider

All providers inherit from qiskit.providers.BaseProvider and implement the methods:

  • backends(): Returns all backend objects known to the provider.

  • get_backend(NAME): Returns the named backend.

Providers associated via your IBMQ account provide access to a collection of different backends, such as those available through the open IBM Quantum Experience or the IBM Q Network.

Using the public provider instance from above:

[4]:
provider = IBMQ.get_provider(hub='ibm-q')
provider.backends()
[4]:
[<IBMQSimulator('ibmq_qasm_simulator') from IBMQ(hub='ibm-q', group='open', project='main')>,
 <IBMQBackend('ibmqx2') from IBMQ(hub='ibm-q', group='open', project='main')>,
 <IBMQBackend('ibmq_16_melbourne') from IBMQ(hub='ibm-q', group='open', project='main')>,
 <IBMQBackend('ibmq_vigo') from IBMQ(hub='ibm-q', group='open', project='main')>,
 <IBMQBackend('ibmq_ourense') from IBMQ(hub='ibm-q', group='open', project='main')>,
 <IBMQBackend('ibmq_valencia') from IBMQ(hub='ibm-q', group='open', project='main')>,
 <IBMQBackend('ibmq_london') from IBMQ(hub='ibm-q', group='open', project='main')>,
 <IBMQBackend('ibmq_burlington') from IBMQ(hub='ibm-q', group='open', project='main')>,
 <IBMQBackend('ibmq_essex') from IBMQ(hub='ibm-q', group='open', project='main')>,
 <IBMQBackend('ibmq_armonk') from IBMQ(hub='ibm-q', group='open', project='main')>,
 <IBMQBackend('ibmq_santiago') from IBMQ(hub='ibm-q', group='open', project='main')>]

Selecting a backend is done by name using the get_backend(NAME) method:

[5]:
backend = provider.get_backend('ibmq_vigo')
backend
[5]:
<IBMQBackend('ibmq_vigo') from IBMQ(hub='ibm-q', group='open', project='main')>

Filtering the Backends

You may also optionally filter the set of returned backends, by passing arguments that query the backend’s configuration, status, or properties. The filters are passed by conditions and, for more general filters, you can make advanced functions using a lambda function.

As a first example, let’s return only those backends that are real quantum devices, and that are currently operational:

[6]:
provider.backends(simulator=False, operational=True)
[6]:
[<IBMQBackend('ibmqx2') from IBMQ(hub='ibm-q', group='open', project='main')>,
 <IBMQBackend('ibmq_16_melbourne') from IBMQ(hub='ibm-q', group='open', project='main')>,
 <IBMQBackend('ibmq_vigo') from IBMQ(hub='ibm-q', group='open', project='main')>,
 <IBMQBackend('ibmq_ourense') from IBMQ(hub='ibm-q', group='open', project='main')>,
 <IBMQBackend('ibmq_valencia') from IBMQ(hub='ibm-q', group='open', project='main')>,
 <IBMQBackend('ibmq_armonk') from IBMQ(hub='ibm-q', group='open', project='main')>,
 <IBMQBackend('ibmq_santiago') from IBMQ(hub='ibm-q', group='open', project='main')>]

Or, only those backends that are real devices, have more than 10 qubits, and are operational:

[7]:
provider.backends(filters=lambda x: x.configuration().n_qubits >= 10
                                    and not x.configuration().simulator
                                    and x.status().operational==True)
[7]:
[<IBMQBackend('ibmq_16_melbourne') from IBMQ(hub='ibm-q', group='open', project='main')>]

Lastly, show the least-busy five-qubit device (in terms of the number of jobs pending in the queue):

[8]:
from qiskit.providers.ibmq import least_busy

small_devices = provider.backends(filters=lambda x: x.configuration().n_qubits == 5
                                   and not x.configuration().simulator)
least_busy(small_devices)
[8]:
<IBMQBackend('ibmq_vigo') from IBMQ(hub='ibm-q', group='open', project='main')>

The above filters can be combined as desired.

Backends

Backends represent either a simulator or a real quantum computer, and are responsible for running quantum circuits and/or pulse schedules and returning results. They have a run method which takes in a qobj as input, the Qiskit API serialization format, 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 qiskit.providers.BaseBackend:

  • provider(): Returns the provider of the backend.

  • name(): Returns the name of the backend.

  • status(): Returns the current status of the backend.

  • configuration(): Returns the backend configuration.

  • properties(): Returns the backend properties.

  • run(QOBJ, **kwargs): Runs a qobj on the backend.

The qiskit-ibmq-provider’s implementation of BaseBackend is IBMQBackend. It accepts additional parameters to the run() method:

  • job_name: Custom name to be assigned to the job.

  • job_share_level: Allows sharing the job at different level.

  • job_tags: Tags to be assigned to the job.

And supports additional methods:

  • jobs(): Returns a list of previous jobs executed on this backend through the current provider instance.

  • retrieve_job(JOB_ID): Returns a job by its job ID.

  • defaults(): Gives a data structure of typical default parameters, if applicable.

  • job_limit(): Returns the job limit for the backend.

  • remaining_jobs_count(): Returns the number of remaining jobs that could be submitted to the backend.

  • active_jobs(): Returns a list of unfinished jobs.

Refer to the IBMQBackend documentation for a complete list of methods.

Let’s load up the least-busy backend from the small_devices filtered above:

[9]:
backend = least_busy(small_devices)

Some examples using the different methods:

[10]:
backend.provider()
[10]:
<AccountProvider for IBMQ(hub='ibm-q', group='open', project='main')>
[11]:
backend.name()
[11]:
'ibmq_vigo'
[12]:
backend.status()
[12]:
<qiskit.providers.models.backendstatus.BackendStatus at 0x7f1d071b6610>

Here we see the name of the backend and the software version it is running, along with its operational status, number of jobs pending in the backends queue, and a more detailed status message.

Next we look at the backend configuration and properties:

[13]:
backend.configuration()
[13]:
<qiskit.providers.models.backendconfiguration.QasmBackendConfiguration at 0x7f1d071b0e50>

The backend configuration provides some useful information via its attributes, such as basis_gates, coupling_map, max_experiments, max_shots, quantum_volume, and simulator.

The backend properties contain data that was measured and reported. Let’s see what kind of information is reported for qubit 0.

[14]:
props = backend.properties()

def describe_qubit(qubit, properties):
    """Print a string describing some of reported properties of the given qubit."""

    # Conversion factors from standard SI units
    us = 1e6
    ns = 1e9
    GHz = 1e-9

    print("Qubit {0} has a \n"
          "  - T1 time of {1} microseconds\n"
          "  - T2 time of {2} microseconds\n"
          "  - U2 gate error of {3}\n"
          "  - U2 gate duration of {4} nanoseconds\n"
          "  - resonant frequency of {5} GHz".format(
              qubit,
              properties.t1(qubit) * us,
              properties.t2(qubit) * us,
              properties.gate_error('u2', qubit),
              properties.gate_length('u2', qubit) * ns,
              properties.frequency(qubit) * GHz))

describe_qubit(0, props)
Qubit 0 has a
  - T1 time of 133.46024423362778 microseconds
  - T2 time of 20.36446297254618 microseconds
  - U2 gate error of 0.00033524610614269585
  - U2 gate duration of 35.55555555555556 nanoseconds
  - resonant frequency of 4.7966093576612705 GHz

To see the last five jobs run on this backend:

[15]:
ran_job = None
for ran_job in backend.jobs(limit=5):
    print(str(ran_job.job_id()) + " " + str(ran_job.status()))

A job can be retrieved using the retrieve_job(JOB_ID) method:

[16]:
if ran_job is not None:
    job = backend.retrieve_job(ran_job.job_id())

Backend Service

qiskit-ibmq-provider version 0.4 (Qiskit version 0.14) introduced a new class IBMQBackendService. It provides generic backend related services for a provider without requiring a particular backend as input. The main methods it supports are:

  • jobs(): Returns a list of previously submitted jobs through the current provider instance.

  • retrieve_job(JOB_ID): Returns a job by its job ID.

The backend service is defined as the backends attribute of a provider. All of the backends available to this provider are also attributes of the backend service, allowing the backend names to be autocompleted:

[17]:
provider.backends.ibmq_vigo
[17]:
<IBMQBackend('ibmq_vigo') from IBMQ(hub='ibm-q', group='open', project='main')>

To see the last five jobs submitted through this provider, regardless of which backend they ran on:

[18]:
for ran_job in provider.backends.jobs(limit=5):
    print(str(ran_job.job_id()) + " " + str(ran_job.status()))
5f749b87ab72f5001224b9b7 JobStatus.DONE
5f749b86274b8d0013bcd189 JobStatus.DONE
5f749b86496f4b0013a4c684 JobStatus.DONE
5f749b771269070012ef3464 JobStatus.DONE
5f749b75496f4b0013a4c682 JobStatus.DONE

To retrieve a particular job:

[19]:
if ran_job is not None:
    job = provider.backends.retrieve_job(ran_job.job_id())

Jobs

Job instances can be thought of as the “ticket” for a submitted job. They find out the execution 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.

Some of the methods that are only available to “IBM Q Job”(IBMQJob) include:

  • creation_date(): Gives the date at which the job was created.

  • queue_info(): Returns queue information for this job, including queue position, estimated start and end time, and dynamic priorities for the hub, group, and project.

  • error_message(): The error message of failed jobs, if any.

  • name(): Returns the name assigned to this job.

  • properties(): Returns the backend properties for this job.

  • time_per_step(): Returns the time spent for each step (job creation, validation, etc).

Refer to the IBMQJob documentation for a complete list of methods.

Now some examples. Let’s start with submitting a job:

[20]:
from qiskit import *
from qiskit.compiler import transpile, assemble
[21]:
backend = provider.get_backend('ibmq_qasm_simulator')
[22]:
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)
[22]:
<qiskit.circuit.instructionset.InstructionSet at 0x7f1d07135190>

To pass this circuit to the backend, we must first map it onto the backend, package it, and send to the device. This is all done for you by the execute function:

[23]:
job = execute(circuit, backend)

Alternatively, you can map the circuit yourself using the transpile function, package it using assemble, and then send it from the backend instance itself:

[24]:
mapped_circuit = transpile(circuit, backend=backend)
qobj = assemble(mapped_circuit, backend=backend, shots=1024)
job = backend.run(qobj)

The status() method returns the job status and a message:

[25]:
job.status()
[25]:
<JobStatus.VALIDATING: 'job is being validated'>

To get a backend object from the job, use the backend() method:

[26]:
backend_temp = job.backend()
backend_temp
[26]:
<IBMQSimulator('ibmq_qasm_simulator') from IBMQ(hub='ibm-q', group='open', project='main')>

To get the job_id use the job_id() method:

[27]:
job.job_id()
[27]:
'5f74bc11ab72f5001224bb30'

To get the result from the job, use the result() method:

[28]:
result = job.result()
counts = result.get_counts()
print(counts)
{'101': 1024}

If you want to check the creation date, use creation_date():

[29]:
job.creation_date()
[29]:
datetime.datetime(2020, 9, 30, 17, 10, 41, 471000, tzinfo=tzlocal())

Job Manager

The Job Manager is another convenience function provided by qiskit-ibmq-provider, available in version 0.4 (Qiskit version 0.14) and up. It splits experiments into multiple jobs based on backend restrictions. When the jobs are finished, it collects and presents the results in a unified view.

IBMQJobManager has the following methods: - run(): Execute a set of circuits or pulse schedules. - report(): Return a report on the statuses of all jobs managed by this manager. - job_sets(): Return a list of managed job sets matching the specified filtering.

You can run multiple sets of experiments by invoking job_manager.run() multiple times, and each call will return a ManagedJobSet instance. The ManagedJobSet methods allow you to apply operations on this set of jobs as a whole and are similiar to those of the Job class: - statuses(): Return the status of each job. - cancel(): Cancel all jobs in this set. - results(): Return the results of the jobs. - error_messages(): Provide details about job failures. - jobs(): Return a list of submitted jobs. - report(): Return a report on current job statuses.

Now let’s see some examples. Say you want to submit a bunch of circuits to a backend:

[30]:
from qiskit.providers.ibmq.managed import IBMQJobManager

sim_backend = provider.get_backend('ibmq_qasm_simulator')
circs = transpile([circuit]*20, backend=sim_backend)

# Submit them all to the backend
job_manager = IBMQJobManager()
job_set = job_manager.run(circs, backend=sim_backend, name='foo')

You can use the report() method to inquire the statuses of the jobs:

[31]:
print(job_set.report())
Job set name: foo
          ID: 8a420dfa7e214df6b2037ee8682cce28-16014858573266501
        tags: []
Summary report:
       Total jobs: 1
  Successful jobs: 0
      Failed jobs: 0
   Cancelled jobs: 0
     Running jobs: 0
     Pending jobs: 1

Detail report:
  experiments: 0-19
    job index: 0
    status: job is being initialized

job_set above is not very interesting, as it only contains a single job. To force multiple jobs without making this tutorial too slow, we can use the max_experiments_per_job parameter:

[32]:
# Restrict 10 experiments per job.
job_set_multiple = job_manager.run(circs, backend=sim_backend, name='bar', max_experiments_per_job=10)
[33]:
print(job_set_multiple.report())
Job set name: bar
          ID: 27fce9c0faa140c2becb953331fe071d-16014858573519075
        tags: []
Summary report:
       Total jobs: 2
  Successful jobs: 0
      Failed jobs: 0
   Cancelled jobs: 0
     Running jobs: 0
     Pending jobs: 2

Detail report:
  experiments: 0-9
    job index: 0
    status: job is being initialized
  experiments: 10-19
    job index: 1
    status: job is being initialized

As you can see, job_set_multiple contains multiple jobs. To collect the results of all jobs, you can use the results() method, which returns a ManagedResults instance. ManagedResults supports the same methods as Result, which can be used to retrieve the ouputs of individual experiments:

[34]:
results = job_set_multiple.results()
print("This is the histogram data for experiment 5: {}".format(results.get_counts(5)))
print("This is the histogram data for experiment 15: {}".format(results.get_counts(15)))
This is the histogram data for experiment 5: {'101': 1024}
This is the histogram data for experiment 15: {'101': 1024}

Similar to a job, each job set is assigned a unique ID. The job set ID along with the provider object can be used to retrieve a previously submitted job set:

[35]:
job_set_id = job_set_multiple.job_set_id()
retrieved_job_set = job_manager.retrieve_job_set(job_set_id=job_set_id, provider=provider)
print(retrieved_job_set.report())
Job set name: bar
          ID: 27fce9c0faa140c2becb953331fe071d-16014858573519075
        tags: []
Summary report:
       Total jobs: 2
  Successful jobs: 2
      Failed jobs: 0
   Cancelled jobs: 0
     Running jobs: 0
     Pending jobs: 0

Detail report:
  experiments: 0-9
    job index: 0
    job ID: 5f74bc21274b8d0013bcd2f7
    name: bar_0_
    status: job has successfully run
  experiments: 10-19
    job index: 1
    job ID: 5f74bc224589620013287441
    name: bar_1_
    status: job has successfully run

If you want to manipulate the jobs within a job set directly, you can use the jobs() method to retrive them. Note that an entry is None if that particular job could not be submitted:

[36]:
jobs = job_set_multiple.jobs()  # Get a list of all jobs in the set.
job0 = jobs[0]
if job0 is None:
    print("Job0 submit failed!")

Back to the User Account and Services table of contents