Readout Post-Processing
This page explains how QAT’s granular readout pipeline transforms raw IQ acquisition data into integer state keys and numeric values. For shot-level filtering (pre-selection and post-selection), see Shot Selection (Pre & Post).
Pipeline order:
Equalise → Discriminate → PostSelect
Overview
The data flow spans three layers:
Hardware model — Qubits carry a configured discrimination method (
LinearMapToRealMethodorMaxLikelihoodMethod).IR builder — Frontends emit granular readout instructions from that method.
Runtime pass — Runtime applies instructions, builds masks, filters invalid shots, and formats final outputs.
Qubit.post_process_method
└─ LinearMapToRealMethod | MaxLikelihoodMethod
│
▼
measure_with_granular_post_processing() ← frontend parsers call this
├─ MeasureBlock
├─ emit_granular_post_processing()
│ └─ Equalise → Discriminate
└─ emit_post_select() (when post_selection=True; always emits PostSelect)
└─ PostSelect → see shot_selection
│
▼
AcquisitionPostprocessing pass (runtime)
├─ apply_equalise()
├─ apply_discriminate_instruction()
├─ apply_post_select() → validity_mask → see shot_selection
│
├─ global_mask = AND of all per-output masks
├─ filter all result arrays by global_mask
└─ store PostSelectionResult(shots_requested, shots_retained, mask)
│
▼
ResultTransform
└─ uses shots_retained as denominator for binary_count
Pipeline steps
Step 1 — Equalise
- class Equalise(**data)
Bases:
InstructionApply an affine transform in the IQ (complex) plane to readout data.
This is the first stage of the granular post-processing pipeline.
In superconducting qubit readout the downconverted IQ signal is distorted by three hardware imperfections: phase imbalance (LO quadrature paths not exactly 90° apart), gain imbalance (unequal I/Q amplifier chains), and DC offsets (mixer leakage and biases). As a result, raw
(I, Q)samples cluster on a distorted, offset ellipse rather than a compact point cloud, degrading any downstream discriminator.The
Equaliseinstruction corrects all three imperfections in a single real affine transform:\[\begin{split}\begin{pmatrix} I' \\ Q' \end{pmatrix} = A \begin{pmatrix} I \\ Q \end{pmatrix} + \begin{pmatrix} b_I \\ b_Q \end{pmatrix}\end{split}\]where
Ais a real 2×2 matrix (transform) and[b_I, b_Q]is the real offset vector (offset). The output is returned as a complex valueI' + j Q'.Each
Equaliseinstruction operates on a single readout channel. To equalise multiple channels, emit one instruction per channel with its ownoutput_variable.The default
transform(2×2 identity) and defaultoffset(zero vector) are a no-op pass-through for already-calibrated hardware.Runtime implementation:
qat.runtime.post_processing.apply_equalise().- 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].
Runtime implementation: apply_equalise().
Step 2 — Discriminate
- class Discriminate(**data)
Bases:
InstructionDiscriminate equalised values to integer state keys.
For the linear-map path a sign-based threshold comparison is used: values above
threshold→0, values at or below →1.For the maximum-likelihood path the nearest centroid in the complex plane determines the state’s dict key from
MaxLikelihoodMethod.states. Non-negative keys are allowed states written to the classical register; negative keys are disallowed and subsequently filtered byPostSelect. Whenp_min > 0, low-confidence shots are assignedBG_KEY.Exactly one of
thresholdormethodmust be provided.Runtime implementation:
qat.runtime.post_processing.apply_discriminate_instruction().- 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].
Runtime implementation: apply_discriminate_instruction().
Configuring classification methods
Configuration lives on Qubit via
post_process_method.
Note
post_process_method and legacy mean_z_map_args are mutually
exclusive.
- class LinearMapToRealMethod(**data)
Bases:
MethodBaseThreshold-based discriminator using a calibrated complex-to-real projection.
Projects each complex IQ readout onto a real value via:
\[v = \mathrm{Re}(a \cdot \mathrm{IQ} + b)\]where
aandbare the two entries ofmean_z_map_args. The result is threshold-classified: above threshold → state0, at-or-below → state1.Post-selection is not supported for this method; state outputs are always the non-negative integers
0and1. UseMaxLikelihoodMethodwith negative keys to configure post-selection on measurement outcomes.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.
-
mean_z_map_args:
list[complex]
-
method:
Literal[<MethodIndicator.LINEAR_MAP_COMPLEX_TO_REAL: 'linear_map_complex_to_real'>]
- model_config: ClassVar[ConfigDict] = {'extra': 'forbid', 'ser_json_inf_nan': 'constants', 'use_enum_values': False, 'validate_assignment': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
-
mean_z_map_args:
- class MaxLikelihoodMethod(**data)
Bases:
MethodBaseMaximum likelihood discriminator for qubit measurement results.
Each shot is assigned to the state
kwith the highest normalised likelihood:\[\tilde{p}_k(z) = \frac{L_k(z)}{\sum_j L_j(z)}, \quad L_k(z) = \exp\!\left(-\frac{|z - \mathrm{loc}_k|^2}{2\,\nu}\right)\]where \(\nu\) is the noise power (variance)
noise_est.Likelihoods are computed in log-domain with log-sum-exp stabilisation to avoid underflow on extreme outliers.
Outlier rejection — set
p_min > 0to automatically reject shots whose winning normalised likelihood falls below the threshold. Those shots are assignedBG_KEYand discarded byPostSelect. Defaultp_min=0.0disables the check entirely (zero overhead).Key encoding convention — the
statesdict maps an integer output key to anMLDiscriminateParams:Non-negative key (
≥ 0) → allowed state; the key value is written to the classical register.Negative key (
< 0) → disallowed state; shots assigned to these states are removed byPostSelect.
Runtime implementation:
qat.runtime.post_processing.apply_discriminate_instruction().- Parameters:
states¶ – Per-state IQ-plane centroids, keyed by integer output value (non-negative) or disallowed sentinel (negative).
noise_est¶ – Global Gaussian noise power (variance, \(\sigma^2\)). Must be strictly positive. Default
1.0.p_min¶ – Minimum normalised likelihood for acceptance. Must be in
[0, 1].transform¶ – Real
(2, 2)IQ affine pre-transform matrixA. IfNone(default), noEqualisestep is emitted.offset¶ – Real offset vector
[b_I, b_Q]for the affine pre-transform. IfNone(default), noEqualisestep is emitted.
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.
-
method:
Literal[<MethodIndicator.MAX_LIKELIHOOD: 'max_likelihood'>]
- model_config: ClassVar[ConfigDict] = {'extra': 'forbid', 'ser_json_inf_nan': 'constants', 'use_enum_values': False, 'validate_assignment': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
-
noise_est:
float
-
p_min:
float
-
states:
dict[int,MLDiscriminateParams]
- class MLDiscriminateParams(**data)
IQ-plane centroid for a single measurement outcome.
Each
MLDiscriminateParamsentry represents one possible measurement outcome. The integer output value and disallowed flag are encoded by the dict key in the parentMaxLikelihoodMethod’sstatesmapping:Non-negative key (
≥ 0) → allowed state; the key is the integer written to the classical register.Negative key (
< 0) → disallowed state; shots assigned here are filtered byPostSelect.
The optional
labelfield carries a human-readable name for the state (e.g."|0⟩"or"|10⟩"). It is purely informational — the runtime uses the dict key for discrimination and thelocationfor distance calculations.Example — a standard qubit with states |0⟩ and |1⟩:
MaxLikelihoodMethod(states={ 0: MLDiscriminateParams(location=1+0j, label="|0⟩"), 1: MLDiscriminateParams(location=-1+0j, label="|1⟩"), })
Example — qutrit classifier that rejects the leakage state (key -2):
MaxLikelihoodMethod(states={ 0: MLDiscriminateParams(location=1+0j, label="|0⟩"), 1: MLDiscriminateParams(location=-1+0j, label="|1⟩"), -2: MLDiscriminateParams(location=0+1j, label="|2⟩ (leakage)"), })
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.
-
label:
str|None
-
location:
complex
- model_config: ClassVar[ConfigDict] = {'extra': 'forbid', 'ser_json_inf_nan': 'constants', 'use_enum_values': False, 'validate_assignment': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
Example — attaching a method to a qubit:
from qat.model.device import Qubit
from qat.model.post_processing import LinearMapToRealMethod
qubit = Qubit(
...,
mean_z_map_args=None,
post_process_method=LinearMapToRealMethod(),
)
Step 3 — PostSelect
PostSelect follows Discriminate and marks shots for filtering based on
discriminated integer state keys. Shots with a negative key, or a key in
PostSelect.additional_disallowed, are marked invalid. For full details on
post-selection and pre-selection, see Shot Selection (Pre & Post).
Step 4 — Results format
ResultTransform formats final results by
results_format.
raw()Complex IQ arrays (equalised path output).
binary()Per-shot mapped int values.
binary_count(){label: count}from discriminated integer state keys (retained shots only).
Semantic matrix
Format |
Selection off |
Selection on (e.g. 3 of 10 filtered) |
|---|---|---|
|
10 complex IQ values |
7 complex IQ values |
|
10 mapped int values |
7 mapped int values |
|
|
|
When results_format is None, dynamic structure return is used.
IR encoding
measure_with_granular_post_processing()Frontend path (QASM2/QASM3/QIR/tket). Emits
MeasureBlock, optionalPostProcessing(MEAN, TIME)(SCOPE only), then granular instructions viaemit_granular_post_processing().measure_single_shot_z()Customer-facing path. Emits legacy
PostProcessing(LINEAR_MAP_COMPLEX_TO_REAL)and does not emit granular instructions.
Legacy PostProcessing instructions remain supported for
backward compatibility.
Runtime execution
AcquisitionPostprocessing applies
the full Equalise/Discriminate/PostSelect chain.
See Shot Selection (Pre & Post) for details on mask construction and filtering.
See also
Shot Selection (Pre & Post) — shot filtering (pre-selection & post-selection).
qat.ir.measure— instruction model docs (canonical API semantics).qat.runtime.post_processing— runtime helper docs.qat.model.post_processing— classification model docs.