---
title: Executor broadcasting
description: How the Executor handles array inputs and outputs using broadcasting semantics
source: https://eu-de.quantum.cloud.ibm.com/docs/en/guides/executor-broadcasting
---

# Executor broadcasting

The data provided to the Executor primitive can be arranged in a variety of shapes to provide flexibility in a workload through broadcasting. This guide explains how the Executor handles array inputs and outputs using broadcasting semantics. Understanding these concepts will help you efficiently sweep over parameter values, combine
multiple configurations, and interpret the shape of returned data.

> **Note**
>
> The examples in this topic cannot be run on their own.  They assume you have defined appropriate circuits, used the Samplomatic pass manager to add boxes and annotations, and used the Samplomatic `build` method to get a template circuit and samplex for each code block, as necessary.

## Quickstart example

This example demonstrates the core idea. It creates a parametric circuit and and five different parameter configurations. The executor runs all five configurations and returns data organized by configuration, with one result per classical register in each quantum program item.

The rest of this guide refers back to this example to explain how this works and how to build more complex sweeps, including Samplomatic-based randomization and inputs.

```python
import numpy as np
from qiskit.circuit import Parameter, QuantumCircuit
from qiskit_ibm_runtime import QiskitRuntimeService, Executor
from qiskit_ibm_runtime.quantum_program import QuantumProgram
from qiskit.transpiler import generate_preset_pass_manager

# A circuit with 2 parameters
# This circuit is used throughout the rest of this guide.
circuit = QuantumCircuit(4)
circuit.rx(Parameter("a"), 0)
circuit.rx(Parameter("b"), 1)
circuit.h(2)
circuit.cx(2, 3)
circuit.measure_all()

# 5 different parameter configurations (shape: 5 configurations × 2 parameters)
parameter_values = np.linspace(0, np.pi, 10).reshape(5, 2)

# Initialize the service and choose a backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# Transpile to ISA circuit
preset_pass_manager = generate_preset_pass_manager(
    backend=backend,
    optimization_level=3,
)
isa_circuit = preset_pass_manager.run(circuit)

# This program is used throughout the rest of this guide.
program = QuantumProgram(shots=1024)
program.append_circuit_item(isa_circuit, circuit_arguments=parameter_values)

# initialize an Executor with default options
executor = Executor(mode=backend)

# Run and get results
result = executor.run(program).result()

# result is a list with one entry per program item
# result[0] is a dict mapping classical register names to data arrays
# Output bool arrays have shape (5, 1024, 4)
#   5 = number of parameter configurations
#   1024 = number of shots
#   4 = bits in the classical register
result[0]["meas"]
```

## Intrinsic and extrinsic axes

Broadcasting only applies to *extrinsic* axes. The intrinsic axes are always preserved as specified.

- **Intrinsic axes** (rightmost): Determined by the data type. For example, if your circuit
  has three parameters, then parameter values require three numbers, giving an intrinsic shape
  of `(3,)`.

- **Extrinsic axes** (leftmost): Your sweep dimensions. These define how many configurations you
  want to run.

| Input type                               | Intrinsic shape | Example full shape                                  |
| ---------------------------------------- | --------------- | --------------------------------------------------- |
| Parameter values (n parameters)          | (n,)            | (5, 3) for five configurations and three parameters |
| Scalar inputs (for example, noise scale) | ()              | (4,) for four configurations                        |
| Observables (if applicable)              | varies          | Depends on observable type                          |

### Example

Consider a circuit with two parameters that you want to sweep over a 4x3 grid of configurations, varying parameter values and a noise scale factor:

```python
import numpy as np

# Parameter values: 4 configurations along axis 0, intrinsic shape (2,)
# Full shape: (4, 1, 2) - the "1" allows broadcasting with noise_scale
parameter_values = np.array([
    [[0.1, 0.2]],
    [[0.3, 0.4]],
    [[0.5, 0.6]],
    [[0.7, 0.8]],
])  # shape (4, 1, 2)

# Noise scale: 3 configurations, intrinsic shape () (scalar)
# Full shape: (3,)
noise_scale = np.array([0.8, 1.0, 1.2])  # shape (3,)

# Extrinsic shapes: (4, 1) and (3,) → broadcast to (4, 3)
# Result: 12 total configurations in a 4×3 grid
program.append_samplex_item(
    template_circuit,
    samplex=samplex,
    samplex_arguments={
        "parameter_values": parameter_values,
        "noise_scales.mod_ref1": noise_scale,
    },
)
```

