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:
InstructionMark shots for filtering based on discriminated state labels.
PostSelectfollowsDiscriminatein the readout pipeline. It does not remove shots inline — instead it records a per-output boolean validity mask. Shots whose state label appears indisallowed_statesare marked invalid; the runtime ANDs all masks together and filters once at the end.Emitting this instruction with an empty
disallowed_statesset 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:
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.
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:
Global flag — set
pre_selection=TrueonCompilerConfig.Per-qubit field — set
preselect_disallowed_statesto 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.AcquirewithPRE_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).PostSelectwith 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_stateson the post-processing method (e.g.MLStateMap.disallowed,LinearMapToRealMethod.disallowed_states) — controls post-selection on end-of-circuit measurements. Frontends read these when emittingPostSelectinstructions for normalmeasureoperations.preselect_disallowed_statesonQubit— 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 ofAcquireMode;AcquirePurposeindicates why the acquisition is happening (normal measurement, pre-selection, etc.), whileAcquireModecontrols 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-qubitpreselect_disallowed_statesattribute.
See also
Readout Post-Processing — readout signal processing (Equalise → Discriminate → Demap for end-of-circuit measurements).
qat.ir.measure— instruction model docs.qat.runtime.post_processing— runtime helper docs.