Shot Selection (Pre & Post)

Shot selection discards individual shots whose discriminated state labels indicate the qubit was in an undesirable state. QAT supports two flavours:

  • Post-selection — filters shots based on the end-of-circuit measurement result (e.g. discard leakage states).

  • Pre-selection — filters shots based on a measurement injected before the circuit begins, verifying each qubit starts in its ground state.

Both mechanisms produce per-output boolean validity masks. The runtime ANDs all masks together in _build_and_apply_global_mask() so that a shot is retained only if it passes every check.

Post-selection

Post-selection discards shots where a qubit’s end-of-circuit measurement lands in a disallowed state (e.g. a leakage state in a multi-level system).

Configuring disallowed states

Disallowed states are declared on the classification method attached to each qubit. For MaxLikelihoodMethod, set disallowed on individual state maps:

from qat.model.post_processing import MaxLikelihoodMethod, MLStateMap

method = MaxLikelihoodMethod(
    states=[
        MLStateMap(label="|01>", output_value=0.0, location=1+0j),
        MLStateMap(label="|10>", output_value=1.0, location=-1+0j),
        MLStateMap(
            label="|00>", output_value=2.0, location=0+1j,
            disallowed=True,
        ),
        MLStateMap(
            label="|11>", output_value=3.0, location=0-1j,
            disallowed=True,
        ),
    ],
)

For LinearMapToRealMethod, use the disallowed_states parameter:

from qat.model.post_processing import LinearMapToRealMethod

# Discards shots where the qubit is measured in the excited state (|1>).
# This is commonly used in single-qubit transmon systems to reject leakage
# or incorrectly prepared initial states.
method = LinearMapToRealMethod(disallowed_states={"1"})

The PostSelect instruction

class PostSelect(**data)

Bases: Instruction

Mark shots for filtering based on discriminated state labels.

PostSelect follows Discriminate in the readout pipeline. It does not remove shots inline — instead it records a per-output boolean validity mask. Shots whose state label appears in disallowed_states are marked invalid; the runtime ANDs all masks together and filters once at the end.

Emitting this instruction with an empty disallowed_states set is a no-op (all shots are considered valid) but is safe to emit unconditionally so that the pipeline structure is uniform.

Runtime implementation: qat.runtime.post_processing.apply_post_select().

See also

Shot Selection (Pre & Post) for full pre/post-selection docs.

Parameters:
  • output_variable – Variable name whose state labels should be screened.

  • disallowed_states – String state labels that should be marked invalid. Shots mapped to these labels will have their validity mask entry set to False. Order is not significant.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

