Get started with primitives
The steps in this topic describes how to set up primitives, explore the options you can use to configure them, and invoke them in a program.
To ensure faster and more efficient results, as of 1 March 2024, circuits and observables need to be transformed to only use instructions supported by the QPU (referred to as instruction set architecture (ISA) circuits and observables) before being submitted to the Qiskit Runtime primitives. See the transpilation documentation for instructions to transform circuits. Due to this change, the primitives will no longer perform layout or routing operations. Consequently, transpilation options referring to those tasks will no longer have any effect. By default, all primitives except Sampler V2 still optimize the input circuits. To bypass all optimization, set optimization_level=0
.
Exception: When you initialize the Qiskit Runtime Service with the Q-CTRL channel strategy (example below), abstract circuits are still supported.
service = QiskitRuntimeService(channel="ibm_cloud", channel_strategy="q-ctrl")
While this documentation uses the primitives from Qiskit Runtime, which allow you to use IBM® backends, the primitives can be run on any provider by using the backend primitives instead. Additionally, you can use the reference primitives to run on a local statevector simulator. See Exact simulation with Qiskit primitives for details.
Get started with Estimator
1. Initialize the account
Because Qiskit Runtime Estimator
is a managed service, you first need to initialize your account. You can then select the QPU you want to use to calculate the expectation value.
Follow the steps in the Install and set up topic if you don't already have an account.
from qiskit_ibm_runtime import QiskitRuntimeService, EstimatorV2 as Estimator
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
estimator = Estimator(mode=backend)
2. Create a circuit and an observable
You need at least one circuit and one observable as inputs to the Estimator
primitive.
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.quantum_info import SparsePauliOp, random_hermitian
n_qubits = 127
mat = np.real(random_hermitian(n_qubits, seed=1234))
circuit = IQP(mat)
observable = SparsePauliOp("Z" * n_qubits)
print(f">>> Observable: {observable.paulis}")
Output
>>> Observable: ['ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ...']
The circuit and observable need to be transformed to only use instructions supported by the QPU (referred to as instruction set architecture (ISA) circuits). We'll use the transpiler to do this.
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_circuit = pm.run(circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
3. Initialize Qiskit Runtime Estimator
When you initialize the Estimator
, use the mode
parameter to specify the mode you want it to run in. Possible values are batch
, session
, or backend
objects for batch, session, and job execution mode, respectively. For more information, see Introduction to Qiskit Runtime execution modes.
To use the Estimator V2, import the EstimatorV2 class. The Estimator class still refers to the version 1 Estimator, for backwards compatibility.
from qiskit_ibm_runtime import EstimatorV2 as Estimator
estimator = Estimator(mode=backend)
from qiskit_ibm_runtime import Estimator
estimator = Estimator(backend=backend)
4. Invoke the Estimator and get results
Next, invoke the run()
method to calculate expectation values for the input circuits and observables.
For Estimator V2, the circuit, observable, and optional parameter value sets are input as primitive unified bloc (PUB) tuples.
job = estimator.run([(isa_circuit, isa_observable)])
print(f">>> Job ID: {job.job_id()}")
print(f">>> Job Status: {job.status()}")
Output
>>> Job ID: 8874203f-f5a0-4491-b093-3f29a1adcae4
>>> Job Status: JobStatus.RUNNING
result = job.result()
print(f">>> {result}")
print(f" > Expectation value: {result[0].data.evs}")
print(f" > Metadata: {result[0].metadata}")
Output
>>> PrimitiveResult([PubResult(data=DataBin<>(evs=0.013671875, stds=0.0156235396179561), metadata={'target_precision': 0.015625})], metadata={})
> Expectation value: 0.013671875
> Metadata: {'target_precision': 0.015625}
job = estimator.run(isa_circuit, isa_observable)
print(f">>> Job ID: {job.job_id()}")
print(f">>> Job Status: {job.status()}")
result = job.result()
print(f">>> {result}")
print(f" > Expectation value: {result.values[0]}")
print(f" > Metadata: {result.metadata[0]}")
Get started with Sampler
1. Initialize the account
Because Qiskit Runtime Sampler
is a managed service, you first need to initialize your account. You can then select the QPU you want to use to calculate the expectation value.
Follow the steps in the Install and set up topic if you don't already have an account set up.
from qiskit_ibm_runtime import QiskitRuntimeService
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
2. Create a circuit
You need at least one circuit as the input to the Sampler
primitive.
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.quantum_info import random_hermitian
n_qubits = 127
mat = np.real(random_hermitian(n_qubits, seed=1234))
circuit = IQP(mat)
circuit.measure_all()
Use the transpiler to get an ISA circuit.
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_circuit = pm.run(circuit)
3. Initialize the Qiskit Runtime Sampler
When you initialize the Sampler
, use the mode
parameter to specify the mode you want it to run in. Possible values are batch
, session
, or backend
objects for batch, session, and job execution mode, respectively. For more information, see Introduction to Qiskit Runtime execution modes.
To use the Sampler V2, import the SamplerV2 class. The Sampler class still refers to the version 1 Sampler, for backward compatibility.
from qiskit_ibm_runtime import SamplerV2 as Sampler
sampler = Sampler(mode=backend)
from qiskit_ibm_runtime import Sampler
sampler = Sampler(backend=backend)
4. Invoke the Sampler and get results
Next, invoke the run()
method to generate the output.
For Sampler V2, the circuit and optional parameter value sets are input as primitive unified bloc (PUB) tuples.
job = sampler.run([isa_circuit])
print(f">>> Job ID: {job.job_id()}")
print(f">>> Job Status: {job.status()}")
Output
>>> Job ID: 58223448-5100-4dec-a47a-942fb30edced
>>> Job Status: JobStatus.RUNNING
result = job.result()
# Get results for the first (and only) PUB
pub_result = result[0]
print(f"Counts for the meas output register: {pub_result.data.meas.get_counts()}")
Output
Counts for the meas output register: {'0111': 50, '0000': 243, '0001': 101, '0101': 93, '0100': 188, '0011': 128, '1011': 22, '0110': 13, '1100': 10, '1000': 24, '0010': 29, '1010': 26, '1101': 20, '1110': 45, '1001': 16, '1111': 16}
job = sampler.run(isa_circuit)
print(f">>> Job ID: {job.job_id()}")
print(f">>> Job Status: {job.status()}")
result = job.result()
print(f">>> {result}")
print(f" > Quasi-probability distribution: {result.quasi_dists[0]}")
print(f" > Metadata: {result.metadata[0]}")
Get started with the backend primitives
Unlike provider-specific primitives, backend primitives are generic implementations that can be used with an arbitrary
backend
object, as long as it implements the Backend
interface.
- The
Sampler
primitive can be run with any provider by usingqiskit.primitives.BackendSamplerV2
or the deprecated V1 implementation,qiskit.primitives.BackendSampler
. - The
Estimator
primitive can be run with any provider by usingqiskit.primitives.BackendEstimatorV2
, or the deprecated V1 implementation,qiskit.primitives.BackendEstimator
.
Some providers implement primitives natively. See the Qiskit Ecosystem page(opens in a new tab) for details.
Example: BackendEstimator
from qiskit.primitives import BackendEstimatorV2
from <some_qiskit_provider> import QiskitProvider
provider = QiskitProvider()
backend = provider.get_backend('backend_name')
estimator = BackendEstimatorV2(backend)
from qiskit.primitives import BackendEstimator
from <some_qiskit_provider> import QiskitProvider
provider = QiskitProvider()
backend = provider.get_backend('backend_name')
estimator = BackendEstimator(backend)
Example: BackendSampler
from qiskit.primitives import BackendSamplerV2
from <some_qiskit_provider> import QiskitProvider
provider = QiskitProvider()
backend = provider.get_backend('backend_name')
sampler = BackendSamplerV2(backend)
from qiskit.primitives import BackendSampler
from <some_qiskit_provider> import QiskitProvider
provider = QiskitProvider()
backend = provider.get_backend('backend_name')
sampler = BackendSampler(backend)
Similarities and differences between backend and Runtime primitives (V2)
-
The inputs to
qiskit.primitives.BackendSamplerV2
andqiskit.primitives.BackendEstimatorV2
follow the same PUB format as the primitives in Qiskit Runtime, so do the outputs, see Primitive inputs and outputs for details. However, there can be differences in the fields of the returned metadata. -
The
qiskit.primitives.BackendEstimatorV2
class offers no measurement or gate error mitigation implementations out-of-the-box, as backend primitives are designed to run locally in the user's machine. -
The
qiskit.primitives.BackendSamplerV2
class requires a backend that supports thememory
option. -
The backend primitives V2 interfaces expose custom
Sampler
andEstimator
Options
that are different from the Runtime implementations.
Next steps
- Learn how to test locally before running on quantum computers.
- Review detailed primitives examples.
- Read Migrate to V2 primitives.
- Practice with primitives by working through the Cost function lesson(opens in a new tab) in IBM Quantum Learning.
- Learn how to transpile locally in the Transpile section.
- Try the Submit pre-transpiled circuits(opens in a new tab) tutorial.
- Learn how to use the primitive options.
- View the API for Sampler and Estimator options.