---
title: Executor inputs and outputs
description: Understand the inputs and outputs to the Executor primitive.
source: https://eu-de.quantum.cloud.ibm.com/docs/en/ibm-quantum-compute/executor-input-output
---

# Executor 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.6.1
qiskit-ibm-runtime~=0.50.0
samplomatic~=0.21.0
```

The Executor primitive is part of the [directed execution model](/docs/ibm-quantum-compute/directed-execution-model), which provides more flexibility when customizing an error mitigation workflow.

The inputs and outputs of the Executor primitive are very different from those of the [Sampler](/docs/ibm-quantum-compute/sampler-input-output) and [Estimator](/docs/ibm-quantum-compute/estimator-input-output) primitives. For example, instead of taking a list of PUBs as the input, Executor takes a `QuantumProgram`, which contains a list of `QuantumProgramItem` objects. These container classes give you more flexibility than a PUB, which is a simple tuple data structure.

Executor's output is a `QuantumProgramResult`, which is an iterable and contains one element for each input `QuantumProgramItem`.

## Inputs: Quantum programs

As stated previously, the input to an Executor primitive is a  [`QuantumProgram`](/docs/api/qiskit-ibm-runtime/quantum-program-quantum-program), which is an iterable of
[`QuantumProgramItem`](/docs/api/qiskit-ibm-runtime/quantum-program-quantum-program-item) objects.  These objects can be of two types:

- `CircuitItem`, which typically stores a circuit and its parameter values (if any).
- `SamplexItem`, which typically stores the following:
  - A template circuit
  - A samplex object, which is used to generate randomized sets of parameters at runtime (for example to perform twirling or inject noise)
  - Arguments for the samplex, which might include parameter values for the original circuit

Each of these items represents a different task for Executor to perform.

### Before you begin

Some of the code examples on this page use `samplex`, which is part of the Samplomatic package.  Therefore, before running those code block, you must install Samplomatic, as shown in the following code block.  For more information, see the [Samplomatic documentation](https://qiskit.github.io/samplomatic).

```python
pip install samplomatic

# For visualization support, include the visualization dependencies.
# pip install samplomatic[vis]
```

### Example: Create a `QuantumProgram` with two different tasks

First initialize your quantum program, then append program items to it by using either `append_circuit_item` or `append_samplex_item` (if a samplex is present), as shown in the following examples.

The following cell initializes a `QuantumProgram` and specifies that it should run 1024 shots for every configuration of each item in the program.

> **Note**
>
> Unlike with Sampler, a `QuantumProgram` takes only a single shot value. If you want a different shot value, you need a separate `QuantumProgram`, which would be a separate job.

```python
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime.quantum_program import QuantumProgram
from qiskit_ibm_runtime import Executor, QiskitRuntimeService
from qiskit.circuit import Parameter, QuantumCircuit
import numpy as np
from samplomatic import build
from samplomatic.transpiler import generate_boxing_pass_manager

# Initialize an empty program
program = QuantumProgram(shots=1024)

# Initialize and transpile a 3-qubit quantum circuit with 2 parameters.
circuit = QuantumCircuit(3)
circuit.h(0)
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.rz(Parameter("theta"), 0)
circuit.rz(Parameter("phi"), 1)

# `measure_all` adds a 3-bit classical register named "meas"
circuit.measure_all()

# Choose the least busy backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# Generate a preset pass manager
# This will be used to convert the abstract circuit to an
# equivalent Instruction Set Architecture (ISA) circuit.
preset_pass_manager = generate_preset_pass_manager(
    backend=backend, optimization_level=0
)

# Transpile the circuit
isa_circuit = preset_pass_manager.run(circuit)
```

### Append a `CircuitItem`

Next, append the target circuit, which was transpiled according to the backend's instruction set architecture (ISA), to the `QuantumProgram`. Since this circuit has two parameters, we must also provide the parameter values (10 sets in this example). Running this `CircuitItem` is the first task that the program will perform.

```python
# Append the transpiled circuit and an array
# containing 10 sets of parameter values to the program
program.append_circuit_item(
    isa_circuit,
    circuit_arguments=np.random.rand(
        10, 2
    ),  # 10 sets of parameter values and 2 parameters
)
```

### Append a `SamplexItem`

Circuit items are executed without any sort of randomization. On the contrary, samplex items let you specify how to randomize their content. The next cell uses the `generate_boxing_pass_manager()` function to group the circuit's gates and measurements into boxes and add a twirling annotation to each box. It then generates a template circuit and a samplex pair using the `build()` function.

Running this `SamplexItem` is the second task that the program will perform.

See the Samplomatic [API](https://github.com/Qiskit/samplomatic/) documentation for full details about `samplex` and its arguments. See the Samplomatic [Transpiler guide](https://qiskit.github.io/samplomatic/guides/transpiler.html) for information about using the  `generate_boxing_pass_manager()` function.

```python
# Transpile the circuit, additionally grouping gates and measurements into annotated boxes
preset_pass_manager = generate_preset_pass_manager(
    backend=backend, optimization_level=0
)

