Skip to main contentIBM Quantum Documentation

Migrate to using Qiskit Runtime primitives

This guide describes key patterns of behavior and use cases with code examples to help you migrate code from the legacy qiskit-ibmq-provider package to use the Qiskit Runtime primitives.


Overview

There are two methods for accessing IBM Quantum systems. First, the qiskit-ibm-provider package provides the backend.run() interface, allowing direct access to IBM Quantum systems with no pre- or post-processing involved. This level of access is suitable for those users who want precise control over circuit execution and result processing. This level of access is needed for those at the level of kernel developer who are looking to develop, for example, circuit optimization routines or error mitigation techniques, or who want to characterize quantum systems.

In contrast, Qiskit Runtime is designed to streamline algorithm and application construction by removing the need for users to understand technical hardware and low-level software details. Advanced processing techniques for error suppression and mitigation are automatically applied, giving users high-fidelity results without the burden of having to code these routines themselves. Sessions within Qiskit Runtime allow users to run iterative algorithm circuits back to back, or batch collections of circuits without having to re-queue each job. This results in more efficient quantum processor use and reduces the time users spend running complex computations.

Note
backend.run is required for running dynamic circuits.

Primitives are the recommended tool to write quantum algorithms, as they encapsulate common device queries seen in application packages and allow for managed performance through the Qiskit Runtime service. However, if your algorithm requires more granular information, such as pre-shot measurements, the primitives might not provide the desired abstraction level.

The Qiskit Runtime primitives implement the reference Sampler and Estimator interfaces found in qiskit.primitives. These interfaces let you switch between primitive implementations with minimal code changes. Different primitive implementations can be found in the qiskit, qiskit_aer, and qiskit_ibm_runtime libraries. Each implementation serves a specific purpose:

  • The primitives in qiskit can perform local state vector simulations - useful for quickly prototyping algorithms.
  • The primitives in qiskit_aer give access to the local Aer simulators for tasks such as noisy simulation.
  • The primitives in qiskit_ibm_runtime provide access to cloud simulators and real hardware through the Qiskit Runtime service. They include exclusive features such as built-in circuit optimization and error mitigation support.
Note

The only primitives that provide access to the Qiskit Runtime service are those imported from qiskit_ibm_runtime (Qiskit Runtime Primitives).

When migrating, the key to writing an equivalent algorithm using primitives is to first identify what is the minimal unit of information your algorithm is based on:

  • If it uses an expectation value, you will need an Estimator.
  • If it uses a probability distribution (from sampling the device), you will need a Sampler.

After determining which primitive to use, identify where the algorithm accesses the backend. Look for the call to backend.run(). Next, you will replace this call with the respective primitive call, as shown in the following examples.

This guide is for algorithm developers who need to refactor algorithms to use primitives instead of backend.run(). See examples here:

The following topics are use cases with code migration examples:


FAQs

Users might have the following questions when planning to migrate their code to Qiskit Runtime:

How do the Qiskit Runtime primitives differ from backend.run?

There are two methods for accessing IBM Quantum systems. First, the qiskit-ibm-provider package provides the backend.run() interface, allowing direct access to IBM Quantum systems with no pre- or post-processing involved. This level of access is suitable for those users who want precise control over circuit execution and result processing. This level of access is needed for those looking to work at the level Kernel developer developing, for example, circuit optimization routines, error mitigation techniques, or characterizing quantum systems.

In contrast, Qiskit Runtime is designed to streamline algorithm and application construction by removing the need for users to understand technical hardware and low-level software details. Advanced processing techniques for error suppression and mitigation are automatically applied, giving users high-fidelity results without the burden of having to code these routines themselves. The inclusion of sessions within Qiskit Runtime allows users to run iterative algorithm circuits back to back, or batch collections of circuits without having to re-queue each job. This results in more efficient quantum processor utilization and reduces the total amount of time users spend running complex computations.

Which channel should I use?

After deciding to use Qiskit Runtime primitives, the user must determine whether to access Qiskit Runtime through IBM Cloud or IBM Quantum Platform. Some information that might help you decide includes:

  • The available plans:
  • The use case requirements:
    • IBM Quantum Platform offers a visual circuit composer (Quantum Composer) and a Jupyter Notebook environment (Quantum Lab).
    • IBM Cloud offers a cloud native service that is ideal if users need to integrate quantum capabilities with other cloud services.
How do I set up my channel?

After deciding which channel to use to interact with Qiskit Runtime, you can get set up on either platform by following the steps in Install and set up.

Should I modify the Qiskit Terra algorithms?

As of v0.22, Qiskit Terra algorithms (opens in a new tab) use Qiskit Runtime primitives. Thus, there is no need for users to modify amplitude estimators or any other Qiskit Terra algorithms.

