Skill Tracer
Skills › Scientific Agent Skills › qiskit › primitives.md

primitives.md

422 lines · skills/qiskit/references/primitives.md at 5f2bc03

Open on GitHub
1# V2 Primitives and PUBs
2
3Cloud authentication, QPU, session, and batch snippets are illustrative and were not executed in this refresh. Local evidence is listed in [sources.md](sources.md).
4Qiskit primitives standardize two core tasks:
5
6- **Sampler** executes measured circuits and returns shot-resolved classical data.
7- **Estimator** computes expectation values of observables for states prepared by circuits.
8
9Use V2 interfaces. Their unit of work is a **Primitive Unified Bloc (PUB)**.

V2 Primitives and PUBs

Cloud authentication, QPU, session, and batch snippets are illustrative and were not executed in this refresh. Local evidence is listed in sources.md. Qiskit primitives standardize two core tasks:

  • Sampler executes measured circuits and returns shot-resolved classical data.
  • Estimator computes expectation values of observables for states prepared by circuits.

Use V2 interfaces. Their unit of work is a Primitive Unified Bloc (PUB).

Raw lines 1 to 10
11## Implementations
13| Implementation | Use |
14|---|---|
15| `StatevectorSampler` | Exact statevector evolution plus finite-shot sampling on the local CPU |
16| `StatevectorEstimator` | Local statevector expectation values |
17| Aer `SamplerV2` / `EstimatorV2` | High-performance and noisy local simulation |
18| Runtime client-side `Sampler` / `Estimator` (V2 API) | IBM QPUs and IBM Runtime services |
19| `BackendSamplerV2` / `BackendEstimatorV2` | Adapt a `BackendV2` that lacks native primitives |
21The V2 `run()` structure is shared, but options are implementation-specific. Do not pass Runtime resilience options to statevector or Aer primitives.

Implementations

ImplementationUse
StatevectorSamplerExact statevector evolution plus finite-shot sampling on the local CPU
StatevectorEstimatorLocal statevector expectation values
Aer SamplerV2 / EstimatorV2High-performance and noisy local simulation
Runtime client-side Sampler / Estimator (V2 API)IBM QPUs and IBM Runtime services
BackendSamplerV2 / BackendEstimatorV2Adapt a BackendV2 that lacks native primitives

The V2 run() structure is shared, but options are implementation-specific. Do not pass Runtime resilience options to statevector or Aer primitives.

Raw lines 11 to 22
23## Sampler PUBs
25A Sampler PUB contains:
271. One circuit with measurements.
282. Optional parameter values.
293. Optional per-PUB shots: `(circuit, values, shots)`.
31The core SDK supports per-PUB shots. Runtime 0.50 client-side primitives require equal shot budgets across PUBs in one call; split unequal budgets into separate jobs.
33Pass shots at the `run()` level unless a specific current API requires otherwise.

Sampler PUBs

A Sampler PUB contains:

  1. One circuit with measurements.
  2. Optional parameter values.
  3. Optional per-PUB shots: (circuit, values, shots).

The core SDK supports per-PUB shots. Runtime 0.50 client-side primitives require equal shot budgets across PUBs in one call; split unequal budgets into separate jobs.

Pass shots at the run() level unless a specific current API requires otherwise.

Raw lines 23 to 34
35### One Circuit
37```python
38from qiskit import QuantumCircuit
39from qiskit.primitives import StatevectorSampler
41circuit = QuantumCircuit(2)
42circuit.h(0)
43circuit.cx(0, 1)
44circuit.measure_all()
46sampler = StatevectorSampler(seed=11)
47primitive_result = sampler.run([circuit], shots=1024).result()
48pub_result = primitive_result[0]
50counts = pub_result.data.meas.get_counts()
51bitstrings = pub_result.data.meas.get_bitstrings()
52metadata = pub_result.metadata
53```
55`meas` is the name of the classical register created by `measure_all()`.

One Circuit

from qiskit import QuantumCircuit
from qiskit.primitives import StatevectorSampler

circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()

sampler = StatevectorSampler(seed=11)
primitive_result = sampler.run([circuit], shots=1024).result()
pub_result = primitive_result[0]

counts = pub_result.data.meas.get_counts()
bitstrings = pub_result.data.meas.get_bitstrings()
metadata = pub_result.metadata