# Use the boxing pass manager to group gates
# and measurements into boxes and add
# a`Twirl` annotation.
preset_pass_manager.post_scheduling = generate_boxing_pass_manager(
    # Add gate twirling
    enable_gates=True,
    # Add measurement twirling
    enable_measures=True,
)
boxed_circuit = preset_pass_manager.run(circuit)

# Build the template circuit and the samplex.  The template circuit has parametric gates
# without fixed values and the samplex randomly generates the parameter
# values on the server side at runtime to perform twirling.
template_circuit, samplex = build(boxed_circuit)

# Determine what arguments are required by the samplex.
# Input the arguments in samplex_arguments.
print(samplex.inputs())
```

Output:

```
TensorInterface(<
  - 'parameter_values' <float64[2]>: Input parameter values to use during sampling.
>)
```

```python
# Append the template circuit and samplex as a samplex item
program.append_samplex_item(
    template_circuit,
    samplex=samplex,
    samplex_arguments={
        # the arguments required by the samplex.sample method
        "parameter_values": np.random.rand(10, 2),
    },
    shape=(28, 10),  # 28 randomizations and 10 sets of parameter values
)
```

```python
# Initialize an Executor with the default options
executor = Executor(mode=backend)

# Submit the job
job = executor.run(program)

# Retrieve the result
result = job.result()
```

## Outputs

Executor's output is a [`QuantumProgramResult`](/docs/api/qiskit-ibm-runtime/results-quantum-program-result), which is an iterable. It contains one entry per input `QuantumProgramItem` in the same order as the input items. Each of these output items is a dictionary where the keys are strings that correspond the classical registers' names in the input circuits (among others), so you no longer need to memorize these names like you did with Sampler output. The dictionary values are of type `np.ndarray`.

The result for the previous example contains these items:

### `CircuitItem` result

The first item contains the results of running the first task (a `CircuitItem`) in the program. It contains a single key, `meas`, which is the name of the classical register in the input circuit. The value of this key maps to an `np.ndarray` of shape `(parameter sets, shots, register bits)`, which is (10, 1024, 3) for the above example.

The following code illustrates how to access this information:

```python
# Access the results of the classical register of task #0, a CircuitItem
result_0 = result[0]["meas"]
print(f"Result shape: {result_0.shape}")
```

Output:

```
Result shape: (10, 1024, 3)
```

The right-most axis of `result_0` runs over classical bits in little-endian order. Thus, index 0 holds the outcome of classical bit 0, the least-significant bit.

### `SamplexItem` result

The second item contains the results of running the second task (a `SamplexItem`) in the program. This item contains multiple keys. The `meas` key, which is the name of the input circuit's classical register, maps to that register's array of results. This array has the shape `(randomizations, parameter sets, shots, classical bits)`, or (28, 10, 1024, 3) in this example.

Additionally, the output contains a `measurement_flips.meas` key, which is the bit-flip corrections to undo the measurement twirling for the `meas` register.  This output shape will be (28, 10, 1, 3) for our example because only one shot is required to perform the bit-flip.

Note that the right-most axis of `result_1` runs over classical bits in little-endian order.

```python
# Access the results of the classical register of task #1
result_1 = result[1]["meas"]
print(f"Result shape: {result_1.shape}")

# Access the bit-flip corrections
flips_1 = result[1]["measurement_flips.meas"]
print(f"Bit-flip corrections shape: {flips_1.shape}")