Which primitive should I use?

When choosing which primitive to use, you first need to understand whether the algorithm uses a quasi-probability distribution sampled from a quantum state (a list of quasi-probabilities), or an expectation value of a certain observable with respect to a quantum state (a real number).

A probability distribution is often of interest in optimization problems that return a classical bit string, encoding a certain solution to a problem at hand. In these cases, you might be interested in finding a bit string that corresponds to a ket value with the largest probability of being measured from a quantum state, for example.

An expectation value of an observable could be the target quantity in scenarios where knowing a quantum state is not relevant. This often occurs in optimization problems or chemistry applications. For example, when trying to discover the extremal energy of a system.


Migrate setup from qiskit-ibmq-provider

This guide describes how to migrate code from the legacy IBMQ provider qiskit-ibmq-provider package to use Qiskit Runtime qiskit-ibm-runtime. This guide includes instructions to migrate legacy runtime programs to the new syntax. However, the ability to use custom uploaded programs is pending deprecation, so these should be migrated to use primitives instead.

Changes in Class name and location

The classes related to Qiskit Runtime that used to be included in qiskit-ibmq-provider are now part of qiskit-ibm-runtime. Before, the provider used to populate the qiskit.providers.ibmq.runtime namespace with objects for Qiskit Runtime. These now live in the qiskit_ibm_runtime module.

The module from which the classes are imported has changed. The following table contains example access patterns in qiskit.providers.ibmq.runtime and their new form in qiskit_ibm_runtime:

class in qiskit-ibmq-providerclass in qiskit-ibm-runtimeNotes
qiskit.providers.ibmq.runtime.IBMRuntimeServiceqiskit_ibm_runtime.QiskitRuntimeServiceIBMRuntimeService class was removed from qiskit-ibm-runtime 0.6 and replaced by the new class in qiskit-ibm-runtime.
qiskit.providers.ibmq.runtime.RuntimeJobqiskit_ibm_runtime.RuntimeJob
qiskit.providers.ibmq.runtime.RuntimeProgramqiskit_ibm_runtime.RuntimeProgram
qiskit.providers.ibmq.runtime.UserMessengerqiskit_ibm_runtime.program.UserMessengerNew location: qiskit_ibm_runtime.program
qiskit.providers.ibmq.runtime.ProgramBackendqiskit_ibm_runtime.program.ProgramBackendNew location: qiskit_ibm_runtime.program
qiskit.providers.ibmq.runtime.ResultDecoderqiskit_ibm_runtime.program.ResultDecoderNew location: qiskit_ibm_runtime.program
qiskit.providers.ibmq.runtime.RuntimeEncoderqiskit_ibm_runtime.RuntimeEncoder
qiskit.providers.ibmq.runtime.RuntimeDecoderqiskit_ibm_runtime.RuntimeDecoder
qiskit.providers.ibmq.runtime.ParameterNamespaceqiskit_ibm_runtime.ParameterNamespace
qiskit.providers.ibmq.runtime.RuntimeOptionsqiskit_ibm_runtime.RuntimeOptions

Import path

The import path has changed as follows:

Legacy

from qiskit import IBMQ

Updated

from qiskit_ibm_runtime import QiskitRuntimeService

Save and load accounts

Use the updated code to work with accounts.

Legacy: Save accounts

IBMQ.save_account("<IQX_TOKEN>", overwrite=True)

Updated: Save accounts The new syntax accepts credentials for Qiskit Runtime on IBM Cloud or IBM Quantum Platform. For more information on retrieving account credentials, see Install and set up.

# IBM Cloud channel
 
QiskitRuntimeService.save_account(channel="ibm_cloud", token="<IBM Cloud API key>", instance="<IBM Cloud CRN>", overwrite=True)
 
# IBM Quantum channel; set to default 
 
QiskitRuntimeService.save_account(channel="ibm_quantum", token="<IQP_TOKEN>", overwrite=True, default=true)

Updated: Name saved credentials You can now name your saved credentials and load the credentials by name.

Example:

# Save different accounts for open and premium access
 
QiskitRuntimeService.save_account(channel="ibm_quantum", token="<IQX_TOKEN>", instance="h1/g1/p1", name="premium")
QiskitRuntimeService.save_account(channel="ibm_quantum", token="<IQX_TOKEN>", instance="h2/g2/p2", name="open")
 
# Load the "open" credentials
 
service = QiskitRuntimeService(name="open")

Legacy: Load accounts

IBMQ.load_account()

Updated: Load accounts

The new syntax combines the functionality from load_account() and get_provider() in one statement. The channel input parameter is optional. If multiple accounts have been saved in one device and no channel is provided, the default is "ibm_cloud".