meas is the name of the classical register created by measure_all().

Raw lines 35 to 56
57### Multiple Circuits
59```python
60circuit_x = QuantumCircuit(1)
61circuit_x.x(0)
62circuit_x.measure_all()
64circuit_h = QuantumCircuit(1)
65circuit_h.h(0)
66circuit_h.measure_all()
68result = sampler.run([circuit_x, circuit_h], shots=512).result()
69counts_x = result[0].data.meas.get_counts()
70counts_h = result[1].data.meas.get_counts()
71```
73Each input PUB produces one `PubResult`.

Multiple Circuits

circuit_x = QuantumCircuit(1)
circuit_x.x(0)
circuit_x.measure_all()

circuit_h = QuantumCircuit(1)
circuit_h.h(0)
circuit_h.measure_all()

result = sampler.run([circuit_x, circuit_h], shots=512).result()
counts_x = result[0].data.meas.get_counts()
counts_h = result[1].data.meas.get_counts()

Each input PUB produces one PubResult.

Raw lines 57 to 74
75### Parameter Sweep
77```python
78import numpy as np
79from qiskit import QuantumCircuit
80from qiskit.circuit import Parameter
81from qiskit.primitives import StatevectorSampler
83theta = Parameter("theta")
84circuit = QuantumCircuit(1)
85circuit.ry(theta, 0)
86circuit.measure_all()
88values = [[0.0], [np.pi / 2], [np.pi]]
89sampler = StatevectorSampler(seed=11)
90pub_result = sampler.run(
91 [(circuit, values)],
92 shots=256,
93).result()[0]
95for index, value in enumerate(values):
96 counts = pub_result.data.meas.get_counts(index)
97 print(value[0], counts)
98```
100For a shaped `BitArray`, pass an index to `get_counts()` when results must remain separated by parameter point. Calling it without an index can aggregate over axes.

Parameter Sweep

import numpy as np
from qiskit import QuantumCircuit
from qiskit.circuit import Parameter
from qiskit.primitives import StatevectorSampler

theta = Parameter("theta")
circuit = QuantumCircuit(1)
circuit.ry(theta, 0)
circuit.measure_all()

values = [[0.0], [np.pi / 2], [np.pi]]
sampler = StatevectorSampler(seed=11)
pub_result = sampler.run(
    [(circuit, values)],
    shots=256,
).result()[0]

for index, value in enumerate(values):
    counts = pub_result.data.meas.get_counts(index)
    print(value[0], counts)

For a shaped BitArray, pass an index to get_counts() when results must remain separated by parameter point. Calling it without an index can aggregate over axes.

Raw lines 75 to 101
102## Multiple Classical Registers
104Sampler data fields use register names:
106```python
107from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister
108from qiskit.primitives import StatevectorSampler
110qubits = QuantumRegister(2, "q")
111left = ClassicalRegister(1, "left")
112right = ClassicalRegister(1, "right")
113circuit = QuantumCircuit(qubits, left, right)
114circuit.h(qubits[0])
115circuit.cx(qubits[0], qubits[1])
116circuit.measure(qubits[0], left[0])
117circuit.measure(qubits[1], right[0])
119pub_result = StatevectorSampler(seed=11).run(
120 [circuit],
121 shots=256,
122).result()[0]
124left_counts = pub_result.data.left.get_counts()
125right_counts = pub_result.data.right.get_counts()
126```
128Do not assume every result has `.data.meas`. Inspect `circuit.cregs` or `pub_result.data`. Register counts are marginals. Preserve their paired shots with:
130```python
131joint_counts = pub_result.join_data(["left", "right"]).get_counts()
132# Qiskit 2.5.2 BitArray: first listed register occupies least-significant bits.
133assert set(joint_counts) <= {"00", "11"}
134```
136Do not multiply marginal frequencies to estimate cross-register correlations. In executed Qiskit 2.5.2, `left=1`, `right=0` with `join_data(["left", "right"])` yields `"01"`. The current API docstring claims the opposite displayed order; the released implementation calls `BitArray.concatenate_bits`, which puts the first input in the least-significant bits. Keep an asymmetric known-state test when upgrading; Bell `00`/`11` alone cannot detect reversal.