model_config: ClassVar[ConfigDict] = {'extra': 'ignore', 'use_enum_values': False, 'validate_assignment': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

IR encoding

Frontends that require post-selection call emit_post_select(), which inserts a complete PostSelect instruction block immediately after the corresponding Discriminate instruction.

Runtime mask logic

AcquisitionPostprocessing collects per-output masks from every PostSelect instruction (both end-of-circuit post-selection and ahead-of-circuit pre-selection), computes a single global mask via AND, filters all result arrays, and stores metadata:

class PostSelectionResult(shots_requested, shots_retained, global_mask=None)

Records the outcome of post-selection applied during acquisition post-processing.

Parameters:
  • shots_requested (int) – Total shots compiled and executed.

  • shots_retained (int) – Number of shots that survived post-selection.

  • global_mask (Optional[ndarray]) – Boolean array of shape (shots_requested,) where True marks a retained shot. None if no post-selection was applied.

Pre-selection

Pre-selection verifies that every flagged qubit starts in its ground state before the quantum algorithm begins. The compiler injects a measurement-and-discriminate step at the start of the circuit; shots in which a qubit is found in a disallowed state are discarded at runtime.

Enabling pre-selection

Pre-selection is controlled by two settings:

  1. Global flag — set pre_selection=True on CompilerConfig.

  2. Per-qubit field — set preselect_disallowed_states to a set of state labels that should cause the shot to be discarded. When this field is empty (the default), pre-selection is not active for that qubit.

Only qubits where both settings are active receive pre-selection measurements.

Example:

from compiler_config.config import CompilerConfig
from qat.model.loaders.lucy import LucyModelLoader

model = LucyModelLoader(qubit_count=1).load()

# Configure qubit 0 for pre-selection.
qubit = model.qubits[0]
qubit.preselect_disallowed_states = {"1"}  # discard excited state

config = CompilerConfig(pre_selection=True, repeats=1000)

For multi-state hardware (e.g. transmon with four explicit states), specify all states that are not the ground state:

qubit.preselect_disallowed_states = {"|10>", "|00>", "|11>"}

How it works

The InsertPreSelectionMeasurement pass runs early in the middleend pipeline. For each qualifying qubit it injects the following instructions immediately after the Repeat instruction, before the circuit body:

Per-qubit block (output variable "presel_{qubit_index}"):

  • Pulse — readout tone on the measure channel.

  • Delay (acquire.delay) — ring-up offset on the acquire channel; waits for the resonator to respond before sampling starts.

  • Acquire with PRE_SELECTION.

  • Delay (resonator.relaxation_delay) — ring-down settle on the acquire channel; waits for the resonator to drain before the next operation. This delay is typically a few microseconds, allowing the readout resonator to return to its equilibrium state.

  • Equalise (when calibrated; omitted if equalisation calibration data is unavailable).

  • Discriminate.

  • PostSelect with the configured disallowed states.

Global synchronisation: a single Synchronize covering all qubit channels is emitted before the first qubit block and again after the last. The trailing sync simultaneously realigns drive/measure/acquire channels (which diverge during the readout window) and provides the cross-qubit alignment barrier before the circuit begins.

The presel_* output variable holds string state labels after Discriminate and is for internal runtime use only — it drives the validity mask but is never returned to the user. For per-shot debug access, inspect the DiscriminateResult stored in the ResultManager after execution. Pre-selection statistics (for example, shots requested/retained counts) are recorded in post-selection metadata such as PostSelectionResult, available via the ResultManager.

Because the pre-selection acquire has purpose=PRE_SELECTION, NoMidCircuitMeasurementValidation skips it when checking for mid-circuit measurements (the validation pass focuses on user-defined measurements; compiler-inserted pre-selection measurements are intentionally allowed).

Channel timeline for a two-qubit circuit:

        sequenceDiagram
    participant Q0D as Q0 Drive
    participant Q0M as Q0 Measure
    participant Q0A as Q0 Acquire
    participant Q1D as Q1 Drive
    participant Q1M as Q1 Measure
    participant Q1A as Q1 Acquire

    Note over Q0D,Q1A: Synchronize(all_qubit_channels) — t=T0

    Q0M->>Q0M: Pulse (readout tone)
    Q0A->>Q0A: Delay (acquire.delay)
    Q0A->>Q0A: Acquire [PRE_SELECTION]
    Q0A->>Q0A: Delay (relaxation_delay)
    Note over Q0D,Q0A: drive@T0, measure@T0+W, acquire@T0+acq_delay+D+acq_dur
    Note over Q0D,Q0A: Equalise → Discriminate → PostSelect

    Q1M->>Q1M: Pulse (readout tone)
    Q1A->>Q1A: Delay (acquire.delay)
    Q1A->>Q1A: Acquire [PRE_SELECTION]
    Q1A->>Q1A: Delay (relaxation_delay)
    Note over Q1D,Q1A: same pattern for Q1
    Note over Q1D,Q1A: Equalise → Discriminate → PostSelect

    Note over Q0D,Q1A: Synchronize(all_qubit_channels)
    Note over Q0D,Q1A: all channels realigned — circuit begins
    

Combined behaviour

When both pre-selection and post-selection are active, each produces its own per-output validity mask. The runtime ANDs all masks together so that a shot is retained only if it passes both checks.

Note

Two separate disallowed-states settings exist and serve different purposes:

  • disallowed_states on the post-processing method (e.g. MLStateMap.disallowed, LinearMapToRealMethod.disallowed_states) — controls post-selection on end-of-circuit measurements. Frontends read these when emitting PostSelect instructions for normal measure operations.

  • preselect_disallowed_states on Qubit — controls pre-selection. The middleend pass uses these for the measurement injected before the circuit begins.

These are independent: neither overrides the other. A shot must pass both checks to be retained.

Results format impact — the semantic matrix in results_format_semantics applies identically: filtered shots are removed from raw(), binary(), and binary_count() outputs, and shots_retained reflects the combined surviving count.

Error mitigation impact — shot selection removes data before error mitigation techniques are applied. If using mitigation methods that rely on shot statistics (e.g. readout error mitigation), ensure the calibration data and the filtered dataset are consistent in their shot selection criteria to avoid biasing the mitigation matrix.

API reference

  • InsertPreSelectionMeasurement — middleend pass that injects pre-selection measurements.

  • PostSelect — IR instruction for shot filtering.

  • AcquirePurpose — enum distinguishing measurement vs pre-selection acquires. This is independent of AcquireMode; AcquirePurpose indicates why the acquisition is happening (normal measurement, pre-selection, etc.), while AcquireMode controls how the hardware performs the acquisition (scope, integrator, etc.).

  • preselect_disallowed_states — per-qubit pre-selection configuration.

  • PostSelectionResult — runtime metadata for filtered shots. Note: post-selection disallowed states are configured per-qubit on the post-processing method; pre-selection uses the per-qubit preselect_disallowed_states attribute.

See also