# To access saved credentials for the IBM cloud channel
service = QiskitRuntimeService(channel="ibm_cloud")
 
# To access saved credentials for the IBM quantum channel
service = QiskitRuntimeService(channel="ibm_quantum")

Channel selection (get a provider)

Use the updated code to select a channel.

Legacy

provider = IBMQ.get_provider(project="my_project", group="my_group", hub="my_hub")

Updated

The new syntax combines the functionality from load_account() and get_provider() in one statement. When using the ibm_quantum channel, the hub, group, and project are specified through the new instance keyword.

# To access saved credentials for the IBM quantum channel and select an instance
service = QiskitRuntimeService(channel="ibm_quantum", instance="my_hub/my_group/my_project")

Get the backend

Use the updated code to view backends.

Legacy

provider = IBMQ.get_provider(hub="h1", group="g1", project="p1")
backend = provider.get_backend("ibm_backend")

Updated

# You can specify the instance in service.backend() instead of initializing a new service
backend = service.backend("ibm_backend", instance="h1/g1/p1")

Upload, view, or delete custom prototype programs

To work with custom programs, replace provider.runtime with service.

Note

This function is pending deprecation.

Legacy

# Printing existing programs
provider.runtime.pprint_programs()
 
# Deleting custom program
provider.runtime.delete_program("my_program") # Substitute "my_program" with your program ID
 
# Uploading custom program
program_id = provider.runtime.upload_program(
            data=program_data,
            metadata=program_json
            )

Updated

# Printing existing programs
service.pprint_programs()
 
# Deleting custom program
service.delete_program("my_program") # Substitute "my_program" with your program ID
 
# Uploading custom program
program_id = service.upload_program(
            data=program_data,
            metadata=program_json
            )

Run prototype programs

To run prototype programs, replace provider.runtime with service.

Note

This function is pending deprecation.

Legacy

program_inputs = {"iterations": 3}
options = {"backend_name": backend.name()}
job = provider.runtime.run(program_id="hello-world",
                           options=options,
                           inputs=program_inputs
                          )
print(f"job id: {job.job_id()}")
result = job.result()
print(result)

Updated

program_inputs = {"iterations": 3}
options = {"backend": ""}
job = service.run(program_id="hello-world",
                  options=options,
                  inputs=program_inputs
                  )
print(f"job id: {job.job_id()}")
result = job.result()
print(result)

Parametrized circuits with primitives

Parametrized circuits are a commonly used tool for quantum algorithm design. Because backend.run() did not accept parametrized circuits, the parameter binding step had to be integrated in the algorithm workflow. The primitives can perform the parameter binding step internally, which results in a simplification of the algorithm-side logic.

The following example summarizes the new workflow for managing parametrized circuits.

Example

Let's define a parametrized circuit:

from qiskit.circuit import QuantumCircuit, ParameterVector
 
n = 3
thetas = ParameterVector('θ',n)
 
qc = QuantumCircuit(n, 1)
qc.h(0)
 
for i in range(n-1):
    qc.cx(i, i+1)
 
for i,t in enumerate(thetas):
    qc.rz(t, i)
 
for i in reversed(range(n-1)):
    qc.cx(i, i+1)
 
qc.h(0)
qc.measure(0, 0)
 
qc.draw()

We want to assign the following parameter values to the circuit:

import numpy as np
theta_values = [np.pi/2, np.pi/2, np.pi/2]

Legacy

Previously, the parameter values had to be bound to their respective circuit parameters prior to calling backend.run().

from qiskit import Aer
 
bound_circuit = qc.bind_parameters(theta_values)
bound_circuit.draw()
 
backend = Aer.get_backend('aer_simulator')
job = backend.run(bound_circuit)
counts = job.result().get_counts()
print(counts)

Primitives

Now, the primitives take in parametrized circuits directly, together with the parameter values, and the parameter assignment operation can be performed more efficiently on the server side of the primitive.

This feature is particularly interesting when working with iterative algorithms because the parametrized circuit remains unchanged between calls while the parameter values change. The primitives can transpile once and then cache the unbound circuit, using classical resources more efficiently. Moreover, only the updated parameters are transferred to the cloud, saving additional bandwidth.

from qiskit.primitives import Sampler
 
sampler = Sampler()
job = sampler.run(qc, theta_values)
result = job.result().quasi_dists
print(result)

Algorithm tuning

One of the advantages of the primitives is that they abstract away the circuit execution setup so that algorithm developers can focus on the pure algorithmic components. However, sometimes, to get the most out of an algorithm, you might want to tune certain primitive options. For details, see Advanced runtime options.


Next steps

Was this page helpful?