The shapes are as follows:

| Input              | Full shape | Extrinsic shape | Intrinsic shape |
| ------------------ | ---------- | --------------- | --------------- |
| `parameter_values` | (4, 1, 2)  | (4, 1)          | (2,)            |
| `noise_scale`      | (3,)       | (3,)            | ()              |
| Broadcast          | None       | (4, 3)          | None            |

## Output array shapes

Output arrays follow the same extrinsic/intrinsic pattern:

- **Extrinsic shape:** Matches the broadcast shape of all inputs
- **Intrinsic shape:** Determined by the output type

The most common output is bitstring data from measurements, which is
formatted as an array of boolean values:

| Output type             | Intrinsic shape          | Description                      |
| ----------------------- | ------------------------ | -------------------------------- |
| Classical register data | (num\_shots, creg\_size) | Bitstring data from measurements |

### Example

If you provide inputs with extrinsic shapes `(4, 1)` and `(3,)`, the broadcast extrinsic
shape is `(4, 3)`. The following code uses a circuit with 1024 shots and a 4-bit classical register (as defined in the [Quickstart](#quickstart) example):

```python
# Input extrinsic shapes: (4, 1) and (3,) → (4, 3)
# Output for classical register "meas":
#   extrinsic: (4, 3)
#   intrinsic: (1024, 4)  - shots × bits
#   full shape: (4, 3, 1024, 4)

result = executor.run(program).result()
meas_data = result[0]["meas"]  # result[0] for first program item
print(meas_data.shape)  # (4, 3, 1024, 4)

# Access a specific configuration
config_2_1 = meas_data[2, 1, :, :]  # shape (1024, 4)
```

> **Note**
>
> Each configuration runs the full shot count specified in the quantum program. Shots are **not** divided among configurations. For example, if you request 1024 shots and have 10 configurations, each configuration runs 1024 shots (10,240 total shots executed).

## Randomization and the `shape` parameter

When using a [samplex](/docs/guides/directed-execution-model), each element of the extrinsic shape corresponds to an independent circuit
execution. The samplex typically injects randomness (for example, gate twirling) into each
execution, so even without explicitly requesting multiple randomizations, each element
receives a random realization.

You can use the `shape` parameter to augment the extrinsic shape for the item, effectively
adding axes that correspond specifically to randomizing the same configuration many times. It must be
broadcastable from the shape implicit in your `samplex_arguments`. Axes where `shape` exceeds
the implicit shape enumerate additional independent randomizations.

### No explicit randomization axes

If you omit `shape` (or set it to match your input shapes), you get one execution per
input configuration. Each execution is still randomized by the samplex, but with only a
single random realization you don't benefit from averaging over multiple randomizations.

> **Note**
>
> If you're accustomed to enabling twirling with a simple flag like `twirling=True`, note that
> the Executor requires you to explicitly request multiple randomizations with the `shape` argument to
> allow your post-processing routines to get the benefits of averaging over multiple
> randomizations. A single randomization (the default when `shape` is omitted) applies
> random gates but typically offers no advantage over running the base circuit without
> randomization.

The following example demonstrates the default behavior:

```python
program.append_samplex_item(
    template_circuit,
    samplex=samplex,
    samplex_arguments={
        "parameter_values": np.random.rand(10, 2),  # extrinsic (10,)
    },
    # shape defaults to (10,) - one randomized execution per config
)
# Output shape for "meas": (10, num_shots, creg_size)
```

### Single randomization axis

To run multiple randomizations per configuration, extend the shape with additional axes.
For example, the following code runs 20 randomizations for each of 10 parameter configurations:

```python
program.append_samplex_item(
    template_circuit,
    samplex=samplex,
    samplex_arguments={
        "parameter_values": np.random.rand(10, 2),  # extrinsic (10,)
    },
    shape=(20, 10),  # 20 randomizations × 10 configurations
)
# Output shape for "meas": (20, 10, num_shots, creg_size)
```

### Multiple randomization axes

You can organize randomizations into a multi-dimensional grid. This is useful for structured
analysis, for example, separating randomizations by type or grouping them for statistical processing.

Here, the input extrinsic shape `(10,)` broadcasts to the requested shape `(2, 14, 10)`,
with axes 0 and 1 filled by independent randomizations.

```python
program.append_samplex_item(
    template_circuit,
    samplex=samplex,
    samplex_arguments={
        "parameter_values": np.random.rand(10, 2),  # extrinsic (10,)
    },
    # 2×14=28 randomizations per configuration, 10 configurations
    # Or you could set shape=(28, 10) for the same effect
    shape=(2, 14, 10),  
)
# Output shape for "meas": (2, 14, 10, num_shots, creg_size)
```

### How `shape` and input shapes interact

The `shape` parameter must be broadcastable *from* your input extrinsic shapes. This means:

- Input shapes with size-1 dimensions can expand to match `shape`.
- Input shapes must align from the right with `shape`.
- Axes in `shape` that exceed the input dimensions enumerate randomizations.

Note that `shape` can contain size-1 dimensions
that expand to match input dimensions, as is illustrated in the last row of the following table.

Examples:

| Input extrinsic | Shape      | Result                                                            |
| --------------- | ---------- | ----------------------------------------------------------------- |
| (10,)           | (10,)      | 10 configurations, 1 randomization each                           |
| (10,)           | (5, 10)    | 10 configurations, 5 randomizations each                          |
| (10,)           | (2, 3, 10) | 10 configurations, 2×3=6 randomizations each                      |
| (4, 1)          | (4, 5)     | 4 configurations, 5 randomizations each                           |
| (4, 3)          | (2, 4, 3)  | 4×3=12 configurations, 2 randomizations each                      |
| (4, 3)          | (2, 1, 3)  | 4×3=12 configurations, 2 randomizations each (the 1 expands to 4) |

### Index into results

With randomization axes, you can index into specific randomization/parameter combinations:

```python
# Using shape=(2, 14, 10) with input extrinsic shape (10,), and 
# 1024 shots and 4 classical registers.
result = executor.run(program).result()
meas_data = result[0]["meas"]  # shape (2, 14, 10, 1024, 4)

# Get all shots for randomization (0, 7) and parameter config 3
specific = meas_data[0, 7, 3, :, :]  # shape (1024, 4)

# Average over all randomizations for parameter config 5 on bit 2
averaged = meas_data[:, :, 5, :, 2].mean(axis=(0, 1))
```

## Common patterns

### Sweep a single parameter

Use code like the following to sweep one parameter while holding others fixed:

```python
# Circuit has 2 parameters, sweep first one over 20 values
sweep_values = np.linspace(0, 2*np.pi, 20)

parameter_values = np.column_stack([
    sweep_values,
    np.full(20, 0.5),
])  # shape (20, 2)
```

### Creating a 2D grid sweep

To create a grid over three parameters:

```python
# Sweep param 0 over 10 values, param 1 over 8 values, param 2 fixed
p0 = np.linspace(0, np.pi, 10)[:, np.newaxis, np.newaxis]  # (10, 1, 1)
p1 = np.linspace(0, np.pi, 8)[np.newaxis, :, np.newaxis]   # (1, 8, 1)
p2 = np.array([[[0.5]]])                                   # (1, 1, 1)

parameter_values = np.broadcast_arrays(p0, p1, p2)
parameter_values = np.stack(parameter_values, axis=-1).squeeze()  # (10, 8, 3)

# Extrinsic shape: (10, 8), intrinsic shape: (3,)
```

### Combining multiple inputs

When combining inputs with different intrinsic shapes, align extrinsic dimensions using
size-1 axes:

```python
# 4 parameter configurations, 3 noise scales → 4×3 = 12 total configurations
parameter_values = np.random.rand(4, 1, 2)  # extrinsic (4, 1), intrinsic (2,)
noise_scale = np.array([0.8, 1.0, 1.2])     # extrinsic (3,), intrinsic ()

# Broadcasted extrinsic shape: (4, 3)
```

## Next steps

> **Recommendations**
>
> - Review the [broadcasting](/docs/guides/primitive-input-output#broadcasting) overview.
> - Understand [Executor inputs and outputs](/docs/guides/executor-input-output).