Multiple Classical Registers

Sampler data fields use register names:

from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister
from qiskit.primitives import StatevectorSampler

qubits = QuantumRegister(2, "q")
left = ClassicalRegister(1, "left")
right = ClassicalRegister(1, "right")
circuit = QuantumCircuit(qubits, left, right)
circuit.h(qubits[0])
circuit.cx(qubits[0], qubits[1])
circuit.measure(qubits[0], left[0])
circuit.measure(qubits[1], right[0])

pub_result = StatevectorSampler(seed=11).run(
    [circuit],
    shots=256,
).result()[0]

left_counts = pub_result.data.left.get_counts()
right_counts = pub_result.data.right.get_counts()

Do not assume every result has .data.meas. Inspect circuit.cregs or pub_result.data. Register counts are marginals. Preserve their paired shots with:

joint_counts = pub_result.join_data(["left", "right"]).get_counts()
# Qiskit 2.5.2 BitArray: first listed register occupies least-significant bits.
assert set(joint_counts) <= {"00", "11"}

Do not multiply marginal frequencies to estimate cross-register correlations. In executed Qiskit 2.5.2, left=1, right=0 with join_data(["left", "right"]) yields "01". The current API docstring claims the opposite displayed order; the released implementation calls BitArray.concatenate_bits, which puts the first input in the least-significant bits. Keep an asymmetric known-state test when upgrading; Bell 00/11 alone cannot detect reversal.

Raw lines 102 to 137
138## Estimator PUBs
140An Estimator PUB contains:
1421. One circuit, normally without final measurements.
1432. One observable or an array of observables.
1443. Optional parameter values.
1454. Optional per-PUB precision: `(circuit, observables, values, precision)`.
147Use one precision per Runtime call; heterogeneous budgets are implementation-specific.

Estimator PUBs

An Estimator PUB contains:

  1. One circuit, normally without final measurements.
  2. One observable or an array of observables.
  3. Optional parameter values.
  4. Optional per-PUB precision: (circuit, observables, values, precision).

Use one precision per Runtime call; heterogeneous budgets are implementation-specific.

Raw lines 138 to 148
149### One Circuit and Observable
151```python
152from qiskit import QuantumCircuit
153from qiskit.primitives import StatevectorEstimator
154from qiskit.quantum_info import SparsePauliOp
156circuit = QuantumCircuit(2)
157circuit.h(0)
158circuit.cx(0, 1)
160observable = SparsePauliOp.from_list(
161 [
162 ("ZZ", 1.0),
163 ("XX", 0.5),
164 ]
167estimator = StatevectorEstimator()
168pub_result = estimator.run(
169 [(circuit, observable)]
170).result()[0]
172expectation_values = pub_result.data.evs
173standard_deviations = pub_result.data.stds
174```
176`SparsePauliOp` labels are little-endian with respect to qubit indices: the rightmost label character acts on qubit 0.

One Circuit and Observable

from qiskit import QuantumCircuit
from qiskit.primitives import StatevectorEstimator
from qiskit.quantum_info import SparsePauliOp

circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)

observable = SparsePauliOp.from_list(
    [
        ("ZZ", 1.0),
        ("XX", 0.5),
    ]
)

estimator = StatevectorEstimator()
pub_result = estimator.run(
    [(circuit, observable)]
).result()[0]

expectation_values = pub_result.data.evs
standard_deviations = pub_result.data.stds

SparsePauliOp labels are little-endian with respect to qubit indices: the rightmost label character acts on qubit 0.

Raw lines 149 to 177
178### Parameter Sweep
180```python
181import numpy as np
182from qiskit import QuantumCircuit
183from qiskit.circuit import Parameter
184from qiskit.primitives import StatevectorEstimator
185from qiskit.quantum_info import SparsePauliOp
187theta = Parameter("theta")
188circuit = QuantumCircuit(2)
189circuit.ry(theta, 0)
190circuit.cx(0, 1)
192observable = SparsePauliOp.from_list([("ZI", 1.0), ("XX", 0.5)])
193values = [[0.0], [np.pi / 4], [np.pi / 2]]
195pub_result = StatevectorEstimator().run(
196 [(circuit, observable, values)]
197).result()[0]
199print(pub_result.data.evs)
200```
202The final axis of `values` corresponds to `list(circuit.parameters)`.

Parameter Sweep

import numpy as np
from qiskit import QuantumCircuit
from qiskit.circuit import Parameter
from qiskit.primitives import StatevectorEstimator
from qiskit.quantum_info import SparsePauliOp

theta = Parameter("theta")
circuit = QuantumCircuit(2)
circuit.ry(theta, 0)
circuit.cx(0, 1)

observable = SparsePauliOp.from_list([("ZI", 1.0), ("XX", 0.5)])
values = [[0.0], [np.pi / 4], [np.pi / 2]]

pub_result = StatevectorEstimator().run(
    [(circuit, observable, values)]
).result()[0]

print(pub_result.data.evs)

The final axis of values corresponds to list(circuit.parameters).

Raw lines 178 to 203
204### Multiple Observables
206```python
207observables = [
208 [SparsePauliOp.from_list([("ZZ", 1.0)])],
209 [SparsePauliOp.from_list([("XX", 1.0)])],
212pub_result = StatevectorEstimator().run(
213 [(circuit, observables, values)]
214).result()[0]
215assert pub_result.data.evs.shape == (2, len(values))
216```
218Estimator V2 broadcasts observable and parameter arrays. For nontrivial shapes, build a small test first and assert the output shape rather than relying on intuition.

Multiple Observables

observables = [
    [SparsePauliOp.from_list([("ZZ", 1.0)])],
    [SparsePauliOp.from_list([("XX", 1.0)])],
]

pub_result = StatevectorEstimator().run(
    [(circuit, observables, values)]
).result()[0]
assert pub_result.data.evs.shape == (2, len(values))

Estimator V2 broadcasts observable and parameter arrays. For nontrivial shapes, build a small test first and assert the output shape rather than relying on intuition.

Raw lines 204 to 219
220## Precision and Shots
222- Sampler controls finite sampling with `shots`.
223- Estimator controls target accuracy with `precision`.
224- `StatevectorEstimator` is exact at precision zero for unitary circuits and Pauli observables; reset can make trajectories stochastic. Positive precision injects synthetic Gaussian noise, not device or shot-level measurement noise. Its `stds` remain zero and are not hardware confidence intervals.
225- Runtime may translate requested precision into a shot and randomization budget.
226- Runtime twirling settings can affect how shots are allocated.
228```python
229pub_result = runtime_estimator.run(
230 [(isa_circuit, isa_observable)],
231 precision=0.02,
232).result()[0]
233```
235Record requested precision, realized metadata, primitive options, and usage. Do not compare two experiments solely by nominal shot count when mitigation or twirling differs.

Precision and Shots

  • Sampler controls finite sampling with shots.
  • Estimator controls target accuracy with precision.
  • StatevectorEstimator is exact at precision zero for unitary circuits and Pauli observables; reset can make trajectories stochastic. Positive precision injects synthetic Gaussian noise, not device or shot-level measurement noise. Its stds remain zero and are not hardware confidence intervals.
  • Runtime may translate requested precision into a shot and randomization budget.
  • Runtime twirling settings can affect how shots are allocated.
pub_result = runtime_estimator.run(
    [(isa_circuit, isa_observable)],
    precision=0.02,
).result()[0]

Record requested precision, realized metadata, primitive options, and usage. Do not compare two experiments solely by nominal shot count when mitigation or twirling differs.

Raw lines 220 to 236
237## Runtime Sampler V2
239Runtime circuits must already satisfy the selected backend's ISA:
241```python
242from qiskit import QuantumCircuit
243from qiskit.transpiler import generate_preset_pass_manager
244from qiskit_ibm_runtime import QiskitRuntimeService
245from qiskit_ibm_runtime.executor_sampler import Sampler
247service = QiskitRuntimeService()
248backend = service.least_busy(
249 operational=True,
250 simulator=False,
251 min_num_qubits=2,
254circuit = QuantumCircuit(2)
255circuit.h(0)
256circuit.cx(0, 1)
257circuit.measure_all()
259pass_manager = generate_preset_pass_manager(
260 backend=backend,
261 optimization_level=1,
262 seed_transpiler=11,
264isa_circuit = pass_manager.run(circuit)
266sampler = Sampler(
267 mode=backend,
268 options={"default_shots": 1024},
270job = sampler.run([isa_circuit])
271print(job.job_id())
272counts = job.result()[0].data.meas.get_counts()
273```
275Sampler noise-management options include dynamical decoupling, twirling, execution, environment, and simulator settings. Sampler does not expose Estimator resilience levels.

Runtime Sampler V2

Runtime circuits must already satisfy the selected backend's ISA:

from qiskit import QuantumCircuit
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime.executor_sampler import Sampler

service = QiskitRuntimeService()
backend = service.least_busy(
    operational=True,
    simulator=False,
    min_num_qubits=2,
)

circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()

pass_manager = generate_preset_pass_manager(
    backend=backend,
    optimization_level=1,
    seed_transpiler=11,
)
isa_circuit = pass_manager.run(circuit)

sampler = Sampler(
    mode=backend,
    options={"default_shots": 1024},
)
job = sampler.run([isa_circuit])
print(job.job_id())
counts = job.result()[0].data.meas.get_counts()

Sampler noise-management options include dynamical decoupling, twirling, execution, environment, and simulator settings. Sampler does not expose Estimator resilience levels.

Raw lines 237 to 276
277## Runtime Estimator V2
279Use an unmeasured preparation circuit; do not reuse the preceding measured Sampler circuit. Map observables through its final layout:
281```python
282from qiskit import QuantumCircuit
283from qiskit.transpiler import generate_preset_pass_manager
284from qiskit.quantum_info import SparsePauliOp
285from qiskit_ibm_runtime.executor_estimator import Estimator
287circuit = QuantumCircuit(2)
288circuit.h(0)
289circuit.cx(0, 1)
290isa_circuit = generate_preset_pass_manager(backend=backend, optimization_level=1, seed_transpiler=11).run(circuit)
291observable = SparsePauliOp.from_list([("ZZ", 1.0)])
292isa_observable = observable.apply_layout(isa_circuit.layout)
294estimator = Estimator(
295 mode=backend,
296 options={"resilience_level": 1},
298job = estimator.run(
299 [(isa_circuit, isa_observable)],
300 precision=0.02,
302print(job.job_id())
303pub_result = job.result()[0]
304print(pub_result.data.evs, pub_result.data.stds)
305```
307For a parameterized circuit, transpile once and include values in the PUB:
309```python
310pub = (isa_circuit, isa_observable, parameter_values)
311pub_result = estimator.run([pub], precision=0.02).result()[0]
312```

Runtime Estimator V2

Use an unmeasured preparation circuit; do not reuse the preceding measured Sampler circuit. Map observables through its final layout:

from qiskit import QuantumCircuit
from qiskit.transpiler import generate_preset_pass_manager
from qiskit.quantum_info import SparsePauliOp
from qiskit_ibm_runtime.executor_estimator import Estimator

circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
isa_circuit = generate_preset_pass_manager(backend=backend, optimization_level=1, seed_transpiler=11).run(circuit)
observable = SparsePauliOp.from_list([("ZZ", 1.0)])
isa_observable = observable.apply_layout(isa_circuit.layout)

estimator = Estimator(
    mode=backend,
    options={"resilience_level": 1},
)
job = estimator.run(
    [(isa_circuit, isa_observable)],
    precision=0.02,
)
print(job.job_id())
pub_result = job.result()[0]
print(pub_result.data.evs, pub_result.data.stds)

For a parameterized circuit, transpile once and include values in the PUB:

pub = (isa_circuit, isa_observable, parameter_values)
pub_result = estimator.run([pub], precision=0.02).result()[0]
Raw lines 277 to 313
314## Runtime Options
316Set options with a dictionary, an `options_models` Pydantic model, direct attributes, or `.update()`:
318```python
319from qiskit_ibm_runtime.options_models import EstimatorOptions
320from qiskit_ibm_runtime.executor_estimator import Estimator
322options = EstimatorOptions(
323 resilience_level=2,
324 resilience={
325 "zne_mitigation": True,
326 "zne": {"noise_factors": [1, 3, 5]},
327 },
329estimator = Estimator(mode=backend, options=options)
331estimator.options.default_precision = 0.02
332estimator.options.update(
333 dynamical_decoupling={
334 "enable": True,
335 "sequence_type": "XpXm",
336 }
338```
340Current Estimator resilience levels are `0`, `1`, and `2`; there is no level 3. Advanced features can be incompatible with each other, especially fractional gates, gate twirling, PEA, PEC, and gate-folding ZNE. Consult the current options guide before combining them.
342Use `qiskit_ibm_runtime.options_models` with the new client-side primitives. Top-level `EstimatorOptions`/`SamplerOptions` belong to the deprecated implementation. Record `estimator.finalize_options().model_dump()` to include resolved resilience/twirling defaults. Put `max_execution_time` at the top level; `environment.max_execution_time` is deprecated.
344Do not use the old shared `Options()` object or `.set_options()`.

Runtime Options

Set options with a dictionary, an options_models Pydantic model, direct attributes, or .update():

from qiskit_ibm_runtime.options_models import EstimatorOptions
from qiskit_ibm_runtime.executor_estimator import Estimator

options = EstimatorOptions(
    resilience_level=2,
    resilience={
        "zne_mitigation": True,
        "zne": {"noise_factors": [1, 3, 5]},
    },
)
estimator = Estimator(mode=backend, options=options)

estimator.options.default_precision = 0.02
estimator.options.update(
    dynamical_decoupling={
        "enable": True,
        "sequence_type": "XpXm",
    }
)

Current Estimator resilience levels are 0, 1, and 2; there is no level 3. Advanced features can be incompatible with each other, especially fractional gates, gate twirling, PEA, PEC, and gate-folding ZNE. Consult the current options guide before combining them.

Use qiskit_ibm_runtime.options_models with the new client-side primitives. Top-level EstimatorOptions/SamplerOptions belong to the deprecated implementation. Record estimator.finalize_options().model_dump() to include resolved resilience/twirling defaults. Put max_execution_time at the top level; environment.max_execution_time is deprecated.

Do not use the old shared Options() object or .set_options().

Raw lines 314 to 345
346## Job, Batch, and Session Modes
348```python
349from qiskit_ibm_runtime import Batch, Session
350from qiskit_ibm_runtime.executor_estimator import Estimator
351from qiskit_ibm_runtime.executor_sampler import Sampler
353# Job mode
354sampler = Sampler(mode=backend)
355job = sampler.run([isa_circuit], shots=1024)
357# Batch mode: independent jobs
358with Batch(backend=backend, max_time="10m") as batch:
359 sampler = Sampler(mode=batch)
360 batch_jobs = [
361 sampler.run([circuit], shots=1024)
362 for circuit in isa_circuits
363 ]
365# Session mode: iterative jobs; unavailable on the Open Plan
366with Session(backend=backend, max_time="20m") as session:
367 estimator = Estimator(mode=session)
368 session_jobs = [
369 estimator.run([pub], precision=0.03)
370 for pub in iterative_pubs
371 ]
372```
374Create the primitive with `mode=batch` or `mode=session`. Passing the backend instead runs in job mode even inside a context.

Job, Batch, and Session Modes

from qiskit_ibm_runtime import Batch, Session
from qiskit_ibm_runtime.executor_estimator import Estimator
from qiskit_ibm_runtime.executor_sampler import Sampler

# Job mode
sampler = Sampler(mode=backend)
job = sampler.run([isa_circuit], shots=1024)

# Batch mode: independent jobs
with Batch(backend=backend, max_time="10m") as batch:
    sampler = Sampler(mode=batch)
    batch_jobs = [
        sampler.run([circuit], shots=1024)
        for circuit in isa_circuits
    ]

# Session mode: iterative jobs; unavailable on the Open Plan
with Session(backend=backend, max_time="20m") as session:
    estimator = Estimator(mode=session)
    session_jobs = [
        estimator.run([pub], precision=0.03)
        for pub in iterative_pubs
    ]

Create the primitive with mode=batch or mode=session. Passing the backend instead runs in job mode even inside a context.

Raw lines 346 to 375
376## Adapting a Backend
378Use backend primitives when a provider exposes `BackendV2` but no native V2 primitive:
380```python
381from qiskit.primitives import BackendEstimatorV2, BackendSamplerV2
383sampler = BackendSamplerV2(backend=backend)
384estimator = BackendEstimatorV2(backend=backend)
385```
387`BackendSamplerV2` requires a backend supporting `memory=True` for shot-resolved output. Provider behavior, result quality, and options differ. Transpile for the backend target and read the provider documentation.

Adapting a Backend

Use backend primitives when a provider exposes BackendV2 but no native V2 primitive:

from qiskit.primitives import BackendEstimatorV2, BackendSamplerV2

sampler = BackendSamplerV2(backend=backend)
estimator = BackendEstimatorV2(backend=backend)

BackendSamplerV2 requires a backend supporting memory=True for shot-resolved output. Provider behavior, result quality, and options differ. Transpile for the backend target and read the provider documentation.

Raw lines 376 to 388
389## Result Handling Checklist
391For every result:
3931. Match each `PubResult` to its input PUB.
3942. Use the actual classical-register field for Sampler output.
3953. Preserve array dimensions for parameter and observable sweeps.
3964. Store metadata alongside values or counts.
3975. Store the job ID before blocking on `result()`.
3986. Record package versions, backend name, seeds, precision or shots, and all non-default options.
3997. Treat mitigated expectation values as estimates with method-dependent bias and overhead.

Result Handling Checklist

For every result:

  1. Match each PubResult to its input PUB.
  2. Use the actual classical-register field for Sampler output.
  3. Preserve array dimensions for parameter and observable sweeps.
  4. Store metadata alongside values or counts.
  5. Store the job ID before blocking on result().
  6. Record package versions, backend name, seeds, precision or shots, and all non-default options.
  7. Treat mitigated expectation values as estimates with method-dependent bias and overhead.
Raw lines 389 to 400
401## Migration Traps
403| Old pattern | Current pattern |
404|---|---|
405| `Sampler()` or `Estimator()` V1 | Explicit V2 implementation |
406| `.quasi_dists` | Shot-resolved `BitArray`, such as `.data.meas.get_counts()` |
407| `.values` | `.result()[i].data.evs` |
408| Parallel circuit/observable/value lists | One or more PUB tuples |
409| Shared `Options()` | `options_models.SamplerOptions`, `options_models.EstimatorOptions`, dictionaries, or `.options.update()` |
410| `backend=` / `session=` primitive arguments | `mode=` |
411| Runtime auto-transpilation | Explicit ISA circuit preparation |
412| Unmapped observables | `observable.apply_layout(isa_circuit.layout)` |

Migration Traps

Old patternCurrent pattern
Sampler() or Estimator() V1Explicit V2 implementation
.quasi_distsShot-resolved BitArray, such as .data.meas.get_counts()
.values.result()[i].data.evs
Parallel circuit/observable/value listsOne or more PUB tuples
Shared Options()options_models.SamplerOptions, options_models.EstimatorOptions, dictionaries, or .options.update()
backend= / session= primitive argumentsmode=
Runtime auto-transpilationExplicit ISA circuit preparation
Unmapped observablesobservable.apply_layout(isa_circuit.layout)
Raw lines 401 to 413
414## Common Errors
416- **No `meas` field**: the circuit has a differently named register or no measurements.
417- **Shape/broadcast error**: check `circuit.parameters`, the last parameter-value axis, and observable-array shape.
418- **Circuit not ISA-compatible**: transpile against the exact backend target before Runtime execution.
419- **Observable qubit mismatch**: apply the circuit layout and confirm the resulting width.
420- **Unsupported option**: options are implementation- and version-specific.
421- **Session rejected**: Open Plan workloads must use job or batch mode.
422- **Unexpected cost**: mitigation, twirling, and precision settings can multiply circuit and shot counts.

Common Errors

  • **No meas field**: the circuit has a differently named register or no measurements.
  • Shape/broadcast error: check circuit.parameters, the last parameter-value axis, and observable-array shape.
  • Circuit not ISA-compatible: transpile against the exact backend target before Runtime execution.
  • Observable qubit mismatch: apply the circuit layout and confirm the resulting width.
  • Unsupported option: options are implementation- and version-specific.
  • Session rejected: Open Plan workloads must use job or batch mode.
  • Unexpected cost: mitigation, twirling, and precision settings can multiply circuit and shot counts.
Raw lines 414 to 422