---
title: Sampler inputs and outputs
description: Understand the input and output format of Sampler primitives
source: https://eu-de.quantum.cloud.ibm.com/docs/en/guides/sampler-input-output
---

# Sampler inputs and outputs

### Package versions

The code on this page was developed using the following requirements.
We recommend using these versions or newer.

```
qiskit[all]~=2.5.1
qiskit-ibm-runtime~=0.47.0
```

This page gives an overview of the inputs and outputs of the `qiskit-ibm-runtime` Sampler primitive, which executes workloads on the IBM Quantum® Compute Service. Sampler lets you efficiently define vectorized workloads by using a data structure known as a [**Primitive Unified Bloc (PUB)**](/docs/guides/primitive-input-output#pubs). They are used as inputs to the [`run()`](/docs/api/qiskit-ibm-runtime/sampler-v2#run) method for the Sampler primitive, which executes the defined workload as a job. Then, after the job has completed, the results are returned in a format that is dependent on both the PUBs used as well as the runtime options specified from the primitive.

## Inputs

Each PUB is in the format:

(`<single circuit>`, `<one or more optional parameter value>`, `<optional shots>`),

There can be multiple `parameter values` items, and each item can be either an array or a single parameter, depending on the chosen circuit. Additionally, the input must contain measurements.

For the Sampler primitive, a PUB can contain at most three values:

- A single `QuantumCircuit`, which may contain one or more [`Parameter`](/docs/api/qiskit/qiskit.circuit.Parameter) objects
  *Note: These circuits should also include measurement instructions for each of the qubits to be sampled.*
- A collection of parameter values to bind the circuit against $\theta_k$ (only needed if any `Parameter` objects are used that must be bound at runtime)
- (Optionally) a number of shots to measure the circuit with

***

The following code demonstrates an example set of vectorized inputs to the `Sampler` primitive and executes them on an IBM® backend as a single `RuntimeJobV2 ` object.

```python
from qiskit.circuit import (
    Parameter,
    QuantumCircuit,
    ClassicalRegister,
    QuantumRegister,
)
from qiskit.transpiler import generate_preset_pass_manager
from qiskit.quantum_info import SparsePauliOp
from qiskit.primitives.containers import BitArray

from qiskit_ibm_runtime import (
    QiskitRuntimeService,
    SamplerV2 as Sampler,
)

import numpy as np

# Instantiate runtime service and get
# the least busy backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# Define a circuit with two parameters.
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.ry(Parameter("a"), 0)
circuit.rz(Parameter("b"), 0)
circuit.cx(0, 1)
circuit.h(0)
circuit.measure_all()

# Transpile the circuit
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
transpiled_circuit = pm.run(circuit)
layout = transpiled_circuit.layout

# Now define a sweep over parameter values, the last axis of dimension 2 is
# for the two parameters "a" and "b"
params = np.vstack(
    [
        np.linspace(-np.pi, np.pi, 100),
        np.linspace(-4 * np.pi, 4 * np.pi, 100),
    ]
).T

sampler_pub = (transpiled_circuit, params)

# Instantiate the new Sampler object, then run the transpiled circuit
# using the set of parameters and observables.
sampler = Sampler(mode=backend)
job = sampler.run([sampler_pub])
result = job.result()
```

## Outputs

After one or more PUBs are sent to a QPU for execution and a job successfully completes, the data is returned as a [`PrimitiveResult`](/docs/api/qiskit/qiskit.primitives.PrimitiveResult) container object accessed by calling the `RuntimeJobV2.result()` method. The `PrimitiveResult` contains an iterable list of [`SamplerPubResult`](/docs/api/qiskit/qiskit.primitives.SamplerPubResult) objects that contain the execution results for each PUB. These data are samples of the circuit output.

Each element of this list corresponds to a PUB submitted to the primitive's `run()` method (for example, a job submitted with 20 PUBs will return a `PrimitiveResult` object that contains a list of 20 `SamplerPubResult` objects, one corresponding to each PUB).

Each `SamplerPubResult` object possesses both a `data` and a `metadata` attribute.

- The `data` attribute is a customized [`DataBin`](/docs/api/qiskit/qiskit.primitives.DataBin) that contains the actual measurement values, standard deviations, and so forth. The data bins are dict-like objects that contain one `BitArray` per `ClassicalRegister` in the circuit.
- The `BitArray` class is a container for ordered shot data. It stores the sampled bitstrings as bytes inside a two-dimensional array. The left-most axis of this array runs over ordered shots, while the right-most axis runs over bytes.
- The `metadata` attribute contains information about the runtime options used (explained later in the [Result metadata](#result-metadata) section of this page).

The following is a visual outline of the `PrimitiveResult` data structure:

```
    └── PrimitiveResult
        ├── SamplerPubResult[0]
        │   ├── metadata
        │   └── data  ## In the form of a DataBin object
        │       ├── NAME_OF_CLASSICAL_REGISTER
        │       │   └── BitArray of count data (default is 'meas')
        |       |
        │       └── NAME_OF_ANOTHER_CLASSICAL_REGISTER
        │           └── BitArray of count data (exists only if more than one
        |                 ClassicalRegister was specified in the circuit)
        ├── SamplerPubResult[1]
        |   ├── metadata
        |   └── data  ## In the form of a DataBin object
        |       └── NAME_OF_CLASSICAL_REGISTER
        |           └── BitArray of count data for second pub
        ├── ...
        ├── ...
        └── ...
```

Put simply, a single job returns a [`PrimitiveResult`](/docs/api/qiskit/qiskit.primitives.PrimitiveResult) object and contains a list of one or more [`SamplerPubResult`](/docs/api/qiskit/qiskit.primitives.SamplerPubResult) objects. These `SamplerPubResult` objects then store the measurement data for each PUB that was submitted to the job.

As a first example, let us look at the following ten-qubit circuit:

```python
# generate a ten-qubit GHZ circuit
circuit = QuantumCircuit(10)
circuit.h(0)
circuit.cx(range(0, 9), range(1, 10))

# append measurements with the `measure_all` method
circuit.measure_all()

# transpile the circuit
transpiled_circuit = pm.run(circuit)

# run the Sampler job and retrieve the results
sampler = Sampler(mode=backend)
job = sampler.run([transpiled_circuit])
result = job.result()

# the data bin contains one BitArray
data = result[0].data
print(f"Databin: {data}\n")

# to access the BitArray, use the key "meas", which is the default name of
# the classical register when this is added by the `measure_all` method
array = data.meas
print(f"BitArray: {array}\n")
print(f"The shape of register `meas` is {data.meas.array.shape}.\n")
print(f"The bytes in register `alpha`, shot by shot:\n{data.meas.array}\n")
```

Output:

```
Databin: DataBin(meas=BitArray(<shape=(), num_shots=4096, num_bits=10>))

BitArray: BitArray(<shape=(), num_shots=4096, num_bits=10>)

The shape of register `meas` is (4096, 2).

The bytes in register `alpha`, shot by shot:
[[  3 255]
 [  0   0]
 [  0   1]
 ...
 [  3   0]
 [  0   0]
 [  3 254]]

```

It can sometimes be convenient to convert away from the bytes format in the `BitArray` to bitstrings. The `get_count` method returns a dictionary mapping bitstrings to the number of times that they occurred.

```python
# optionally, convert away from the native BitArray format to a dictionary format
counts = data.meas.get_counts()
print(f"Counts: {counts}")
```

Output:

```
Counts: {'1111111111': 1346, '0000000000': 1754, '0000000001': 55, '1000000000': 56, '1111111110': 92, '0111111111': 23, '1011111111': 15, '0001111111': 23, '1111011011': 1, '1111111101': 45, '1111111011': 108, '1111110111': 32, '0100000000': 10, '0000000111': 21, '0011111111': 21, '1111110000': 26, '1101111111': 47, '1111011111': 23, '1111111010': 6, '1100000000': 45, '1111100000': 32, '1110000000': 21, '1111101111': 13, '0010000000': 14, '0000000011': 19, '0000000101': 2, '0000001110': 2, '0000100000': 4, '0000001111': 20, '1111111100': 22, '0000010000': 5, '1101110111': 4, '1011111101': 1, '0000000010': 15, '0000001000': 12, '1111110110': 7, '1111000000': 3, '0010000001': 1, '0111011111': 3, '1001111111': 3, '1101111011': 3, '0000011111': 16, '0000011110': 3, '0001111011': 1, '1011111011': 3, '1111110011': 4, '1111101011': 2, '0000000100': 6, '1110111111': 12, '1111111000': 17, '0000111111': 5, '0001111101': 2, '1101100000': 2, '1101110001': 1, '1000001111': 2, '1111101110': 1, '1110111101': 1, '1101111101': 2, '1110000100': 1, '0100011111': 1, '1110000010': 1, '0011111110': 2, '0111111110': 1, '1111110010': 1, '0111110111': 1, '0000000110': 1, '0101111111': 1, '1101011111': 1, '1111001111': 1, '1110011111': 1, '0011111000': 2, '1101111110': 3, '1110111110': 1, '0110000000': 2, '1110000111': 1, '0000010111': 3, '0001000000': 3, '0111101111': 1, '0000011100': 1, '1000000001': 1, '1111011010': 1, '0000001010': 1, '1111100111': 2, '1111100011': 2, '0000001101': 1, '0111001111': 1, '1111111001': 1, '1101111000': 1, '0111110000': 1, '1111000111': 1, '1010000000': 1, '0011110000': 1, '1100000001': 1, '1011001101': 1, '0000001100': 1, '1100111111': 1, '1110111011': 1, '1111011101': 1, '1000011111': 1, '1101111001': 1, '0101101111': 1, '0000011011': 1, '0000111011': 1, '0111111100': 1, '1011100000': 1, '0011111011': 1, '0000010010': 1, '1001111011': 1}
```

When a circuit contains more than one classical register, the results are stored in different `BitArray` objects. The following example modifies the previous snippet by splitting the classical register into two distinct registers:

```python
# generate a ten-qubit GHZ circuit with two classical registers
circuit = QuantumCircuit(
    qreg := QuantumRegister(10),
    alpha := ClassicalRegister(1, "alpha"),
    beta := ClassicalRegister(9, "beta"),
)
circuit.h(0)
circuit.cx(range(0, 9), range(1, 10))

# append measurements with the `measure_all` method
circuit.measure([0], alpha)
circuit.measure(range(1, 10), beta)

# transpile the circuit
transpiled_circuit = pm.run(circuit)

# run the Sampler job and retrieve the results
sampler = Sampler(mode=backend)
job = sampler.run([transpiled_circuit])
result = job.result()

# the data bin contains two BitArrays, one per register, and can be accessed
# as attributes using the registers' names
data = result[0].data
print(f"BitArray for register 'alpha': {data.alpha}")
print(f"BitArray for register 'beta': {data.beta}")
```

Output:

```
BitArray for register 'alpha': BitArray(<shape=(), num_shots=4096, num_bits=1>)
BitArray for register 'beta': BitArray(<shape=(), num_shots=4096, num_bits=9>)
```

### Use `BitArray` objects for performant post-processing

Since arrays generally offer better performance compared to dictionaries, it is advisable to perform any post-processing directly on the `BitArray` objects rather than on dictionaries of counts. The `BitArray` class offers a range of methods to perform some common post-processing operations:

```python
print(f"The shape of register `alpha` is {data.alpha.array.shape}.")
print(f"The bytes in register `alpha`, shot by shot:\n{data.alpha.array}\n")

print(f"The shape of register `beta` is {data.beta.array.shape}.")
print(f"The bytes in register `beta`, shot by shot:\n{data.beta.array}\n")

# post-select the bitstrings of `beta` based on having sampled "1" in `alpha`
mask = data.alpha.array == "0b1"
ps_beta = data.beta[mask[:, 0]]
print(f"The shape of `beta` after post-selection is {ps_beta.array.shape}.")
print(f"The bytes in `beta` after post-selection:\n{ps_beta.array}")

# get a slice of `beta` to retrieve the first three bits
beta_sl_bits = data.beta.slice_bits([0, 1, 2])
print(
    f"The shape of `beta` after bit-wise slicing is {beta_sl_bits.array.shape}."
)
print(f"The bytes in `beta` after bit-wise slicing:\n{beta_sl_bits.array}\n")

# get a slice of `beta` to retrieve the bytes of the first five shots
beta_sl_shots = data.beta.slice_shots([0, 1, 2, 3, 4])
print(
    f"The shape of `beta` after shot-wise slicing is {beta_sl_shots.array.shape}."
)
print(
    f"The bytes in `beta` after shot-wise slicing:\n{beta_sl_shots.array}\n"
)

# calculate the expectation value of diagonal operators on `beta`
ops = [SparsePauliOp("ZZZZZZZZZ"), SparsePauliOp("IIIIIIIIZ")]
exp_vals = data.beta.expectation_values(ops)
for o, e in zip(ops, exp_vals):
    print(f"Exp. val. for observable `{o}` is: {e}")

# concatenate the bitstrings in `alpha` and `beta` to "merge" the results of the two
# registers
merged_results = BitArray.concatenate_bits([data.alpha, data.beta])
print(f"\nThe shape of the merged results is {merged_results.array.shape}.")
print(f"The bytes of the merged results:\n{merged_results.array}\n")
```

Output:

```
The shape of register `alpha` is (4096, 1).
The bytes in register `alpha`, shot by shot:
[[0]
 [0]
 [0]
 ...
 [1]
 [0]
 [1]]

The shape of register `beta` is (4096, 2).
The bytes in register `beta`, shot by shot:
[[  0   0]
 [  0   0]
 [  1 255]
 ...
 [  1 255]
 [  0   0]
 [  1 255]]

The shape of `beta` after post-selection is (0, 2).
The bytes in `beta` after post-selection:
[]
The shape of `beta` after bit-wise slicing is (4096, 1).
The bytes in `beta` after bit-wise slicing:
[[0]
 [0]
 [7]
 ...
 [7]
 [0]
 [7]]

The shape of `beta` after shot-wise slicing is (5, 2).
The bytes in `beta` after shot-wise slicing:
[[  0   0]
 [  0   0]
 [  1 255]
 [  0   0]
 [  1 255]]

Exp. val. for observable `SparsePauliOp(['ZZZZZZZZZ'],
              coeffs=[1.+0.j])` is: 0.115234375
Exp. val. for observable `SparsePauliOp(['IIIIIIIIZ'],
              coeffs=[1.+0.j])` is: 0.02392578125

The shape of the merged results is (4096, 2).
The bytes of the merged results:
[[  0   0]
 [  0   0]
 [  3 254]
 ...
 [  3 255]
 [  0   0]
 [  3 255]]

```

## Result metadata

In addition to the execution results, both the `PrimitiveResult` and `SamplerPubResult` objects contain a metadata attribute about the job that was submitted. The metadata containing information for all submitted PUBs (such as the various [runtime options](/docs/api/qiskit-ibm-runtime/options) available) can be found in the `PrimitiveResult.metatada`, while the metadata specific to each PUB is found in `SamplerPubResult.metadata`.

The Sampler result metadata also includes execution timing information called the [*execution span*](#execution-spans).

> **Note**
>
> In the metadata field, primitive implementations can return any information about execution that is relevant to them, and there are no key-value pairs that are guaranteed by the base primitive. Thus, the returned metadata might be different in different primitive implementations.

```python
# Print out the results metadata
print("The metadata of the PrimitiveResult is:")
for key, val in result.metadata.items():
    print(f"'{key}' : {val},")

print("\nThe metadata of the PubResult result is:")
for key, val in result[0].metadata.items():
    print(f"'{key}' : {val},")
```

Output:

```
The metadata of the PrimitiveResult is:
'execution' : {'execution_spans': ExecutionSpans([DoubleSliceSpan(<start='2026-08-01 08:21:10', stop='2026-08-01 08:21:13', size=4096>)])},
'version' : 2,

The metadata of the PubResult result is:
'circuit_metadata' : {},
```

### View execution spans

The results of [`SamplerV2`](/docs/api/qiskit-ibm-runtime/sampler-v2) jobs executed in IBM Quantum Compute Service contain execution timing information in their metadata.
This timing information can be used to place upper and lower timestamp bounds on when particular shots were executed on the QPU.
Shots are grouped into [`ExecutionSpan`](/docs/api/qiskit-ibm-runtime/execution-span-execution-span) objects, each of which indicates a start time, a stop time, and a specification of which shots were collected in the span.

An execution span specifies which data was executed during its window by providing an [`ExecutionSpan.mask`](/docs/api/qiskit-ibm-runtime/execution-span-execution-span#mask) method. This method, given any [Primitive Unified Block (PUB)](/docs/guides/primitive-input-output#pubs) index, returns a boolean mask that is `True` for all shots executed during its window. PUBs are indexed by the order in which they were given to the Sampler run call. If, for example, a PUB has shape `(2, 3)` and was run with four shots, then the mask's shape is `(2, 3, 4)`. See the [execution\_span](/docs/api/qiskit-ibm-runtime/execution-span) API page for full details.

To view execution span information, review the metadata of the result returned by `SamplerV2`, which comes in the form of an `ExecutionSpans` object. This object is a list-like container containing instances of subclasses of `ExecutionSpan`, such as `SliceSpan`.

Example:

```python
# Define two circuits, each with one parameter with two parameters.
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.ry(Parameter("a"), 0)
circuit.cx(0, 1)
circuit.h(0)
circuit.measure_all()


pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
transpiled_circuit = pm.run(circuit)

params = np.random.uniform(size=(2, 3)).T

sampler_pub = (transpiled_circuit, params)

# Instantiate the new Estimator object, then run the transpiled circuit
# using the set of parameters and observables.

job = sampler.run([sampler_pub], shots=4)

result = job.result()
spans = job.result().metadata["execution"]["execution_spans"]
print(spans)
```

Output:

```
ExecutionSpans([DoubleSliceSpan(<start='2026-08-01 08:21:37', stop='2026-08-01 08:21:38', size=24>)])
```

```python
from qiskit.primitives import BitArray

# Get the mask of the 1st PUB for the 0th span.
mask = spans[0].mask(0)

# Decide whether the 0th shot of parameter set (1, 2) occurred in this span.
in_this_span = mask[2, 1, 0]

# Create a new bit array containing only the PUB-1 data collected during this span.
bits = result[0].data.meas
filtered_data = BitArray(bits.array[mask], bits.num_bits)
```

Execution spans can be filtered to include information pertaining to specific PUBs, selected by their indices:

```python
# take the subset of spans that reference data in PUBs 0 or 2
spans.filter_by_pub([0, 2])
```

Output:

```
ExecutionSpans([DoubleSliceSpan(<start='2026-08-01 08:21:37', stop='2026-08-01 08:21:38', size=24>)])
```

View global information about the collection of execution spans:

```python
print("Number of execution spans:", len(spans))
print("  Start of the first span:", spans.start)
print("     End of the last span:", spans.stop)
print("       Total duration (s):", spans.duration)
```

Output:

```
Number of execution spans: 1
  Start of the first span: 2026-08-01 08:21:37.606114
     End of the last span: 2026-08-01 08:21:38.960352
       Total duration (s): 1.354238
```

Extract and inspect a particular span:

```python
spans.sort()
print(" Start of first span:", spans[0].start)
print("   End of first span:", spans[0].stop)
print("#shots in first span:", spans[0].size)
```

Output:

```
 Start of first span: 2026-08-01 08:21:37.606114
   End of first span: 2026-08-01 08:21:38.960352
#shots in first span: 24
```

> **Note**
>
> It is possible for time windows specified by distinct execution spans to overlap. This is not because a QPU was performing multiple executions at once, but is instead an artifact of certain classical processing that might happen concurrently with quantum execution. The guarantee being made is that the referenced data definitely occurred in the reported execution span, but not necessarily that the limits of the time window are as tight as possible.
