Qiskit IBM Quantum Experience account

The IBM Quantum Experience is accessed through Qiskit via the account interface. This notebook gives an overview of how to use this account to access the systems and simulators available on the 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 IBM Quantum Experience (IQX) 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 IQX account:

[1]:
from qiskit import IBMQ

# IBMQ.save_account(TOKEN)
IBMQ.load_account() # Load account from disk
IBMQ.providers()    # List all available providers
[1]:
[<AccountProvider for IBMQ(hub='ibm-q', group='open', project='main')>,
 <AccountProvider for IBMQ(hub='ibm-q-example', group='test-private', project='test-project')>]

where we have assumed that the user has stored their IQX account information locally ahead of time using IBMQ.save_account(TOKEN). TOKEN here is the API token you obtain from your IQX 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 IQX 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_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_rome') 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 lets 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_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_rome') 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 5 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('ibmqx2') 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 <https://qiskit.org/documentation/stubs/qiskit.providers.ibmq.IBMQBackend.html#qiskit.providers.ibmq.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 <https://qiskit.org/documentation/stubs/qiskit.providers.ibmq.IBMQBackend.html#qiskit.providers.ibmq.IBMQBackend>`__ documentation for a complete list of methods.

Lets 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]:
'ibmqx2'
[12]:
backend.status()
[12]:
<qiskit.providers.models.backendstatus.BackendStatus at 0x7f2f2257a150>

Here we see the name of the backend, 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]:
QasmBackendConfiguration(allow_object_storage=True, allow_q_object=True, backend_name='ibmqx2', backend_version='2.0.6', basis_gates=['u1', 'u2', 'u3', 'cx', 'id'], conditional=False, coupling_map=[[0, 1], [0, 2], [1, 0], [1, 2], [2, 0], [2, 1], [2, 3], [2, 4], [3, 2], [3, 4], [4, 2], [4, 3]], credits_required=True, description='5 qubit device', gates=[GateConfig(id, [], gate id q { U(0,0,0) q; }, [[0], [1], [2], [3], [4]]), GateConfig(u1, ['lambda'], gate u1(lambda) q { U(0,0,lambda) q; }, [[0], [1], [2], [3], [4]]), GateConfig(u2, ['phi', 'lambda'], gate u2(phi,lambda) q { U(pi/2,phi,lambda) q; }, [[0], [1], [2], [3], [4]]), GateConfig(u3, ['theta', 'phi', 'lambda'], gate u3(theta,phi,lambda) q { U(theta,phi,lambda) q; }, [[0], [1], [2], [3], [4]]), GateConfig(cx, [], gate cx q1,q2 { CX q1,q2; }, [[0, 1], [0, 2], [1, 0], [1, 2], [2, 0], [2, 1], [2, 3], [2, 4], [3, 2], [3, 4], [4, 2], [4, 3]])], local=False, max_experiments=75, max_shots=8192, meas_map=[[0, 1, 2, 3, 4]], memory=True, n_qubits=5, n_registers=1, online_date=datetime.datetime(2017, 1, 24, 5, 0, tzinfo=tzutc()), open_pulse=False, quantum_volume=8, sample_name='sparrow', simulator=False, url='None')

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 65.08681549583736 microseconds
  - T2 time of 78.74490689224909 microseconds
  - U2 gate error of 0.0006597266543849755
  - U2 gate duration of 35.555555555555564 nanoseconds
  - resonant frequency of 5.2863972381246604 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 <https://qiskit.org/documentation/stubs/qiskit.providers.ibmq.IBMQBackendService.html#qiskit.providers.ibmq.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()))
5ef5c85c51390900147cdedb JobStatus.DONE
5ef5c859fb7fab0014a7ee40 JobStatus.DONE
5ef5c85984b1b700123757f5 JobStatus.DONE
5ef5c84bddb65000120793ad JobStatus.DONE
5ef5c849a2591200133f36eb 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 <https://qiskit.org/documentation/stubs/qiskit.providers.ibmq.job.IBMQJob.html#qiskit.providers.ibmq.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 <https://qiskit.org/documentation/stubs/qiskit.providers.ibmq.job.IBMQJob.html#qiskit.providers.ibmq.job.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 0x7f2ecdf93b10>

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]:
'5ef5c86984b1b700123757f8'

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()
/usr/local/lib/python3.7/site-packages/ipykernel_launcher.py:1: UserWarning: The creation date is returned in local time now, rather than UTC.
  """Entry point for launching an IPython kernel.
[29]:
datetime.datetime(2020, 6, 26, 10, 5, 29, 531000, 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 <https://qiskit.org/documentation/stubs/qiskit.providers.ibmq.managed.IBMQJobManager.html#qiskit.providers.ibmq.managed.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 <https://qiskit.org/documentation/stubs/qiskit.providers.ibmq.managed.ManagedJobSet.html>`__ 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: 9923226cbf4149ca95524153984a739e-15931659421924984
        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: 1c41c57e1853417db802431072eab3ca-15931659422224004
        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 <https://qiskit.org/documentation/stubs/qiskit.providers.ibmq.managed.ManagedResults.html>`__ 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 retrive 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: 1c41c57e1853417db802431072eab3ca-15931659422224004
        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: 5ef5c877fb7fab0014a7ee42
    name: bar_0_
    status: job has successfully run
  experiments: 10-19
    job index: 1
    job ID: 5ef5c8797855410012c58413
    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!")
[37]:
import qiskit.tools.jupyter
%qiskit_version_table

Version Information

Qiskit SoftwareVersion
Qiskit0.19.6
Terra0.14.2
Aer0.5.2
Ignis0.3.3
Aqua0.7.3
IBM Q Provider0.7.2
System information
Python3.7.6 (default, Feb 26 2020, 15:34:58) [GCC 8.3.0]
OSLinux
CPUs1
Memory (Gb)29.93264389038086
Fri Jun 26 10:05:58 2020 UTC
[ ]: