CUDA-Q QEC Python API

Code

class cudaq_qec.Code(*args, **kwargs)

Represents a quantum error correction code

contains_operation

Return true if this code contains the given operation encoding

get_num_ancilla_qubits

Total number of ancilla qubits required by the code.

get_num_ancilla_x_qubits

Number of X-type ancilla qubits used for X stabilizer measurements.

get_num_ancilla_z_qubits

Number of Z-type ancilla qubits used for Z stabilizer measurements.

get_num_data_qubits

Total number of physical data qubits required by the code.

get_num_x_stabilizers

Number of X-type stabilizers.

get_num_z_stabilizers

Number of Z-type stabilizers.

get_observables_x

Get the Pauli X observables of the code

get_observables_z

Get the Pauli Z observables of the code

get_operation_one_qubit

Get a CUDA-Q Python kernel for a one-qubit logical operation.

Returns:

A valid CUDA-Q Python kernel object.

get_operation_two_qubit

Get a CUDA-Q Python kernel for a two-qubit logical operation.

Returns:

A valid CUDA-Q Python kernel object.

get_parity

Get the parity check matrix of the code

get_parity_x

Get the X-type parity check matrix of the code

get_parity_z

Get the Z-type parity check matrix of the code

get_pauli_observables_matrix

Get a matrix of the Pauli observables of the code

get_stabilizer_round

Get a CUDA-Q Python kernel for a stabilizer round logical operation.

Returns:

A valid CUDA-Q Python kernel object.

get_stabilizer_schedule_x

Get the X-stabilizer schedule matrix passed to the code’s stabilizer_round kernel. Entry 0 = no support, entry k >= 1 = interaction at timestep k; defaults to the X-type parity check matrix (every interaction at timestep 1).

get_stabilizer_schedule_z

Get the Z-stabilizer schedule matrix passed to the code’s stabilizer_round kernel. Entry 0 = no support, entry k >= 1 = interaction at timestep k; defaults to the Z-type parity check matrix (every interaction at timestep 1).

get_stabilizers

Get the stabilizer generators of the code

Surface code layout

The rotated surface code exposes a grid helper for stabilizer and data-qubit indexing. In Python it is available as cudaq_qec.stabilizer_grid (call cudaq_qec.stabilizer_grid(distance)). The C++ type is cudaq::qec::surface_code::stabilizer_grid (API).

class cudaq_qec.stabilizer_grid(*args, **kwargs)
property data_coords

(self) -> list[cudaq_qec.vec2d]

property data_indices

(self) -> dict

property distance

(self) -> int

format_data_grid
format_stabilizer_coords
format_stabilizer_grid
format_stabilizer_indices
format_stabilizers
get_cnot_schedule_pairs_x

Return the X-stabilizer CNOT schedule as a flat list of (stabilizer index, data index) pairs ordered by timestep within each stabilizer.

get_cnot_schedule_pairs_z

Return the Z-stabilizer CNOT schedule as a flat list of (stabilizer index, data index) pairs ordered by timestep within each stabilizer.

get_cnot_schedule_x

Return the X-stabilizer CNOT schedule matrix as a numpy array whose rows match the sorted parity-matrix rows. Entry 0 = no support, k >= 1 = CNOT timestep, ordered so that ancilla (hook) errors land perpendicular to the logical operators.

get_cnot_schedule_z

Return the Z-stabilizer CNOT schedule matrix as a numpy array whose rows match the sorted parity-matrix rows. Entry 0 = no support, k >= 1 = CNOT timestep, ordered so that ancilla (hook) errors land perpendicular to the logical operators.

get_spin_op_observables

Return the logical observables as [X, Z] cudaq::spin_op_term entries

get_spin_op_stabilizers

Return the stabilizers as a list of cudaq::spin_op_term

property grid_length

(self) -> int

property orientation

(self) -> cudaq_qec.sc_orientation

property roles

(self) -> list[cudaq_qec.surface_role]

property x_stab_coords

(self) -> list[cudaq_qec.vec2d]

property x_stab_indices

(self) -> dict

property x_stabilizers

(self) -> list[list[int]]

property z_stab_coords

(self) -> list[cudaq_qec.vec2d]

property z_stab_indices

(self) -> dict

property z_stabilizers

(self) -> list[list[int]]

Detector Error Model

class cudaq_qec.DetectorErrorModel(*args, **kwargs)

A detector error model (DEM) for a quantum error correction circuit. A DEM can be created from a QEC circuit and a noise model. It contains information about which errors flip which detectors. This is used by the decoder to help make predictions about observables flips.

canonicalize_for_rounds

Canonicalize the detector error model for a given number of rounds.

Columns sharing the same detector and observable signature are merged, with rates composed to match the input model. By default, zero-syndrome columns that still flip an observable (undetectable logical errors) are retained so the model’s observable-flip probability is preserved. Set remove_zero_syndrome_errors=True to drop all columns with no detector signature, which is appropriate when the canonicalized DEM is consumed only for round-based decoding.

Canonicalization does not preserve cross-column exclusivity structure: each output column is given a fresh unique error id and treated as independent of every other column, so any error_ids correlation in the input model is discarded.

canonicalize_for_rounds_with_boundary

Boundary-aware canonicalization for memory-experiment DEMs whose first and last detector layers (the boundaries) are narrower than the interior layers. The first num_boundary_syndromes detector rows form the initial round, each subsequent block of num_syndromes_per_round rows is an interior round, and the trailing num_boundary_syndromes rows form the final round. This makes the round-based column ordering respect the true rounds even when the boundary width differs from the interior width.

remove_zero_syndrome_errors behaves as in canonicalize_for_rounds. Raises ValueError if num_syndromes_per_round is zero or num_boundary_syndromes exceeds num_syndromes_per_round.

property detector_error_matrix

The detector error matrix is a specific kind of circuit-level parity-check matrix where each row represents a detector, and each column represents an error mechanism. The entries of this matrix are H[i,j] = 1 if detector i is triggered by error mechanism j, and 0 otherwise.

property error_ids

Error mechanism ID. From a probability perspective, each error mechanism ID is independent of all other error mechanism ID. For all errors with the same ID, only one of them can happen. That is - the errors containing the same ID are correlated with each other.

property error_rates

The list of weights has length equal to the number of columns of the detector error matrix, which assigns a likelihood to each error mechanism.

num_detectors

The number of detectors in the detector error model

num_error_mechanisms

The number of error mechanisms in the detector error model

num_observables

The number of observables in the detector error model

property observables_flips_matrix

The observables flips matrix is a specific kind of circuit-level parity- check matrix where each row represents a Pauli observable, and each column represents an error mechanism. The entries of this matrix are O[i,j] = 1 if Pauli observable i is flipped by error mechanism j, and 0 otherwise.

class cudaq_qec.DecoderContext

Lazy handle returned by decoder_context_from_memory_circuit.

Stores the raw circuit analysis. Each *_component method canonicalizes exactly the requested stabilizer type and returns a (dem, m2d, m2o) tuple.

full_component

Canonicalize both stabilizer types with boundary awareness; return (dem, m2d, m2o).

dem is the canonicalized DetectorErrorModel, m2d and m2o are lists of lists of measurement indices.

property num_measurements

Total number of measurements per shot.

x_component

Canonicalize X-stabilizer detectors; return (dem, m2d, m2o).

dem is the canonicalized DetectorErrorModel, m2d and m2o are lists of lists of measurement indices.

z_component

Canonicalize Z-stabilizer detectors; return (dem, m2d, m2o).

dem is the canonicalized DetectorErrorModel, m2d and m2o are lists of lists of measurement indices.

Note

The x_component(), z_component(), and full_component() methods each return a (dem, m2d, m2o) tuple:

  • dem (DetectorErrorModel) — canonicalized detector error model

  • m2d (list[list[int]]) — measurement-to-detector map; m2d[d] lists the measurement indices whose XOR forms detector d

  • m2o (list[list[int]]) — measurement-to-observable map

Pass m2d to d_sparse() to produce the D_sparse vector for a real-time decoder config.

cudaq_qec.dem_from_memory_circuit(code: cudaq_qec.Code, op: cudaq_qec.operation, numRounds: int, noise: cudaq.NoiseModel | None = None, decompose_errors: bool = False) cudaq_qec.DetectorErrorModel

Generate a detector error model from a memory circuit.

This function generates a detector error model from a memory circuit. The memory circuit is specified by the code, the initial state preparation operation, and the number of stabilizer measurement rounds. The noise model is required.

Parameters:
  • code – The code to generate the detector error model for.

  • op – The initial state preparation operation.

  • numRounds – The number of stabilizer measurement rounds.

  • noise – The noise model to apply to the memory circuit.

  • decompose_errors – If True, hyperedge error mechanisms are decomposed into pairs of two-detector edges by Stim before returning.

Returns:

A detector error model.

cudaq_qec.x_dem_from_memory_circuit(code: cudaq_qec.Code, op: cudaq_qec.operation, numRounds: int, noise: cudaq.NoiseModel | None = None, decompose_errors: bool = False) cudaq_qec.DetectorErrorModel

Generate a detector error model from a memory circuit in the X basis.

This function generates a detector error model from a memory circuit in the X basis. The memory circuit is specified by the code, the initial state preparation operation, and the number of stabilizer measurement rounds. The noise model is required.

Parameters:
  • code – The code to generate the detector error model for.

  • op – The initial state preparation operation.

  • numRounds – The number of stabilizer measurement rounds.

  • noise – The noise model to apply to the memory circuit.

  • decompose_errors – If True, hyperedge error mechanisms are decomposed into pairs of two-detector edges by Stim before returning.

Returns:

A detector error model.

cudaq_qec.z_dem_from_memory_circuit(code: cudaq_qec.Code, op: cudaq_qec.operation, numRounds: int, noise: cudaq.NoiseModel | None = None, decompose_errors: bool = False) cudaq_qec.DetectorErrorModel

Generate a detector error model from a memory circuit in the Z basis.

This function generates a detector error model from a memory circuit in the Z basis. The memory circuit is specified by the code, the initial state preparation operation, and the number of stabilizer measurement rounds. The noise model is required.

Parameters:
  • code – The code to generate the detector error model for.

  • op – The initial state preparation operation.

  • numRounds – The number of stabilizer measurement rounds.

  • noise – The noise model to apply to the memory circuit.

  • decompose_errors – If True, hyperedge error mechanisms are decomposed into pairs of two-detector edges by Stim before returning.

Returns:

A detector error model.

cudaq_qec.decoder_context_from_memory_circuit(code: cudaq_qec.Code, op: cudaq_qec.operation, numRounds: int, noise: cudaq.NoiseModel | None = None, decompose_errors: bool = False) cudaq_qec.DecoderContext

Run a memory-circuit analysis and return a lazy DecoderContext handle.

Executes dem_from_kernel once and stores the raw result. Canonicalization is deferred: call x_component(), z_component(), or full_component() on the returned handle to canonicalize exactly the stabilizer type needed.

Parameters:
  • code – The code to characterize.

  • op – The initial state preparation operation.

  • numRounds – The number of stabilizer measurement rounds.

  • noise – The noise model to apply to the memory circuit.

  • decompose_errors – If True, hyperedge error mechanisms are decomposed into pairs of two-detector edges by Stim before returning.

Returns:

A DecoderContext handle; call a component method to obtain the canonicalized (dem, m2d, m2o) tuple.

cudaq_qec.dem_from_stim_text(dem_text: str, use_decomp_suggestions: bool = False) cudaq_qec.DetectorErrorModel

Parse a Stim detector error model string into a DetectorErrorModel.

Parameters:
  • dem_text – A Stim detector error model string.

  • use_decomp_suggestions – If error mechanism separated by ^ are decomposed

cudaq_qec.d_sparse(m2d: collections.abc.Sequence[collections.abc.Sequence[int]]) list[int]

Flatten a measurement-to-detector map into the -1-terminated sparse vector a realtime decoder config expects for its D_sparse.

m2d is a list of lists: m2d[d] contains the measurement indices whose XOR forms detector d. Each detector’s indices are emitted in order, followed by -1.

This is a standalone helper for consumers who hold an m2d map (e.g., extracted from a decoder_inputs returned by a component method) and want to build the D_sparse vector without going through a DecoderContext.

Decoder Interfaces

class cudaq_qec.Decoder(*args, **kwargs)

Represents a decoder for quantum error correction

decode

Decode the given syndrome to determine the error correction

decode_async

Asynchronously decode the given syndrome

decode_batch

Decode multiple syndromes and return the results

get_block_size

Get the size of the code block

get_syndrome_size

Get the size of the syndrome

get_version

Get the version of the decoder

class cudaq_qec.DecoderResult(*args, **kwargs)

Single-shot decoder result.

Returned by decoder.decode(...). Carries the convergence flag, the decoded correction chain, and optional decoder-specific metadata.

Like BatchDecoderResult, this is conceptually output-only — user code should not need to construct or mutate one. Unlike BatchDecoderResult, the no-arg constructor and writable fields are preserved here because Python decoder plugins implementing a decode override use the construct-then-mutate pattern:

res = DecoderResult() res.converged = True res.result = np.arange(…) return res

Tightening this construction surface would break every existing Python decoder plugin and is deferred to a future change.

property converged

Boolean flag indicating if the decoder converged to a solution.

True if the decoder successfully found a valid correction chain, False if the decoder failed to converge or exceeded iteration limits.

property opt_results

Optional additional results from the decoder stored in a heterogeneous map.

This field may be empty if no additional results are available.

property result

The decoded correction chain or recovery operation.

Contains the sequence of corrections that should be applied to recover the original quantum state. The format depends on the specific decoder implementation.

Returns a 1-D NumPy array of the configured QEC floating point dtype (float64 in standard wheels). A fresh array is allocated per access — the underlying storage is a std::vector<float_t> and the data is copied out on read.

Accepts any sequence of floats on assignment; this is the path Python decoder plugins use in their decode overrides (see class docstring).

class cudaq_qec.BatchDecoderResult(*args, **kwargs)

Batched decoder result.

Produced by decoder.decode_batch(...). This type is output-only: it carries decoder output back to the caller and is not parsed by decoders. User code should not need to construct one — call decoder.decode_batch(...) and read the result.

Python decoder plugins implementing a decode_batch override may use the constructor to produce one. result should be a 2-D NumPy array of the configured QEC floating point dtype (float64 in standard wheels); converged should be a 1-D NumPy bool array; opt_results is a list of per-shot dicts or None entries. The constructor coerces result and converged to C-contiguous storage of the expected dtype (via np.ascontiguousarray), copying when the input doesn’t already satisfy those invariants. Wrong rank (e.g. 1-D result) is rejected with TypeError.

An empty batch (zero syndromes) yields result.shape == (0, 0) and converged.shape == (0,). The per-shot width is unknown without running a decode and depends on decoder mode, so result.shape[1] is only meaningful when the batch is non-empty.

Access patterns, fastest to slowest:

  1. Vectorized: read result, converged, or opt_results directly. The properties return the underlying NumPy arrays and Python list with no copy. This is the recommended path for batch processing.

  2. Slicing: batch[a:b] returns another BatchDecoderResult that shares data with the parent. NumPy basic slicing — including stepped slices like batch[::2] — returns views, so no data is copied. The opt results list slice creates a new Python list, but its entries are shared references.

  3. Integer indexing / iteration: batch[i] or for r in batch: yields a DecoderResult copy of one shot. This compatibility surface exists for code written against the previous list[DecoderResult] return type. Each access copies the row out into a fresh per-shot buffer because DecoderResult’s underlying storage (std::vector<float_t>) cannot alias into the batch’s packed NumPy array — the layouts are incompatible. Avoid in hot loops; prefer pattern 1.

property converged

A one-dimensional NumPy bool array indicating convergence per shot.

property opt_results

A list of per-shot optional result dictionaries, or None entries.

property result

A two-dimensional NumPy array of decoder outputs, with one row per shot.

This is the fast path for batch consumers. Its dtype is the configured QEC floating point type.

class cudaq_qec.AsyncDecoderResult

A future-like object that holds the result of an asynchronous decoder call. Call get() to block until the result is available.

get

Return the decoder result (blocking until ready)

ready

Return True if the asynchronous decoder result is ready, False otherwise

Note

NumPy result arrays — As of 0.7.0, the result field of cudaq_qec.DecoderResult (and the per-shot results returned by cudaq_qec.BatchDecoderResult and cudaq_qec.AsyncDecoderResult) is a 1-D NumPy array rather than a Python list. Indexing and iteration are unchanged, but code that relied on the result being a list specifically (for example isinstance(res, list) or list-only methods) should be updated.

cudaq_qec.get_decoder(arg0: str, arg1: object, /, **kwargs) object | cudaq_qec.Decoder

Get a decoder by name.

H may be:

  • A scipy sparse matrix (CSR, CSC, COO, or any scipy.sparse format): the preferred input — no dense allocation occurs, and any format is normalised to CSR internally before building the C++ sparse storage.

  • A dense 2D NumPy uint8 array in row-major order: a full dense cudaqx::tensor is built first, then converted to CSC sparse storage. For large PCMs this can allocate as much memory as rows * cols.

  • A Stim detector error model string: native C++ decoders receive the raw DEM text via decoder_init; Python-registered decoders receive the DEM-derived PCM plus O and error_rate_vec defaults.

For Python-registered decoders (cudaq.qec.decoder decorator), H is passed through to __init__ unchanged (NumPy array or scipy sparse matrix). DEM string inputs are parsed first as described above. Call Decoder.__init__(self, H) so nanobind can store the PCM internally without building a dense rows x cols allocation.

Note

scipy.sparse interopcudaq_qec.get_decoder() and cudaq_qec.Decoder accept a scipy.sparse matrix (CSR, CSC, COO, or any other scipy.sparse format) as the parity-check matrix H. This is the preferred form for large PCMs because no dense rows x cols allocation is made — the matrix is normalised to CSR internally. Dense NumPy uint8 arrays remain supported. The PCM utilities cudaq_qec.reorder_pcm_columns(), cudaq_qec.shuffle_pcm_columns(), and cudaq_qec.pcm_to_sparse_vec() also accept SciPy sparse matrices without creating a dense cudaqx::tensor. Reordering and shuffling a sparse input returns a scipy.sparse.csc_matrix; a dense input continues to return a NumPy array.

scipy is an optional dependency; if it is not installed, pass a dense NumPy array instead.

Built-in Decoders

NVIDIA QLDPC Decoder

class cudaq_qec.nv_qldpc_decoder

A general purpose Quantum Low-Density Parity-Check Decoder (QLDPC) decoder based on GPU accelerated belief propagation (BP). Since belief propagation is an iterative method, decoding can be improved with a second-stage post-processing step. Optionally, ordered statistics decoding (OSD) can be chosen to perform the second stage of decoding.

An [[n,k,d]] quantum error correction (QEC) code encodes k logical qubits into an n qubit data block, with a code distance d. Quantum low-density parity-check (QLDPC) codes are characterized by sparse parity-check matrices (or Tanner graphs), corresponding to a bounded number of parity checks per data qubit.

Requires a CUDA-Q compatible GPU. See the CUDA-Q GPU Compatibility List for a list of valid GPU configurations.

References: Decoding Across the Quantum LDPC Code Landscape

Note

It is required to create decoders with the get_decoder API from the CUDA-QX extension points API, such as

import cudaq_qec as qec
import numpy as np
H = np.array([[1, 0, 0, 1, 0, 1, 1],
              [0, 1, 0, 1, 1, 0, 1],
              [0, 0, 1, 0, 1, 1, 1]], dtype=np.uint8) # sample 3x7 PCM
opts = dict() # see below for options
# H may also be a scipy.sparse matrix (CSR, CSC, COO, or any
# other scipy.sparse format), which avoids a full dense rows×cols
# allocation for large PCMs.  Any format is normalised to CSR
# internally; no call to .toarray() or .todense() is needed.
nvdec = qec.get_decoder('nv-qldpc-decoder', H, **opts)
std::size_t block_size = 7;
std::size_t syndrome_size = 3;
cudaqx::tensor<uint8_t> H;

std::vector<uint8_t> H_vec = {1, 0, 0, 1, 0, 1, 1,
                              0, 1, 0, 1, 1, 0, 1,
                              0, 0, 1, 0, 1, 1, 1};
H.copy(H_vec.data(), {syndrome_size, block_size});

cudaqx::heterogeneous_map nv_custom_args;
nv_custom_args.insert("use_osd", true);
// See below for options

auto nvdec = cudaq::qec::get_decoder("nv-qldpc-decoder", H, nv_custom_args);

Note

The "nv-qldpc-decoder" implements the cudaq_qec.Decoder interface for Python and the cudaq::qec::decoder interface for C++, so it supports all the methods in those respective classes.

Parameters:
  • H – Parity check matrix (tensor format)

  • params

    Heterogeneous map of parameters:

    • cuda_device_id (int): Zero-based CUDA device ordinal on which to construct the decoder and run every decode. Must be >= 0 and less than the number of visible GPUs. When omitted, the decoder is not pinned to a specific device and runs on the default device (GPU 0). Introduced in 0.7.0.

    • use_sparsity (bool): Whether or not to use a sparse matrix solver

    • error_rate (double): Probability of an error (in 0-1 range) on a block data bit (defaults to 0.001)

    • error_rate_vec (double): Vector of length “block size” containing the probability of an error (in 0-1 range) on a block data bit (defaults to 0.001). This overrides error_rate.

    • max_iterations (int): Maximum number of BP iterations to perform (defaults to 30)

    • n_threads (int): Number of CUDA threads to use for the GPU decoder (defaults to smart selection based on parity matrix size)

    • use_osd (bool): Whether or not to use an OSD post processor if the initial BP algorithm fails to converge on a solution

    • osd_method (int): 1=OSD-0, 2=Exhaustive, 3=Combination Sweep (defaults to 1). Ignored unless use_osd is true.

    • osd_order (int): OSD postprocessor order (defaults to 0). Ref: Decoding Across the Quantum LDPC Code Landscape

      • For osd_method=2 (Exhaustive), the number of possible permutations searched after OSD-0 grows by 2^osd_order.

      • For osd_method=3 (Combination Sweep), this is the λ parameter. All weight 1 permutations and the first λ bits worth of weight 2 permutations are searched after OSD-0. This is (syndrome_length - block_size + λ * (λ - 1) / 2) additional permutations.

      • For other osd_method values, this is ignored.

    • bp_batch_size (int): Number of syndromes that will be decoded in parallel for the BP decoder (defaults to 1)

    • osd_batch_size (int): Number of syndromes that will be decoded in parallel for OSD (defaults to the number of concurrent threads supported by the hardware)

    • iter_per_check (int): Number of iterations between BP convergence checks (defaults to 1, and max is max_iterations). Introduced in 0.4.0.

    • clip_value (float): Value to clip the BP messages to. Should be a non-negative value (defaults to 0.0, which disables clipping). Introduced in 0.4.0.

    • repeatable (bool): Whether to make the BP algorithm (and Relay BP algorithm if enabled) bit-for-bit repeatable. Defaults to False. You must set clip_value to a non-zero value to use this option. Setting this option to True makes it run approximately 5-10% slower, but you are guaranteed to get repeatable results, which is often useful for both timing and detailed syndrome analysis. Introduced in 0.6.0.

    • bp_method (int): Core BP algorithm to use (defaults to 0). Introduced in 0.4.0, expanded in 0.5.0 and 0.7.0:

      • 0: sum-product

      • 1: min-sum (introduced in 0.4.0)

      • 2: min-sum+mem (uniform memory strength, requires use_sparsity=True. Introduced in 0.5.0)

      • 3: min-sum+dmem (disordered memory strength, requires use_sparsity=True. Introduced in 0.5.0)

      • 4: sum-product+mem (uniform memory strength, requires use_sparsity=True. Introduced in 0.7.0)

      • 5: sum-product+dmem (disordered memory strength, requires use_sparsity=True. Introduced in 0.7.0)

    • composition (int): Iteration strategy (defaults to 0). Introduced in 0.5.0:

      • 0: Standard (single run)

      • 1: Sequential relay (multiple gamma legs). Requires: bp_method=3 (min-sum+dmem) or bp_method=5 (sum-product+dmem), use_sparsity=True, and srelay_config. Support for bp_method=5 was added in 0.7.0.

    • scale_factor (float): The scale factor to use for min-sum. Defaults to 1.0. When set to 0.0, the scale factor is dynamically computed based on the number of iterations. Introduced in 0.4.0.

    • proc_float (string): The processing float type to use. Defaults to “fp64”. Valid values are “fp32” and “fp64”. Introduced in 0.5.0.

    • gamma0 (float): Memory strength parameter. Required for bp_method=2 (min-sum+mem) and bp_method=4 (sum-product+mem), and for composition=1 (sequential relay). Introduced in 0.5.0; extended in 0.7.0 for bp_method=4.

    • gamma_dist (vector<float>): Gamma distribution interval [min, max] for disordered memory strength. Required for bp_method=3 (min-sum+dmem) or bp_method=5 (sum-product+dmem) if explicit_gammas not provided. Introduced in 0.5.0; extended in 0.7.0 for bp_method=5.

    • explicit_gammas (vector<vector<float>>): Explicit gamma values for each variable node. For bp_method=3 or bp_method=5 with composition=0, provide a 2D vector where each row has block_size columns. For composition=1 (Sequential relay), provide num_sets rows (one per relay leg). Overrides gamma_dist if provided. Introduced in 0.5.0; extended in 0.7.0 for bp_method=5.

    • srelay_config (heterogeneous_map): Sequential relay configuration (required for composition=1). Contains the following parameters. Introduced in 0.5.0:

      • pre_iter (int): Number of pre-iterations to run before relay legs

      • num_sets (int): Number of relay sets (legs) to run

      • stopping_criterion (string): When to stop relay legs:

        • ”All”: Run all legs

        • ”FirstConv”: Stop relay after first convergence

        • ”NConv”: Stop after N convergences (requires stop_nconv parameter)

      • stop_nconv (int): Number of convergences to wait for before stopping (required only when stopping_criterion="NConv")

      Note

      Starting in version 0.6.0, convergence during the pre_iter phase counts as a successful convergence towards the stopping criteria. Prior to 0.6.0, convergence during pre-iterations did not count.

    • bp_seed (int): Seed for random number generation used in bp_method=3 or bp_method=5 (disordered memory BP), or in composition=1 (sequential relay). Optional parameter, defaults to 42 if not provided. Introduced in 0.5.0.

    • O (tensor<uint8_t>): Optional observables matrix with shape (num_observables, block_size). When provided, decode() and decode_batch() return observable flips (O * correction (mod 2)) in DecoderResult.result instead of the raw decoded correction vector. Mutually exclusive with the realtime enqueue_syndrome path: use one or the other, not both. Introduced in 0.7.0.

    • opt_results (heterogeneous_map): Optional results to return. This field can be left empty if no additional results are desired. Choices are:

      • bp_llr_history (int): Return the last bp_llr_history iterations of the BP LLR history. Minimum value is 0 and maximum value is max_iterations. The actual number of returned iterations might be fewer than bp_llr_history if BP converges before the requested number of iterations. Introduced in 0.4.0. Note: Not supported for composition=1.

      • num_iter (bool): If true, return the number of BP iterations run. Introduced in 0.5.0.

    • gamma_ensemble_size (int): Number of parallel gamma trajectories (“lanes”) run per sequential-relay BP iteration. Allowed values are 1, 2, 4, and 8 (defaults to 1, which disables the ensemble). Each lane explores a distinct gamma set drawn from gamma_dist (or explicit_gammas). The constructor requires num_sets >= gamma_ensemble_size so the N per-lane gamma-set offsets stay pairwise distinct. Introduced in 0.7.0.

      Design semantics (race-to-fastest): the ensemble is optimized for decode latency. The kernel runs the N lanes in parallel and applies the user-supplied stopping_criterion across the ensemble: the first lane to satisfy the criterion stops the others. The winning lane is the converged lane with the lowest-weight correction (sum of error-rate LLRs over bits decoded as 1). If no lane converges across all num_sets legs the decoder falls back to lane 0’s last-leg marginals. The speedups are maximized for the long-tail (p99) syndromes: easy syndromes converge in the first leg either way, while hard syndromes that would otherwise walk through many sequential legs get them raced in parallel instead.

      Warm-up (`pre_iter`) semantics: with the ensemble, the search diversity comes entirely from the relay legs, because each lane runs its own sequence of gamma sets. The pre_iter warm-up is the opposite: every lane would run it with the same uniform gamma0 value, so the kernel computes it once (on one lane) and broadcasts the warmed-up marginals to every lane before the legs start. The warm-up helps convergence by settling the messages before the disordered gammas are applied and by letting easy syndromes converge early (which counts toward the stopping criterion), at the cost of delaying the start of the legs by the warm-up iterations. Ensemble speedups are therefore maximized when pre_iter is small or zero.

      The gamma ensemble is supported on the sparse GPU single-decode path with composition=1 and bp_method=3 (min-sum + dmem) or bp_method=5 (sum-product + dmem). Passing gamma_ensemble_size > 1 with any other bp_method or with the CPU or dense GPU path raises std::invalid_argument at construction. The batched relay path (decode_batch()) and the realtime / graph-dispatch path (capture_decode_graph()) do not yet support ensembles and raise at call time; support for gamma_ensemble_size > 1 with capture_decode_graph() is intended in a future release.

Sliding Window Decoder

class cudaq_qec.sliding_window

The Sliding Window Decoder is a wrapper around a standard decoder that introduces two key differences:

1. Sliding Window Decoding: The decoding process is performed incrementally, one window at a time. The window size is specified by the user. This allows decoding to begin before all syndromes have been received, potentially reducing overall latency in multi-round QEC codes.

2. Partial Syndrome Support: Unlike standard decoders, the decode function (and its variants like decode_batch) can accept partial syndromes. If partial syndromes are provided, the return vector will be empty, the decoder will not complete the processing and remain in an intermediate state, awaiting future syndromes. The return vector is only non-empty once enough data has been provided to match the original syndrome size (calculated from the Parity Check Matrix).

Sliding window decoders are advantageous in QEC codes subject to circuit-level noise across multiple syndrome extraction rounds. These decoders permit syndrome processing to begin before the complete syndrome measurement sequence is obtained, potentially reducing the overall decoding latency. However, this approach introduces a trade-off: the reduction in latency typically comes at the cost of increased logical error rates. Therefore, the viability of sliding window decoding depends critically on the specific code parameters, noise model, and latency requirements of the system under consideration.

Sliding window decoding imposes only a single structural constraint on the parity check matrices: each syndrome extraction round must produce a constant number of syndrome measurements. Notably, the decoder makes no assumptions about temporal correlations or periodicity in the underlying noise process.

Streaming Syndrome Interface

For real-time applications, the decoder provides an enqueue_syndrome() method that accepts syndrome data one round at a time. This allows the host to feed syndrome measurements as they arrive without waiting for all rounds to complete. The decoder automatically manages internal buffering and triggers window decodes at appropriate boundaries.

References: Toward Low-latency Iterative Decoding of QLDPC Codes Under Circuit-Level Noise

Note

It is required to create decoders with the get_decoder API from the CUDA-QX extension points API, such as

import cudaq
import cudaq_qec as qec
import numpy as np

cudaq.set_target('stim')
num_rounds = 5
code = qec.get_code('surface_code', distance=num_rounds)
noise = cudaq.NoiseModel()
noise.add_all_qubit_channel("x", cudaq.Depolarization2(0.001), 1)
statePrep = qec.operation.prep0
dem = qec.dem_from_memory_circuit(code, statePrep, num_rounds, noise)
inner_decoder_params = {'use_osd': True, 'max_iterations': 50}
opts = {
    'error_rate_vec': np.array(dem.error_rates),
    'window_size': 1,
    'num_syndromes_per_round': code.get_num_z_stabilizers() + code.get_num_x_stabilizers(),
    'num_boundary_syndromes': code.get_num_z_stabilizers(),
    'inner_decoder_name': 'single_error_lut',
    'inner_decoder_params': inner_decoder_params,
}
swdec = qec.get_decoder('sliding_window', dem.detector_error_matrix, **opts)
#include "cudaq/qec/code.h"
#include "cudaq/qec/decoder.h"
#include "cudaq/qec/experiments.h"
#include "common/NoiseModel.h"

int main() {
    // Generate a Detector Error Model.
    int num_rounds = 5;
    auto code = cudaq::qec::get_code(
        "surface_code", cudaqx::heterogeneous_map{{"distance", num_rounds}});
    cudaq::noise_model noise;
    noise.add_all_qubit_channel("x", cudaq::depolarization2(0.001), 1);
    auto statePrep = cudaq::qec::operation::prep0;
    auto dem = cudaq::qec::dem_from_memory_circuit(*code, statePrep, num_rounds,
                                                    noise);
    // Use the DEM to create a sliding window decoder.
    auto inner_decoder_params =
        cudaqx::heterogeneous_map{{"use_osd", true}, {"max_iterations", 50}};
    auto opts = cudaqx::heterogeneous_map{
        {"error_rate_vec", dem.error_rates},
        {"window_size", 1},
        {"num_syndromes_per_round", code->get_num_z_stabilizers() + code->get_num_x_stabilizers()},
        {"num_boundary_syndromes", code->get_num_z_stabilizers()},
        {"inner_decoder_name", "single_error_lut"},
        {"inner_decoder_params", inner_decoder_params}};
    auto swdec = cudaq::qec::get_decoder("sliding_window",
                                        dem.detector_error_matrix, opts);

    return 0;
}

Note

The "sliding_window" decoder implements the cudaq_qec.Decoder interface for Python and the cudaq::qec::decoder interface for C++, so it supports all the methods in those respective classes.

Parameters:
  • H – Parity check matrix (tensor format)

  • params

    Heterogeneous map of parameters:

    • error_rate_vec (double): Vector of length “block size” containing the probability of an error (in 0-1 range). This vector is used to populate the error_rate_vec parameter for the inner decoder (automatically sliced correctly according to each window).

    • window_size (int): The number of rounds of syndrome data in each window. (Defaults to 1.)

    • step_size (int): The number of rounds to advance the window by each time. (Defaults to 1.)

    • num_syndromes_per_round (int): The number of syndromes per round. (Must be provided.)

    • num_boundary_syndromes (int): The number of detectors in the first and last (boundary) round of the memory experiment. (Defaults to 0, meaning all layers have num_syndromes_per_round detectors.) For a single-basis DEM from z_dem_from_memory_circuit() (respectively x_dem_from_memory_circuit()), every layer has code.get_num_z_stabilizers() (respectively code.get_num_x_stabilizers()) detectors, so this may be left at its default. For a full DEM from dem_from_memory_circuit(), num_syndromes_per_round is code.get_num_z_stabilizers() + code.get_num_x_stabilizers() while the boundary rounds only carry the stabilizer type fixed by the state prep, so set this to code.get_num_z_stabilizers() for Z-basis preps (prep0/prep1) or code.get_num_x_stabilizers() for X-basis preps (prepp/prepm).

    • straddle_start_round (bool): When forming a window, should error mechanisms that span the start round and any preceding rounds be included? (Defaults to False.)

    • straddle_end_round (bool): When forming a window, should error mechanisms that span the end round and any subsequent rounds be included? (Defaults to True.)

    • inner_decoder_name (string): The name of the inner decoder to use.

    • inner_decoder_params (Python dict or C++ heterogeneous_map): A dictionary of parameters to pass to the inner decoder.

TensorRT Decoder

class cudaq_qec.trt_decoder

A GPU-accelerated quantum error correction decoder based on NVIDIA TensorRT. This decoder leverages TensorRT’s optimized inference engine to perform fast neural network-based decoding of quantum error correction syndromes.

The TRT decoder supports loading pre-trained neural network models in ONNX format or directly loading pre-built TensorRT engine files for maximum performance. It automatically optimizes the model for the target GPU architecture and supports various precision modes (FP16, BF16, INT8, FP8) to balance accuracy and speed.

Neural network-based decoders can be trained to perform syndrome decoding for specific quantum error correction codes and noise models. The TRT decoder provides a high-performance inference engine for these models, with automatic CUDA graph optimization for reduced latency.

Requires a CUDA-capable GPU and TensorRT installation. See the CUDA-Q GPU Compatibility List for a list of valid GPU configurations.

Note

It is required to create decoders with the get_decoder API from the CUDA-QX extension points API, such as

import cudaq_qec as qec
import numpy as np

# Create a simple parity check matrix (not used by the TRT decoder)
H = np.array([[1, 0, 0, 1, 0, 1, 1],
              [0, 1, 0, 1, 1, 0, 1],
              [0, 0, 1, 0, 1, 1, 1]], dtype=np.uint8)

# Option 1: Load from ONNX model (builds TRT engine)
trt_dec = qec.get_decoder('trt_decoder', H,
                          onnx_load_path='model.onnx',
                          precision='fp16',
                          engine_save_path='model.engine')

# Option 2: Load pre-built TRT engine (faster startup)
trt_dec = qec.get_decoder('trt_decoder', H,
                          engine_load_path='model.engine')
#include "cudaq/qec/decoder.h"

std::size_t block_size = 7;
std::size_t syndrome_size = 3;
cudaqx::tensor<uint8_t> H;

// Create a simple parity check matrix (not used by the TRT decoder)
std::vector<uint8_t> H_vec = {1, 0, 0, 1, 0, 1, 1,
                              0, 1, 0, 1, 1, 0, 1,
                              0, 0, 1, 0, 1, 1, 1};
H.copy(H_vec.data(), {syndrome_size, block_size});

// Option 1: Load from ONNX model (builds TRT engine)
cudaqx::heterogeneous_map params1;
params1.insert("onnx_load_path", "model.onnx");
params1.insert("precision", "fp16");
params1.insert("engine_save_path", "model.engine");
auto trt_dec1 = cudaq::qec::get_decoder("trt_decoder", H, params1);

// Option 2: Load pre-built TRT engine (faster startup)
cudaqx::heterogeneous_map params2;
params2.insert("engine_load_path", "model.engine");
auto trt_dec2 = cudaq::qec::get_decoder("trt_decoder", H, params2);

Note

The "trt_decoder" implements the cudaq_qec.Decoder interface for Python and the cudaq::qec::decoder interface for C++, so it supports all the methods in those respective classes.

Note

The parity check matrix H is not used by the TRT decoder. The neural network model encodes the decoding logic, so the parity check matrix is only required to satisfy the decoder interface. You can pass any valid parity check matrix of appropriate dimensions.

Note

Batch Processing: The TRT decoder automatically handles batch size optimization. Models trained with batch_size > 1 will receive zero-padded inputs when using decode() on a single syndrome. When using decode_batch(), provide syndromes in multiples of the model’s batch size for optimal performance.

Parameters:
  • H – Parity check matrix (tensor format). Note: This parameter is not used by the TRT decoder but is required by the decoder interface.

  • params

    Heterogeneous map of parameters:

    Required (choose one):

    • onnx_load_path (string): Path to ONNX model file. The decoder will build a TensorRT engine from this model. Cannot be used together with engine_load_path.

    • engine_load_path (string): Path to pre-built TensorRT engine file. Provides faster initialization since the engine is already optimized. Cannot be used together with onnx_load_path.

    Optional:

    • engine_save_path (string): Path to save the built TensorRT engine. Only applicable when using onnx_load_path. Saving the engine allows for faster initialization in subsequent runs by using engine_load_path.

    • precision (string): Precision mode for inference (defaults to “best”). Valid options:

      • ”fp16”: Use FP16 (half precision) - good balance of speed and accuracy

      • ”bf16”: Use BF16 (bfloat16) - available on newer GPUs (Ampere+)

      • ”int8”: Use INT8 quantization - fastest but requires calibration

      • ”fp8”: Use FP8 precision - available on Hopper GPUs

      • ”tf32”: Use TensorFloat-32 - available on Ampere+ GPUs

      • ”noTF32”: Disable TF32 and use standard FP32

      • ”best”: Let TensorRT automatically choose the best precision (default)

      Note: If the requested precision is not supported by the hardware, the decoder will fall back to FP32 with a warning.

    • memory_workspace (size_t): Memory workspace size in bytes for TensorRT engine building (defaults to 1GB = 1073741824 bytes). Larger workspaces may allow TensorRT to explore more optimization strategies.

    • use_cuda_graph (bool): Enable CUDA graph optimization for improved performance (defaults to True). CUDA graphs capture inference operations and replay them with reduced kernel launch overhead, providing ~20% speedup. The optimization is applied automatically on the first decode call. Automatically disabled for models with dynamic shapes or multiple optimization profiles. Set to False to force traditional execution path.

    • batch_size (automatic): The decoder automatically detects the model’s batch size from the first input dimension. For models with batch_size > 1, the decode() method automatically zero-pads single syndromes to fill the batch. The decode_batch() method requires the number of syndromes to be an integral multiple of the model’s batch size.

    • global_decoder (string): Name of an optional second-stage “global” decoder to chain after the TensorRT model (composite decoding). The TRT model acts as a first-stage predecoder whose output is passed to the named global decoder (for example "pymatching" or "chromobius"). When omitted, the TRT model’s output is returned directly. See the real-time decoding API for configuring composite decoding from YAML. Introduced in 0.7.0.

    • global_decoder_params (map): Parameters forwarded to the global_decoder. The accepted keys follow the parameter schema of the named global decoder. When constructing the decoder directly (rather than from YAML), supply global_decoder_params (which may be an empty map) whenever global_decoder is set; otherwise the global stage is skipped. Introduced in 0.7.0.

    • cuda_device_id (int): Zero-based CUDA device ordinal on which to construct the decoder and run every decode. Must be >= 0 and less than the number of visible GPUs. When omitted, the decoder is not pinned to a specific device and runs on the default device (GPU 0). Introduced in 0.7.0.

Tensor Network Decoder

class cudaq_qec.plugins.decoders.tensor_network_decoder.TensorNetworkDecoder

A general class for tensor network decoders for quantum error correction codes.

This decoder constructs a tensor network representation of a quantum code using its parity check matrix, logical observables, and noise model. The tensor network is based on the Tanner graph of the code and can be contracted to compute the probability that a logical observable has flipped, given a syndrome.

The decoder supports both single-syndrome and batch decoding, and can run on CPU or GPU (using cuTensorNet if available).

The Tensor Network Decoder is a Python-only implementation and it requires Python 3.11 or higher. C++ APIs are not available for this decoder.

Due to the additional dependencies of the Tensor Network Decoder, you must specify the optional pip package when installing CUDA-Q QEC in order to use this decoder. Use pip install cudaq-qec[tensor-network-decoder] in order to use this decoder.

The Tensor Network Decoder has the same GPU support as the Quantum Low-Density Parity-Check Decoder. However, if you are using the V100 GPU (SM70), you will need to pin your cuTensor version to 2.2 by running pip install cutensor_cu12==2.2. Note that this GPU is not supported by the Tensor Network Decoder.

Note

It is recommended to create decoders using the cudaq_qec plugin API:

import cudaq_qec as qec
import numpy as np

# Example: [3,1] repetition code
H = np.array([[1, 1, 0],
        [0, 1, 1]], dtype=np.uint8)
logical_obs = np.array([[1, 1, 1]], dtype=np.uint8)
noise_model = [0.1, 0.1, 0.1]

decoder = qec.get_decoder("tensor_network_decoder", H, logical_obs=logical_obs, noise_model=noise_model)

syndrome = [0.0, 1.0]
result = decoder.decode(syndrome)

Tensor Network Structure

The tensor network constructed by this decoder is based on the Tanner graph of the code, extended with noise and logical observable tensors. The structure is illustrated below:

      open/output index < logical observable
          --------
             |
s1      s2   |     s3   < syndromes               : product of 2D vectors [1 , 1-2pi] (pi is the probability detector i flipped)
|       |    |     |                        ----|
c1      c2  l1     c3   < checks / logical      | : delta tensors
|     / |   | \    |                            |
H   H   H   H  H   H    < Hadamard matrices     | TANNER (bipartite) GRAPH
  \ |   |  /   |  /                             |
    e1  e2     e3       < errors                | : delta tensors
    |   |     /                            -----|
     \ /     /
    P(e1, e2, e3)       < noise / error model     : classical probability density

ci, ej, lk are delta tensors represented sparsely as indices.
Parameters:
  • H – Parity check matrix (numpy.ndarray), shape (num_checks, num_qubits)

  • logical_obs – Logical observable matrix (numpy.ndarray), shape (1, num_qubits)

  • noise_model – Noise model, either a list of probabilities (length = num_qubits) or a quimb.tensor.TensorNetwork

  • check_inds – (optional) List of check index names

  • error_inds – (optional) List of error index names

  • logical_inds – (optional) List of logical index names

  • logical_tags – (optional) List of logical tags

  • contract_noise_model – (bool, optional) Whether to contract the noise model at initialization (default: True)

  • dtype – (str, optional) Data type for tensors (default: “float64”)

  • device – (str, optional) Device for tensor operations (“cpu”, “cuda”, or “cuda:X”, default: “cuda”)

Methods

decode(syndrome)

Decode a single syndrome by contracting the tensor network.

Parameters:

syndrome – List of float values (soft-decision probabilities) for each check.

Returns:

DecoderResult with the probability that the logical observable flipped.

decode_batch(syndrome_batch)

Decode a batch of syndromes.

Parameters:

syndrome_batch – numpy.ndarray of shape (batch_size, num_checks)

Returns:

List of DecoderResult objects with the probability that the logical observable has flipped for each syndrome.

optimize_path(optimize=None, batch_size=-1)

Optimize the contraction path for the tensor network.

Parameters:
  • optimize – Optimization options or None

  • batch_size – (int, optional) Batch size for optimization (default: -1, no batching)

Returns:

Optimizer info object

PyMatching Decoder

class cudaq_qec.pymatching

A minimum-weight perfect matching (MWPM) decoder for matchable quantum error correction codes (such as the surface code), built on the open-source PyMatching library. It is a CPU decoder: each syndrome bit becomes a detector node, and each error (column of the parity-check matrix) with one or two set entries becomes a (boundary) edge whose weight is derived from the error prior.

Note

To use the decoder, use the get_decoder API with a parity-check matrix as the decoder input:

import cudaq_qec as qec
import numpy as np

# Parity check matrix. Each column (error mechanism) must have one
# or two set entries so the graph is matchable.
H = np.array([[1, 1, 0],
              [0, 1, 1]], dtype=np.uint8)

dec = qec.get_decoder("pymatching", H,
                      error_rate_vec=[0.1, 0.1, 0.1],
                      merge_strategy="smallest_weight")
#include "cudaq/qec/decoder.h"

cudaqx::heterogeneous_map params;
params.insert("merge_strategy", std::string("smallest_weight"));
auto dec = cudaq::qec::get_decoder("pymatching", H, params);

Note

The "pymatching" decoder implements the cudaq_qec.Decoder interface for Python and the cudaq::qec::decoder interface for C++, so it supports all the methods in those respective classes.

Parameters:
  • H – Parity check matrix. Each column must have one or two set entries (matchable graph). In Python, a scipy.sparse matrix or a dense NumPy uint8 array may be passed.

  • params

    Heterogeneous map of parameters:

    • error_rate_vec (vector<double>): Per-error prior probabilities, one per column of H (length block_size). Each value must lie in (0, 0.5] and sets the matching edge weight -log(p / (1 - p)). When omitted, all edge weights default to 1.0.

    • merge_strategy (string): How to combine parallel edges that map to the same pair of detectors. One of "disallow" (default for the H-only path), "independent", "smallest_weight", "keep_original", or "replace".

    • O (tensor, optional): A num_observables x block_size binary matrix. When provided, the decoder returns predicted observable flips (decode_to_obs) instead of a raw error vector, and merge_strategy defaults to "independent" to match PyMatching’s detector-error-model construction.

Chromobius Decoder

class cudaq_qec.chromobius

A decoder for color codes built on the open-source Chromobius Möbius decoder. Unlike the matrix-based decoders, Chromobius is detector-error-model native: it is constructed directly from Stim detector-error-model (DEM) text and predicts logical observable flips directly.

Note

To use the decoder, use the get_decoder API with the Stim DEM text as the decoder input:

import cudaq_qec as qec

with open("color_code.dem") as f:
    dem_text = f.read()

dec = qec.get_decoder("chromobius", dem_text)
corrections = dec.decode(syndrome)  # predicted observable flips
#include "cudaq/qec/decoder.h"

std::string dem_text = /* Stim detector error model text */;
cudaqx::heterogeneous_map params;
auto dec = cudaq::qec::get_decoder("chromobius", dem_text, params);

Note

Chromobius is DEM-native: constructing it from a parity-check matrix is rejected with an error. Use get_decoder("chromobius", dem_text, params). The DEM must describe a color code whose errors carry the color/basis annotations that Chromobius decomposes.

Note

The "chromobius" decoder implements the cudaq_qec.Decoder interface for Python and the cudaq::qec::decoder interface for C++. The wrapper currently returns observable flips as a 64-bit mask, so at most 64 logical observables are supported; decode() returns one bit per observable.

Parameters:
  • dem_text – Stim detector error model, as text. Detectors become the decoder syndrome bits; observables become the returned block_size correction bits.

  • params

    Heterogeneous map of parameters (all optional and boolean),

    forwarded to the Chromobius decoder configuration:

    • drop_mobius_errors_involving_remnant_errors (bool)

    • ignore_decomposition_failures (bool)

    • include_coords_in_mobius_dem (bool)

    • return_weight (bool): When true, the match weight is returned in the decode result’s opt_results under the key "weight".

    • write_mobius_match_to_stderr (bool)

Realtime Decoding

The Realtime Decoding API enables low-latency error correction on quantum hardware by allowing CUDA-Q quantum kernels to interact with decoders during circuit execution. This API is designed for use cases where corrections must be calculated and applied within qubit coherence times.

The real-time decoding system supports simulation environments for local testing and hardware integration (e.g., on Quantinuum’s Helios QPU).

Core Decoding Functions

These functions can be called from within CUDA-Q quantum kernels (@cudaq.kernel decorated functions) to interact with real-time decoders.

cudaq_qec.qec.enqueue_syndromes(decoder_id, syndromes, tag=0)

Enqueue syndrome measurements for decoding.

Parameters:
  • decoder_id – Unique identifier for the decoder instance (matches configured decoder ID)

  • syndromes – List of syndrome measurement results from stabilizer measurements

  • tag – Optional tag for logging and debugging (default: 0)

Example:

import cudaq
import cudaq_qec as qec
from cudaq_qec import patch

@cudaq.kernel
def measure_and_decode(logical: patch, decoder_id: int):
    syndromes = measure_stabilizers(logical)
    qec.enqueue_syndromes(decoder_id, syndromes, 0)
cudaq_qec.qec.get_corrections(decoder_id, return_size, reset=False)

Retrieve calculated corrections from the decoder.

Parameters:
  • decoder_id – Unique identifier for the decoder instance

  • return_size – Number of correction bits to return (typically equals number of logical observables)

  • reset – Whether to reset accumulated corrections after retrieval (default: False)

Returns:

List of boolean values indicating detected bit flips for each logical observable

Example:

@cudaq.kernel
def apply_corrections(logical: patch, decoder_id: int):
    corrections = qec.get_corrections(decoder_id, 1, False)
    if corrections[0]:
        x(logical.data)  # Apply transversal X correction
cudaq_qec.qec.reset_decoder(decoder_id)

Reset decoder state, clearing all queued syndromes and accumulated corrections.

Parameters:

decoder_id – Unique identifier for the decoder instance to reset

Example:

@cudaq.kernel
def run_experiment(decoder_id: int):
    qec.reset_decoder(decoder_id)  # Reset at start of each shot
    # ... perform experiment ...

Configuration API

The configuration API enables setting up decoders before circuit execution. Decoders are configured using YAML files or programmatically constructed configuration objects.

Decoder Parameters

Decoder-specific parameters (decoder_config.decoder_custom_args) are plain dicts. The set of accepted keys, their types, and which are required are defined by the parameter schema each decoder registers – including out-of-tree decoder plugins. Use cudaq_qec.decoder_param_schema(name) to inspect a decoder’s parameters and cudaq_qec.registered_decoder_schemas() to list all decoders with registered schemas.

For example, the pymatching decoder accepts error_rate_vec (per-error prior probabilities in the range (0, 0.5], length matching the decoder block_size) and merge_strategy (one of "disallow", "independent", "smallest_weight", "keep_original", "replace"):

config.type = "pymatching"
config.decoder_custom_args = {
    "error_rate_vec": [0.1, 0.1, 0.1],
    "merge_strategy": "smallest_weight",
}

The trt_decoder accepts onnx_load_path or engine_load_path (mutually exclusive), engine_save_path, precision (“fp16”, “bf16”, “int8”, “fp8”, “tf32”, “noTF32”, or “best”), memory_workspace (bytes), batch_size, use_cuda_graph, and an optional global decoder attached via global_decoder plus global_decoder_params (a nested dict whose keys follow the schema of the named global decoder).

cudaq_qec.decoder_param_schema(decoder_name)

Return the registered parameter schema for a decoder as a list of descriptors (key, kind, required, and, for nested sections, subschema or discriminator), or None when the decoder has not registered one.

cudaq_qec.registered_decoder_schemas()

Names of all decoders (and nested parameter sections) with registered parameter schemas.

cudaq_qec.decoder_config_json_schema()

Return a JSON Schema (draft 2020-12) document, as a string, that validates multi_decoder_config YAML files. Generated from the decoder parameter schemas registered in this installation (including loaded third-party decoder plugins), for use with standard tools such as check-jsonschema, the python jsonschema package, or editor YAML language servers. Schema validation hooks are not representable in JSON Schema, so a passing document may still be rejected when parsed.

decoder_config.validate_custom_args()

Validate decoder_custom_args against the parameter schema registered for this decoder type: unknown keys, missing required keys, and the schema’s own validation hook. Raises RuntimeError on the first violation. YAML parsing applies the same checks automatically; call this to vet a configuration built programmatically before using it. Also available on multi_decoder_config to validate every decoder at once.

Deprecated Typed Configuration Classes

The typed configuration classes from earlier releases (nv_qldpc_decoder_config, trt_decoder_config, pymatching_config, chromobius_config, multi_error_lut_config, and the qecrt.config-level single_error_lut_config, sliding_window_config, and srelay_bp_config) remain available as deprecated compatibility shims. They emit a DeprecationWarning on construction and will be removed in a future release; existing code that builds one and passes it to decoder_config.set_decoder_custom_args (or assigns it to decoder_config.decoder_custom_args) continues to work unchanged. Note that reading decoder_custom_args now always returns a plain dict, never a typed object. New code should assign dicts directly, as shown above.

Configuration Functions

cudaq_qec.configure_decoders(config)

Configure decoders from a multi_decoder_config object.

Parameters:

config – multi_decoder_config object containing decoder specifications

Returns:

0 on success, non-zero error code on failure

cudaq_qec.configure_decoders_from_file(config_file)

Configure decoders from a YAML file.

Parameters:

config_file – Path to YAML configuration file

Returns:

0 on success, non-zero error code on failure

cudaq_qec.configure_decoders_from_str(config_str)

Configure decoders from a YAML string.

Parameters:

config_str – YAML configuration as a string

Returns:

0 on success, non-zero error code on failure

cudaq_qec.finalize_decoders()

Finalize and clean up decoder resources. Should be called before program exit.

Helper Functions

Realtime decoding requires converting matrices to sparse format for efficient decoder configuration. The following utility functions are essential:

cudaq_qec.pcm_to_sparse_vec(pcm)

Convert a parity check matrix (PCM) to sparse vector representation for decoder configuration.

Parameters:

pcm – Dense binary matrix as numpy array (e.g., dem.detector_error_matrix or dem.observables_flips_matrix)

Returns:

Sparse vector (list of integers) where -1 separates rows

Usage in real-time decoding:

config.H_sparse = qec.pcm_to_sparse_vec(dem.detector_error_matrix)
config.O_sparse = qec.pcm_to_sparse_vec(dem.observables_flips_matrix)
cudaq_qec.pcm_from_sparse_vec(sparse_vec, num_rows, num_cols)

Convert sparse vector representation back to a dense parity check matrix.

Parameters:
  • sparse_vec – Sparse representation (from YAML or decoder config)

  • num_rows – Number of rows in the output matrix

  • num_cols – Number of columns in the output matrix

Returns:

Dense binary matrix as numpy array

cudaq_qec.d_sparse(m2d)

Flatten a measurement-to-detector map into the -1-terminated sparse vector a realtime decoder config expects for its D_sparse.

Parameters:

m2d – List of lists of measurement indices. m2d[d] contains the measurement indices whose XOR forms detector d. Obtain this from the second element of the tuple returned by DecoderContext.x_component(), DecoderContext.z_component(), or DecoderContext.full_component().

Returns:

-1-terminated sparse vector suitable for decoder_config.D_sparse

Usage in real-time decoding:

ctx = qec.decoder_context_from_memory_circuit(code, statePrep, num_rounds, noise)
dem, m2d, m2o = ctx.z_component()  # or x_component() / full_component()
config.D_sparse = qec.d_sparse(m2d)

See also Parity Check Matrix Utilities for additional PCM manipulation functions.

Common

cudaq_qec.sample_memory_circuit(code: cudaq_qec.Code, numShots: int, numRounds: int, noise: cudaq.NoiseModel | None = None) tuple
cudaq_qec.sample_memory_circuit(code: cudaq_qec.Code, op: cudaq_qec.operation, numShots: int, numRounds: int, noise: cudaq.mlir._mlir_libs._quakeDialects.cudaq_runtime.NoiseModel | None = None) tuple

Overloaded function.

  1. sample_memory_circuit(code: cudaq_qec.Code, numShots: int, numRounds: int, noise: cudaq.NoiseModel | None = None) -> tuple

Sample the memory circuit of the code

  1. sample_memory_circuit(code: cudaq_qec.Code, op: cudaq_qec.operation, numShots: int, numRounds: int, noise: cudaq.NoiseModel | None = None) -> tuple

Sample the memory circuit of the code with a specific initial operation. Returns (syndromes, data): syndromes has numFixed boundary detectors (basis matching op), then one block per inter-round transition, then numFixed more boundary detectors.

cudaq_qec.x_sample_memory_circuit(code: cudaq_qec.Code, op: cudaq_qec.operation, numShots: int, numRounds: int, noise: cudaq.NoiseModel | None = None) tuple

Sample the memory circuit of the code with a specific initial operation, keeping only the X stabilizer syndromes (same detector layout as sample_memory_circuit, restricted to X stabilizers).

cudaq_qec.z_sample_memory_circuit(code: cudaq_qec.Code, op: cudaq_qec.operation, numShots: int, numRounds: int, noise: cudaq.NoiseModel | None = None) tuple

Sample the memory circuit of the code with a specific initial operation, keeping only the Z stabilizer syndromes (same detector layout as sample_memory_circuit, restricted to Z stabilizers).

Note

Syndrome measurement layoutsample_memory_circuit returns a tuple (syndromes, data). The syndromes tensor has shape (num_shots, num_detectors) with columns laid out as [ B  S  S    S  B ]:

  • B (boundary block) = code.get_num_z_stabilizers() for Z-basis preparations (prep0/prep1), or code.get_num_x_stabilizers() for X-basis preparations (prepp/prepm).

  • S (inter-round block) = num_z_stabilizers + num_x_stabilizers detectors per round transition (num_rounds - 1 blocks total).

  • Total: num_detectors = 2*B + (num_rounds - 1)*S.

The data tensor has shape (num_shots, block_size) and holds the final data-qubit measurements used to verify logical-state preservation.

cudaq_qec.sample_code_capacity(code: cudaq_qec.Code, numShots: int, errorProb: float, seed: int | None = None) tuple
cudaq_qec.sample_code_capacity(H: numpy.ndarray[dtype=uint8], numShots: int, errorProb: float, seed: int | None = None) tuple

Sample syndrome measurements with code capacity noise.

Detector Error Model (DEM) Sampling

cudaq_qec.dem_sampling(check_matrix, num_shots: int, error_probabilities, seed: Optional[int] = None, backend: str = 'auto') Tuple[object, object]

Sample errors and syndromes from a Detector Error Model.

Parameters:
  • check_matrix – Binary matrix [num_checks x num_error_mechanisms], as a NumPy uint8 array or PyTorch CUDA tensor.

  • num_shots – Number of independent Monte-Carlo shots.

  • error_probabilities – 1-D array of length num_error_mechanisms with independent Bernoulli probabilities for each mechanism. Accepts NumPy float64 array or PyTorch CUDA tensor.

  • seed – Optional RNG seed for reproducibility.

  • backend – Backend selection policy: - “auto” (default): try GPU, fall back to CPU. - “cpu”: force CPU implementation. - “gpu”: force GPU implementation and raise if unavailable.

Returns:

(syndromes, errors) where

syndromes: uint8 array/tensor [num_shots x num_checks] errors: uint8 array/tensor [num_shots x num_error_mechanisms]

The GPU path uses cuStabilizer for accelerated sampling. When PyTorch CUDA tensors are provided, outputs are returned as CUDA tensors. Otherwise, outputs are NumPy arrays.

PyTorch CPU tensors are moved to CUDA under backend="gpu" (raising a GPU-unavailable error if there is no GPU) but rejected under "auto"/"cpu"; convert to NumPy first for the CPU path. PyTorch is an optional dependency; install with pip install torch.

Parity Check Matrix Utilities

cudaq_qec.generate_random_pcm(n_rounds: int, n_errs_per_round: int, n_syndromes_per_round: int, weight: int, seed: int = 0) object

Generate a random parity check matrix.

This function creates a random parity check matrix for quantum error correction with specified parameters controlling the structure and randomness.

Parameters:
  • n_rounds – Number of measurement rounds in the error correction protocol

  • n_errs_per_round – Number of error mechanisms per round

  • n_syndromes_per_round – Number of syndrome measurements per round

  • weight – The weight parameter controlling the sparsity of the matrix

  • seed – Random seed for reproducibility (0 for random seed)

See also

cudaq::qec::generate_random_pcm(): The underlying C++ implementation of this function.

Returns:

A NumPy array containing the generated parity check matrix

cudaq_qec.generate_timelike_sparse_detector_matrix(num_syndromes_per_round: int, num_rounds: int, include_first_round: bool) list[int]

Generate a sparse detector matrix for a given number of syndromes per round and number of rounds. Time-like here means that each round of syndrome measurement bits are xor’d against the preceding round.

Parameters:
  • num_syndromes_per_round – The number of syndrome measurements per round

  • num_rounds – The number of rounds to generate the sparse detector matrix for

  • include_first_round – Whether to include the first round of syndrome measurements

Returns:

The detector matrix format is CSR-like, with -1 values indicating the end of each row.

cudaq_qec.get_pcm_for_rounds(H: numpy.ndarray[dtype=uint8], num_syndromes_per_round: int, start_round: int, end_round: int, straddle_start_round: bool = False, straddle_end_round: bool = False, num_boundary_syndromes: int = 0) tuple

Get a sub-PCM for a range of rounds.

This function returns a sub-parity check matrix for a range of rounds.

Parameters:
  • H – A NumPy array representing the parity check matrix

  • num_syndromes_per_round – The number of syndrome measurements per round

  • start_round – The starting round

  • end_round – The ending round

  • straddle_start_round – Whether to allow error mechanisms that straddle the start round (i.e. include prior rounds, too). This defaults to false.

  • straddle_end_round – Whether to allow error mechanisms that straddle the end round (i.e. include future rounds, too). This defaults to false.

  • num_boundary_syndromes – The number of syndrome measurements in the boundary layers

Returns:

A tuple containing the sub-parity check matrix and the first and last column indices of the sub-PCM relative to the original PCM.

See also

cudaq::qec::get_pcm_for_rounds(): The underlying C++ implementation of this function.

cudaq_qec.get_sorted_pcm_column_indices(H: numpy.ndarray[dtype=uint8], num_syndromes_per_round: int = 0) list[int]

Get the sorted column indices of a parity check matrix.

This function returns the column indices of a parity check matrix in topological order.

Parameters:
  • H – A NumPy array representing the parity check matrix

  • num_syndromes_per_round – The number of syndrome measurements per round

Returns:

A NumPy array containing the sorted column indices

See also

cudaq::qec::get_sorted_pcm_column_indices(): The underlying C++ implementation of this function.

cudaq_qec.pcm_extend_to_n_rounds(H: numpy.ndarray[dtype=uint8], num_syndromes_per_round: int, n_rounds: int) tuple

Extend a parity check matrix to a given number of rounds.

This function extends a parity check matrix to a given number of rounds.

Parameters:
  • H – A NumPy array representing the parity check matrix

  • num_syndromes_per_round – The number of syndrome measurements per round

  • n_rounds – The number of rounds to extend the parity check matrix to

Returns:

A tuple containing the extended parity check matrix and the list of column indices from the original PCM that were used to form the new PCM.

See also

cudaq::qec::pcm_extend_to_n_rounds(): The underlying C++ implementation of this function.

cudaq_qec.pcm_is_sorted(H: numpy.ndarray[dtype=uint8], num_syndromes_per_round: int = 0) bool

Check if a parity check matrix is sorted.

This function checks if a parity check matrix is sorted in topological order.

Parameters:
  • H – A NumPy array representing the parity check matrix

  • num_syndromes_per_round – The number of syndrome measurements per round

Returns:

A boolean indicating if the parity check matrix is sorted

See also

cudaq::qec::pcm_is_sorted(): The underlying C++ implementation of this function.

cudaq_qec.pcm_to_sparse_vec(pcm: object) list[int]

Return a sparse representation of the PCM.

Parameters:

pcm – A NumPy array or scipy sparse matrix.

cudaq_qec.reorder_pcm_columns(H: object, column_order: collections.abc.Sequence[int]) object

Reorder the columns of a parity check matrix.

This function reorders the columns of a parity check matrix according to the given column order.

Parameters:
  • H – A NumPy array or scipy sparse matrix

  • column_order – A NumPy array containing the column order

Returns:

A NumPy array, or a scipy CSC matrix when H is scipy sparse.

See also

cudaq::qec::reorder_pcm_columns(): The underlying C++ implementation of this function.

cudaq_qec.shuffle_pcm_columns(H: object, seed: int = 0) object

Shuffle the columns of a parity check matrix.

This function shuffles the columns of a parity check matrix.

Parameters:
  • H – A NumPy array or scipy sparse matrix

  • seed – Random seed for reproducibility (0 for random seed)

Returns:

A NumPy array, or a scipy CSC matrix when H is scipy sparse.

See also

cudaq::qec::shuffle_pcm_columns(): The underlying C++ implementation of this function.

cudaq_qec.simplify_pcm(H: numpy.ndarray[dtype=uint8], weights: numpy.ndarray[dtype=float64], num_syndromes_per_round: int) tuple

Simplify a parity check matrix.

This function simplifies a parity check matrix by removing duplicate columns and 0-weight columns.

Parameters:
  • H – A NumPy array representing the parity check matrix

  • weights – A NumPy array containing the weights of the columns

  • num_syndromes_per_round – The number of syndrome measurements per round

Returns:

A tuple containing the simplified parity check matrix and the weights

See also

cudaq::qec::simplify_pcm(): The underlying C++ implementation of this function.

cudaq_qec.sort_pcm_columns(H: numpy.ndarray[dtype=uint8], num_syndromes_per_round: int = 0) object

Sort the columns of a parity check matrix.

This function sorts the columns of a parity check matrix in topological order.

Parameters:
  • H – A NumPy array representing the parity check matrix

  • num_syndromes_per_round – The number of syndrome measurements per round

Returns:

A NumPy array containing the sorted parity check matrix

See also

cudaq::qec::sort_pcm_columns(): The underlying C++ implementation of this function.