Readout Post-Processing
This page explains how QAT’s granular readout pipeline transforms raw IQ acquisition data into state labels and numeric values. For shot-level filtering (pre-selection and post-selection), see Shot Selection (Pre & Post).
Pipeline order:
Equalise → Discriminate → PostSelect → Demap
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 → Demap
└─ emit_post_select() (if disallowed states configured)
└─ PostSelect (inserted before Demap) → see shot_selection
│
▼
AcquisitionPostprocessing pass (runtime)
├─ apply_equalise()
├─ apply_discriminate_instruction()
├─ apply_post_select() → validity_mask → see shot_selection
└─ apply_demap_instruction()
│
├─ 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 string state labels.
For the linear-map path a sign-based threshold comparison is used: values above
threshold→ label"0", values at or below → label"1". For the maximum-likelihood path the nearest centroid in the complex plane determines the state’s configured string label (MLStateMap.label).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 sign-classified: positive → state"0", non-positive → state"1".Set
disallowed_statesto filter out shots by their classified state label (e.g.{"1"}to discard shots not in the ground state).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.
- property declared_states: set[str]
All state labels known to this method.
- Returns:
{"0", "1"}for a standard threshold discriminator.
-
disallowed_states:
set[str]
-
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].
- 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 labelledBG_LABELand discarded byPostSelect. Defaultp_min=0.0disables the check entirely (zero overhead).Disallowed states — individual states can be marked
disallowed=TrueonMLStateMapfor erasure checks and pre-selection; these are independent ofp_minand handled by separatePostSelectinstructions.Runtime implementation:
qat.runtime.post_processing.apply_discriminate_instruction().- Parameters:
states¶ – Per-state IQ-plane centroids, labels and output values.
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.
- property declared_states: set[str]
All state labels known to this method.
Includes labels from
statesandBG_LABELwhenp_min > 0.- Returns:
Set of all recognised state label strings.
- property disallowed_states: set[str]
State labels marked
disallowed=True, plusBG_LABELifp_min > 0.- Returns:
Set of disallowed state label strings.
-
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:
list[MLStateMap]
- class MLStateMap(**data)
Demapping from a quantum state to its IQ-plane location and C-register output value.
Each
MLStateMapentry represents one possible measurement outcome. Thelabelis string routing key that threads through the discrimination pipeline (Discriminate→PostSelect→Demap). Theoutput_valueis the final integer written to the classical register afterDemaphas been applied.The
disallowedfield marks a state as invalid for use-cases such as erasure checks and pre-selection. Shots assigned to adisallowed=Truestate are subsequently removed byPostSelect.Example — a standard qubit with states |0⟩ and |1⟩:
MLStateMap(label="0", output_value=0, location=1+0j) MLStateMap(label="1", output_value=1, location=-1+0j)
Example — qutrit classifier that rejects the leakage state |2⟩:
MLStateMap(label="0", output_value=0, location=1+0j) MLStateMap(label="1", output_value=1, location=-1+0j) MLStateMap(label="2", output_value=2, location=0+1j, disallowed=True)
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.
-
disallowed:
bool
-
label:
str
-
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].
-
output_value:
int
-
disallowed:
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(disallowed_states=["1"]),
)
Step 3 — PostSelect
PostSelect sits between Discriminate and Demap and marks shots
for filtering based on discriminated state labels. For full details on
post-selection and pre-selection, see Shot Selection (Pre & Post).
Step 4 — Demap
- class Demap(**data)
Bases:
InstructionDe-map string state labels to final integer output values.
This is the final stage of the granular post-processing pipeline. Each shot is mapped from a string state label (produced by
Discriminate) to the configured integer output value written to the classical register.Runtime implementation:
qat.runtime.post_processing.apply_demap_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_demap_instruction().
Step 5 — 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 string labels (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/Demap 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.