# Undo the bit flips via classical XOR
unflipped_result_1 = result_1 ^ flips_1
```

Output:

```
Result shape: (28, 10, 1024, 3)
Bit-flip corrections shape: (28, 10, 1, 3)
```

## View execution spans

A single `Executor.run()` call submits a single job. On the server side, however, the service does not necessarily run every configuration of the quantum program at the same time. Instead, it groups the program items' bound circuits into *chunks*, and runs one chunk at a time on the QPU. You don't need to split your program or submit additional jobs; the chunking happens automatically within the one job. The result's [`metadata`](/docs/api/qiskit-ibm-runtime/results-metadata) attribute contains timing information about these chunks in `result.metadata.chunk_timing`. These timestamps are the actual start and stop times of each chunk on the QPU. You can use this information to see when particular data was collected on the QPU, to understand how Executor distributed the work, or to diagnose performance issues.

`chunk_timing` is a list that contains one [`ChunkSpan`](/docs/api/qiskit-ibm-runtime/results-chunk-span) per executed chunk. Each `ChunkSpan` has the following attributes:

- `start` and `stop`: The UTC timestamps when the chunk started and stopped executing on the QPU.
- `parts`: A list of [`ChunkPart`](/docs/api/qiskit-ibm-runtime/results-chunk-part) objects that describe which program items contributed to the chunk. Each `ChunkPart` contains the following:
  - `idx_item`: The index of the program item, in the order that the items were appended to the `QuantumProgram`.
  - `size`: The number of configurations (elements of the item's shape) from that item that were executed in this chunk. For example, the `SamplexItem` in the previous example has shape `(28, 10)`, so it has 280 configurations in total.

The following code prints the duration of each chunk and the contents of each chunk for the job submitted previously:

```python
for idx, span in enumerate(result.metadata.chunk_timing):
    duration = (span.stop - span.start).total_seconds()
    print(f"Chunk {idx}: {span.start} to {span.stop} ({duration:.3f} s)")
    for part in span.parts:
        print(f"  Item {part.idx_item}: {part.size} configurations")
```

Output:

```
Chunk 0: 2026-11-06 01:47:46.009718+00:00 to 2026-11-06 01:49:05.577720+00:00 (79.568 s)
  Item 0: 10 configurations
  Item 1: 280 configurations
```

The `QuantumProgramResult.timing` property wraps the same list in a [`ChunkTiming`](/docs/api/qiskit-ibm-runtime/results-chunk-timing) object, which also reports global information about the collection of chunks:

```python
timing = result.timing

print("Number of chunks:", len(timing))
print("   Start of the first chunk:", timing.start)
print("      End of the last chunk:", timing.stop)
print("        Total duration (s):", timing.duration)
```

Output:

```
Number of chunks: 1
   Start of the first chunk: 2026-11-06 01:47:46.009718+00:00
      End of the last chunk: 2026-11-06 01:49:05.577720+00:00
        Total duration (s): 79.568002
```

### Visualize execution spans

Use the [`draw_chunk_timings`](/docs/api/qiskit-ibm-runtime/visualization-draw-chunk-timings) function to visualize the chunk timings on an interactive bar plot. The x-axis is time, and each chunk is drawn as a horizontal segment that spans its start and stop times. The y-axis shows the cumulative number of configurations completed (or the percentage of the total workload, if you set `normalize_y=True`). Hover over a segment to see the timestamps, duration, and parts of that chunk.

The function returns a `plotly` figure, which you can display directly, save to a file, or both. It accepts either the `chunk_timing` list or a `ChunkTiming` object. You can pass several of them, for example from different jobs, to draw them on the same plot. Set `common_start=True` to align them at a common start time for comparison.

> **Plotly required**
>
> `draw_chunk_timings` requires `plotly`. You can install it by running `pip install plotly`.

> **Gaps between chunks**
>
> The visualization might show gaps between the chunk execution. All jobs submitted to a real QPU go through some classical preprocessing, such as translation into pulses that can be sent to the hardware. Because this preprocessing can be done in parallel, the service dequeues and starts multiple jobs simultaneously. The QPU, however, can only process one unit of work at a time. Therefore, if a job generates multiple chunks, its chunks might be interleaved with those of other users' jobs, and each chunk might have to wait for its turn on the QPU. This is why gaps can appear between chunks.

```python
from qiskit_ibm_runtime.visualization import draw_chunk_timings

fig = draw_chunk_timings(result.timing, names="Executor job")

# Alternatively, call the `draw()` method of the ChunkTiming object
# fig = result.timing.draw()

# Uncomment the following line to display the figure
# fig.show(renderer="notebook")

# Save to a file
# fig.write_html("chunk_timings.html")
```

## Next steps

> **Recommendations**
>
> - Explore [examples](/docs/ibm-quantum-compute/executor-examples) that use Executor.
> - Learn about the [directed execution model](/docs/ibm-quantum-compute/directed-execution-model).
> - Understand [Executor broadcasting](/docs/ibm-quantum-compute/executor-broadcasting).
