qat.model.post_processing module

Post-processing method models for qubit measurement results.

This module defines the post-processing methods available for qubit measurement discrimination. The main interface is the PostProcessMethod union, which is a discriminated union over all supported post-processing method models.

The discriminator field is method, which is present in the base class MethodBase and is used by Pydantic to determine which concrete model to use when parsing or serializing data.

Supported methods:

Key encoding convention for MaxLikelihoodMethod

The states dict maps an integer output key to an MLDiscriminateParams. Non-negative keys ( 0) represent allowed states whose value will be written to the classical register. Negative keys (< 0) represent disallowed states; shots classified to those states are filtered out by PostSelect.

The reserved sentinel BG_KEY is used by the runtime when p_min > 0 rejects a shot.

Post-selection is only supported for MaxLikelihoodMethod. LinearMapToRealMethod always emits states 0 and 1 via a threshold discriminator and does not support post-selection.

BG_KEY = -99

Reserved integer key used by the runtime when p_min > 0 rejects a shot.

The runtime stores this value in the integer state array produced by apply_discriminate_instruction() for any shot whose normalised likelihood falls below MaxLikelihoodMethod.p_min. The subsequent PostSelect step filters all negative keys (including this one) from the results.

class LinearMapToRealMethod(**data)

Bases: MethodBase

Threshold-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 a and b are the two entries of mean_z_map_args. The result is threshold-classified: above threshold → state 0, at-or-below → state 1.

Post-selection is not supported for this method; state outputs are always the non-negative integers 0 and 1. Use MaxLikelihoodMethod with 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].

class MLDiscriminateParams(**data)

Bases: NoExtraFieldsModel

IQ-plane centroid for a single measurement outcome.

Each MLDiscriminateParams entry represents one possible measurement outcome. The integer output value and disallowed flag are encoded by the dict key in the parent MaxLikelihoodMethod’s states mapping:

  • 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 by PostSelect.

The optional label field 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 the location for 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].

class MaxLikelihoodMethod(**data)

Bases: MethodBase

Maximum likelihood discriminator for qubit measurement results.

Each shot is assigned to the state k with 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 > 0 to automatically reject shots whose winning normalised likelihood falls below the threshold. Those shots are assigned BG_KEY and discarded by PostSelect. Default p_min=0.0 disables the check entirely (zero overhead).

Key encoding convention — the states dict maps an integer output key to an MLDiscriminateParams:

  • 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 by PostSelect.

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 matrix A. If None (default), no Equalise step is emitted.

  • offset – Real offset vector [b_I, b_Q] for the affine pre-transform. If None (default), no Equalise step 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
offset: Optional[Annotated[PydArray]]
p_min: float
states: dict[int, MLDiscriminateParams]
transform: Optional[Annotated[PydArray]]
class MethodBase(**data)

Bases: NoExtraFieldsModel

Base class for all post-processing methods.

Contains the discriminator field.

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: MethodIndicator
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 MethodIndicator(value)

Bases: str, Enum

Enum for discriminating post-processing methods applied to qubit measurement results.

LINEAR_MAP_COMPLEX_TO_REAL = 'linear_map_complex_to_real'
MAX_LIKELIHOOD = 'max_likelihood'