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=Trueto 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_idscorrelation 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_syndromesdetector rows form the initial round, each subsequent block ofnum_syndromes_per_roundrows is an interior round, and the trailingnum_boundary_syndromesrows 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_errorsbehaves as incanonicalize_for_rounds. RaisesValueErrorifnum_syndromes_per_roundis zero ornum_boundary_syndromesexceedsnum_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
*_componentmethod 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).
demis the canonicalized DetectorErrorModel,m2dandm2oare 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).
demis the canonicalized DetectorErrorModel,m2dandm2oare lists of lists of measurement indices.
- z_component
Canonicalize Z-stabilizer detectors; return (dem, m2d, m2o).
demis the canonicalized DetectorErrorModel,m2dandm2oare 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 modelm2d(list[list[int]]) — measurement-to-detector map;m2d[d]lists the measurement indices whose XOR forms detectordm2o(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_kernelonce and stores the raw result. Canonicalization is deferred: callx_component(),z_component(), orfull_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.
m2dis a list of lists:m2d[d]contains the measurement indices whose XOR forms detectord. 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_inputsreturned by a component method) and want to build the D_sparse vector without going through aDecoderContext.
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. UnlikeBatchDecoderResult, the no-arg constructor and writable fields are preserved here because Python decoder plugins implementing adecodeoverride 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
decodeoverrides (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 — calldecoder.decode_batch(...)and read the result.Python decoder plugins implementing a
decode_batchoverride may use the constructor to produce one.resultshould be a 2-D NumPy array of the configured QEC floating point dtype (float64 in standard wheels);convergedshould be a 1-D NumPy bool array;opt_resultsis a list of per-shot dicts or None entries;batch_opt_resultsis an optional dict of batch-level results. The constructor coercesresultandconvergedto C-contiguous storage of the expected dtype (vianp.ascontiguousarray), copying when the input doesn’t already satisfy those invariants. Wrong rank (e.g. 1-Dresult) is rejected with TypeError.An empty batch (zero syndromes) yields
result.shape == (0, 0)andconverged.shape == (0,). The per-shot width is unknown without running a decode and depends on decoder mode, soresult.shape[1]is only meaningful when the batch is non-empty.Access patterns, fastest to slowest:
Vectorized: read
result,converged, oropt_resultsdirectly. The properties return the underlying NumPy arrays and Python list with no copy. This is the recommended path for batch processing.Slicing:
batch[a:b]returns another BatchDecoderResult that shares data with the parent. NumPy basic slicing — including stepped slices likebatch[::2]— returns views, so no data is copied. The opt results list slice creates a new Python list, but its entries are shared references.Integer indexing / iteration:
batch[i]orfor r in batch:yields a DecoderResult copy of one shot. This compatibility surface exists for code written against the previouslist[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 batch_opt_results
A dict of batch-level optional results, or None.
Unlike
opt_results, this describes the batch as a whole rather than any single shot: its arrays are indexed by position in the batch. Most decoders produce None here. It is the fast path for data that would otherwise cost one Python dict and several small arrays per shot.Because its arrays are indexed by position in the full batch, slicing a BatchDecoderResult drops this field rather than carrying it through misaligned.
- 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.
Hmay be:A scipy sparse matrix (CSR, CSC, COO, or any
scipy.sparseformat): 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
uint8array in row-major order: a full densecudaqx::tensoris built first, then converted to CSC sparse storage. For large PCMs this can allocate as much memory asrows * 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 plusOanderror_rate_vecdefaults.
For Python-registered decoders (
cudaq.qec.decoderdecorator),His passed through to__init__unchanged (NumPy array or scipy sparse matrix). DEM string inputs are parsed first as described above. CallDecoder.__init__(self, H)so nanobind can store the PCM internally without building a denserows x colsallocation.
Note
scipy.sparse interop — cudaq_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_decoderAPI from the CUDA-QX extension points API, such asimport 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 thecudaq_qec.Decoderinterface for Python and thecudaq::qec::decoderinterface 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>= 0and 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 solvererror_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 overrideserror_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 solutionosd_method(int): 1=OSD-0, 2=Exhaustive, 3=Combination Sweep (defaults to 1). Ignored unlessuse_osdis true.osd_order(int): OSD postprocessor order (defaults to 0). Ref: Decoding Across the Quantum LDPC Code LandscapeFor
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_methodvalues, 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 ismax_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 setclip_valueto 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) orbp_method=5(sum-product+dmem),use_sparsity=True, andsrelay_config. Support forbp_method=5was 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 forbp_method=2(min-sum+mem) andbp_method=4(sum-product+mem), and forcomposition=1(sequential relay). Introduced in 0.5.0; extended in 0.7.0 forbp_method=4.gamma_dist(vector<float>): Gamma distribution interval [min, max] for disordered memory strength. Required forbp_method=3(min-sum+dmem) orbp_method=5(sum-product+dmem) ifexplicit_gammasnot provided. Introduced in 0.5.0; extended in 0.7.0 forbp_method=5.explicit_gammas(vector<vector<float>>): Explicit gamma values for each variable node. Forbp_method=3orbp_method=5withcomposition=0, provide a 2D vector where each row hasblock_sizecolumns. Forcomposition=1(Sequential relay), providenum_setsrows (one per relay leg). Overridesgamma_distif provided. Introduced in 0.5.0; extended in 0.7.0 forbp_method=5.srelay_config(heterogeneous_map): Sequential relay configuration (required forcomposition=1). Contains the following parameters. Introduced in 0.5.0:pre_iter(int): Number of pre-iterations to run before relay legsnum_sets(int): Number of relay sets (legs) to runstopping_criterion(string): When to stop relay legs:”All”: Run all legs
”FirstConv”: Stop relay after first convergence
”NConv”: Stop after N convergences (requires
stop_nconvparameter)
stop_nconv(int): Number of convergences to wait for before stopping (required only whenstopping_criterion="NConv")
Note
Starting in version 0.6.0, convergence during the
pre_iterphase 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 inbp_method=3orbp_method=5(disordered memory BP), or incomposition=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()anddecode_batch()return observable flips (O * correction (mod 2)) inDecoderResult.resultinstead of the raw decoded correction vector. Mutually exclusive with the realtimeenqueue_syndromepath: 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 lastbp_llr_historyiterations of the BP LLR history. Minimum value is 0 and maximum value is max_iterations. The actual number of returned iterations might be fewer thanbp_llr_historyif BP converges before the requested number of iterations. Introduced in 0.4.0. Note: Not supported forcomposition=1.num_iter(bool): If true, return the number of BP iterations run. Introduced in 0.5.0.relay_solutions(bool or int): Record every relay convergence (“solution”), not just the winning one.Truerecords all of them; a positive integer caps the number of records kept per shot (convergences beyond the cap still increment the per-shot total but are not stored). Requirescomposition=1(sequential relay) on the sparse GPU path (use_sparsity=True); other backends reject the option at construction. Not yet supported withgamma_ensemble_size > 1. Compatible withuse_osd=True(OSD post-processes non-converged shots and does not affect the records). Introduced in 0.8.0.Each record holds the cumulative BP iteration count at which the convergence occurred, the LLR weight of its hard decision (the sum of error-rate LLRs over bits decoded as 1), and the hard decision itself, bit-packed 32 bits per little-endian word. The hard decision is the correction vector, or its observable flips when the decoder was constructed with
O.The records describe a batch as a whole, so
decode_batch()returns them through its batch-level results (theBatchDecoderResult.batch_opt_resultsattribute in Python; the optionalbatch_opt_resultsoutput parameter in C++) as flat arrays under the keysrelay_solutions_width,relay_solutions_max_records,relay_solutions_counts,relay_solutions_totals,relay_solutions_iters,relay_solutions_weight, andrelay_solutions_result. (decode()returns the same keys throughDecoderResult.opt_results, with scalarrelay_solutions_count/relay_solutions_total.) Thecudaq_qec.relay_solutionsPython module post-processes the records:unpack()reconstructs the per-shot record axes, andstop_nconv_sweep()replays an entire RelayBP-Nstop_nconvsweep — logical error rate, mean iterations, and iteration percentiles for every N — from a single recording run. See Sweeping Relay BP Stopping Criteria From a Single Run for a usage and performance walkthrough.
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 fromgamma_dist(orexplicit_gammas). The constructor requiresnum_sets >= gamma_ensemble_sizeso 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_criterionacross 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 allnum_setslegs 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_iterwarm-up is the opposite: every lane would run it with the same uniformgamma0value, 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 whenpre_iteris small or zero.The gamma ensemble is supported on the sparse GPU single-decode path with
composition=1andbp_method=3(min-sum + dmem) orbp_method=5(sum-product + dmem). Passinggamma_ensemble_size > 1with any otherbp_methodor with the CPU or dense GPU path raisesstd::invalid_argumentat 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 forgamma_ensemble_size > 1withcapture_decode_graph()is intended in a future release.
Relay Solutions Post-Processing
Post-processing for the nv-qldpc-decoder relay_solutions records.
When the decoder is configured with opt_results={"relay_solutions": True}
(sequential relay, composition=1), every relay convergence is recorded and
returned through BatchDecoderResult.batch_opt_results as flat arrays. This
module reconstructs, offline, what the decoder would have returned for any
stop_nconv setting, so a single recording run answers the whole
RelayBP-N sweep.
- Public API:
unpack(batch_opt_results, num_shots) -> RelaySolutionRecords stop_nconv_sweep(results, obs_truth, percentiles=…, n_values=None, observables=None) -> StopNConvSweep
Requirements on the recording run (documented, not detectable from the data):
the sweep is only meaningful if the run did not itself stop early, i.e.
srelay_config={"stopping_criterion": "All"} and relay_solutions=True
(uncapped). A capped run (relay_solutions=<int>) is detected and rejected
for any N beyond the cap.
- cudaq_qec.relay_solutions.unpack(batch_opt_results, num_shots)
Reshape the flat
relay_solutions_*arrays.Accepts either the batched key set (
relay_solutions_counts/totals, from decode_batch) or the single-shot one (relay_solutions_count/total, from decode, withnum_shots == 1).
- cudaq_qec.relay_solutions.stop_nconv_sweep(results, obs_truth, *, percentiles, n_values=None, observables=None)
Reconstruct LER and iteration statistics for a range of
stop_nconv.For each N, each shot’s prediction is the minimum-weight record among its first
min(N, count)convergences (weight ties resolve to the earliest record), and its iteration count is the cumulative count at the Nth convergence – or the shot’s full-schedulenum_iterwhen it produced fewer than N convergences, exactly what stop_nconv=N would have run. Shots that never converged are scored with the decoder’s returned (fallback) result, which is identical for every N.- Parameters:
results – The
BatchDecoderResultof a recording run. Beyond the records themselves, per-shotnum_iteris read fromresults.opt_resultswhen any shot exhausts the schedule within the sweep range, andresults.resultsupplies the fallback prediction for never-converged shots.obs_truth – (num_shots, k) 0/1 array of actual observable flips.
percentiles – Iteration percentile(s) to report, in (0, 100]. A scalar yields
iters_percentilesof shape (n_N,); a list yields (n_p, n_N).n_values – Iterable of stop_nconv values to sweep; defaults to
1..max_records.observables – (k, width) 0/1 observables matrix. Required when the decoder ran without one (records are corrections); must be omitted when it ran with one (records already are observable classes).
- Returns:
- class cudaq_qec.relay_solutions.RelaySolutionRecords(width: int, max_records: int, counts: ndarray, totals: ndarray, iters: ndarray, weight: ndarray, packed: ndarray)
The
relay_solutions_*arrays with the record axes reconstructed.Rows beyond a shot’s own record count are padding: iteration count -1, weight +inf, packed bits all zero.
- bits(shot, record)
One record’s hard decision as a 0/1 vector of length
width.
- class cudaq_qec.relay_solutions.StopNConvSweep(n: ndarray, ler: ndarray, num_errors: ndarray, avg_iters: ndarray, iters_percentiles: ndarray, percentiles: ndarray, num_shots: int, num_unconverged: int, frac_exhausted: ndarray)
Per-N results of a
stop_nconvsweep. Arrays are indexed byn.
NVIDIA Fusion Decoder
- class cudaq_qec.relay_solutions.nv_fusion_decoder
A multi-threaded minimum-weight perfect matching (MWPM) decoder based on the NV Fusion Brickwall algorithm. It is in essence a combination of fusion blossom with sparse blossom: the detector matching graph is partitioned into temporal blocks that are solved independently and then fused across their boundaries (fusion blossom), while each individual block is solved by PyMatching’s sparse blossom implementation. Blocks and the fuses between them form a dependency DAG that is dispatched to a worker pool as syndrome data arrives, so the decoder is designed to maximize parallel processing utilization and reach minimal latency in a streaming, realtime decoding environment. Offline batch decoding is supported through the same scaffold.
Thread count is set by
num_threads, which defaults to1; pass0to size the pool from the hardware. That pool parallelizes the blocks and fuses within one shot; it does not make a decoder callable from several threads at once.Important
One decoder decodes one shot at a time. Both
decode()and the realtime enqueue path keep per-shot state on the decoder – syndrome routing, block and fuse matching state, and the herald flags – so two threads sharing a decoder corrupt each other. Use one decoder per thread.This applies to
decode_async(), which runsdecode()on a new thread: keeping two futures outstanding on the same decoder is not supported.decode_batch()is sequential and therefore safe.Concurrent
decode()calls on one nv-fusion decoder raisestd::logic_errorrather than interleaving, so the misuse surfaces as an exception instead of a wrong answer.The decoder is graphlike: every error mechanism must touch exactly one (boundary edge) or two (bulk edge) detectors. Constructing from
H, that is a constraint on its columns; constructing from a DEM, Stim’s^decomposition suggestions are applied first, so a hyperedge is accepted only when the DEM suggests a graphlike decomposition for it. Block size is set byblock_leaf_size, blocks overlap so that fusing adjacent pairs propagates information across their shared boundary, and the observable corrections that come out are XOR-accumulated into a running Pauli frame.Important
A single-leaf schedule is one block with no fuses, and is exact monolithic MWPM. Any fused schedule is a windowed approximation: the brickwall has two fuse layers, so each block’s corrections are decided within a window of at most four leaves (two for the first and last blocks) rather than against the whole shot. An error chain longer than that window can be matched differently than monolithic MWPM would match it, and the result is not always flagged – the heralded-failure check fires on unresolved virtual-boundary matches, which an undersized window does not reliably produce.
Sizing the leaf from the code distance is what keeps the window large enough for this to be safe; see
block_leaf_sizebelow.References:
Note
It is required to create decoders with the
get_decoderAPI from the CUDA-QX extension points API, such asimport cudaq_qec as qec import numpy as np # Two boundary edges carrying one observable each, plus a timelike # edge between the detectors carrying none. H = np.array([[1, 0, 1], # rows = detectors [0, 1, 1]], dtype=np.uint8) O = np.array([[1, 0, 0], # rows = observables [0, 1, 0]], dtype=np.uint8) opts = { "O": O, # int32, one round index per detector "detector_round": np.array([0, 1], dtype=np.int32), } decoder = qec.get_decoder('nv-fusion-decoder', H, **opts) decoder.decode([1.0, 0.0]) # -> [1.0, 0.0]
#include "cudaq/qec/decoder.h" // Two boundary edges carrying one observable each, plus a // timelike edge between the detectors carrying none. cudaqx::tensor<uint8_t> H, O; std::vector<uint8_t> H_vec = {1, 0, 1, // rows = detectors 0, 1, 1}; std::vector<uint8_t> O_vec = {1, 0, 0, // rows = observables 0, 1, 0}; H.copy(H_vec.data(), {2, 3}); O.copy(O_vec.data(), {2, 3}); cudaqx::heterogeneous_map opts; opts.insert("O", O); opts.insert("detector_round", std::vector<int32_t>{0, 1}); auto decoder = cudaq::qec::get_decoder("nv-fusion-decoder", H, opts); decoder->decode({1.0, 0.0}); // -> {1.0, 0.0}
Note
The
"nv-fusion-decoder"implements thecudaq_qec.Decoderinterface for Python and thecudaq::qec::decoderinterface for C++, so it supports all the methods in those respective classes.- Parameters:
H – Parity-check matrix (sparse binary matrix or dense
tensor<uint8_t>), shape(num_detectors, num_error_mechanisms). When the matching graph is built from it, each column must have exactly one or two non-zero rows (graphlike error mechanisms); alongside adem_stringonly its shape is read. Pass Stim DEM text in this position instead to construct from a DEM, which needs neitherHnorO.params –
Heterogeneous map of parameters:
Note
These are the C++ and Python parameters. The realtime YAML surface is narrower:
decoder_custom_argsaccepts onlynum_threads,block_leaf_size,fusion_strategyanderror_rate_vec, and rejects any other key. A YAML configuration supplies the matrices and the temporal layout through the top-levelH_sparse,O_sparseandD_sparsefields instead.Construction:
dem_string(str): Serialized Stim detector error model string. Equivalent to passing the DEM text in place ofH, and only needed when you want to supply anHof your own alongside it: the matching graph and observable wiring still come from the DEM, soHis read for its shape alone and its dimensions must agree withdem.count_detectors()anddem.count_errors(). Either way, Stim detector coordinates supply the per-detector temporal round map automatically, removing the need to passdetector_roundexplicitly — provided every detector carries a coordinate. A DEM with an uncoordinated detector is rejected at construction, asking fordetector_round.O(tensor<uint8_t>orsparse_binary_matrix): Observable matrix, shape(num_observables, num_error_mechanisms). When provided,decode()returns observable flip predictions of lengthnum_observablesrather than a block-size correction vector; without it theHpath stays in edge mode. Redundant with a DEM, which already carries the observable wiring and decodes todem.count_observables()observables on its own. Supplying one alongside a DEM is only useful to widen the correction buffer past that count; anOwith fewer rows than the DEM declares is rejected. Takes precedence over theO_sparseset-from-outside path: whenOis given, a laterset_O_sparse()does not change the observable wiring the decoder matches against. Supply one or the other to keep that unambiguous.
Temporal layout (one of the following is required, unless a DEM supplies detector coordinates):
detector_round(vector<int32_t>): Per-detector temporal round index, lengthH.num_rows(). Maps each parent detector to its round in 0-based integer coordinates. Takes highest priority over all automatic derivation paths.D_sparse(vector<int64_t>): Flat measurement-to-detector map in row-major format, with-1row terminators. The decoder infers the per-detector round from the column stride of the two-entry (timelike) rows. Rows with more than two entries are treated as terminal boundary detectors placed in the last round. Used automatically by the realtime layer; can also be supplied explicitly whendetector_roundis not available at construction time.
The three sources are consulted in that order: an explicit
detector_roundfirst, then DEM coordinates, thenD_sparse. A DEM therefore takes precedence overD_sparse, and one missing a detector coordinate fails rather than falling back to it. Without a DEM and with neither vector given, scaffold construction is deferred untilset_D_sparse()is called.Blocking and threading:
block_leaf_size(uint64): Number of temporal rounds per leaf block. Smaller values reduce per-block latency but increase fuse overhead; larger values amortize fuse cost at the expense of latency. An explicit value is always honored as given; if it is below the measured code distance and the schedule fuses more than one block, construction logs a warning, because such a leaf cannot contain a logical error chain and inflates the logical error rate without raising a heralded failure.When omitted, the schedule is selected automatically:
192 rounds or fewer – a single leaf spanning the shot, which is exact monolithic MWPM.
More than 192 rounds – a fused schedule with a leaf of
2 * d, wheredis the code distance measured from the matching graph as the shortest undetectable logical error. This bounds tail latency, which under a single leaf grows linearly with the number of rounds.Distance not measurable (a model with no observables, or none admitting an undetectable logical error) – a single leaf at any shot length, since fusing on an unverified leaf height inflates the logical error rate silently.
Warning
The second case trades exactness for latency, and it is the default. A shot longer than 192 rounds is decoded by the windowed approximation described above, not by monolithic MWPM. A leaf of
2 * dputs four times the margin over the smallest leaf measured to be safe (0.5 * d), and over d=5..21 on rotated surface and repetition codes it reproduced single-leaf corrections shot for shot – but that is a measured result on those codes and noise models, not a guarantee for every code.To keep exact monolithic MWPM at any shot length, set
block_leaf_sizeto the shot’s full round count explicitly.This differs from earlier releases, where omitting
block_leaf_sizealways produced a single leaf.num_threads(uint64): Number of CPU threads to use during scaffold construction and parallel fuse operations. Defaults to1.
Edge weights:
error_rate_vec(vector<double>): Physical error probability per error mechanism (column ofH), lengthH.num_cols(). Each value must be in the range(0, 0.5]. When provided, edge weights are computed as the log-likelihood ratio-log(p / (1 - p)). When absent, unit weights are used. Applies to theHconstruction path only: a DEM already carries a probability per error mechanism, soerror_rate_vecis ignored when constructing from one.
Fusion schedule:
fusion_strategy(str): Fusion schedule to use. Currently only"brickwall"is supported.
Decode result format:
decode()returns aDecoderResultwhere:result— lengthnum_observableswhen constructed from a DEM or withO, otherwise lengthblock_size. In observable mode each entry is0.0or1.0indicating a predicted logical flip. In edge mode each entry is1.0if the corresponding H column was selected as a matching edge,0.0otherwise.converged—Trueif the fuse pass raised no herald flag. AFalsevalue indicates a suspect or ambiguous match; the correction is still populated and may be used.opt_results— heterogeneous map with key:heralded(bool): Raw herald flag from the brickwall fuse pass, corresponding toconverged = not heralded.
Realtime streaming:
The decoder supports the base-class realtime API (
enqueue_syndrome,get_obs_corrections,reset_decoder,clear_corrections). The realtime path accepts raw per-round measurement bits rather than pre-differenced detector bits; the decoder accumulates measurements internally and computes detector events viaD_sparseas each detector round’s measurements become complete. Observable corrections are XOR-accumulated into a running Pauli frame and exposed viaget_obs_corrections().Note
D_sparsemust be configured (either viaset_D_sparse()or theD_sparseconstructor parameter) before the first call toenqueue_syndrome(). The realtime layer in the CUDA-QX QEC stack setsD_sparseautomatically; direct callers must set it manually.Note
Observable corrections accumulate across shots. Call
clear_corrections()orreset_decoder()between shots to reset the Pauli frame.reset_decoder()also rewinds the streaming session;clear_corrections()does not.
Sliding Window Decoder
- class cudaq_qec.relay_solutions.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
decodefunction (and its variants likedecode_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_decoderAPI from the CUDA-QX extension points API, such asimport 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 thecudaq_qec.Decoderinterface for Python and thecudaq::qec::decoderinterface 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 theerror_rate_vecparameter 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 havenum_syndromes_per_rounddetectors.) For a single-basis DEM fromz_dem_from_memory_circuit()(respectivelyx_dem_from_memory_circuit()), every layer hascode.get_num_z_stabilizers()(respectivelycode.get_num_x_stabilizers()) detectors, so this may be left at its default. For a full DEM fromdem_from_memory_circuit(),num_syndromes_per_roundiscode.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 tocode.get_num_z_stabilizers()for Z-basis preps (prep0/prep1) orcode.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.relay_solutions.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_decoderAPI from the CUDA-QX extension points API, such asimport 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 thecudaq_qec.Decoderinterface for Python and thecudaq::qec::decoderinterface for C++, so it supports all the methods in those respective classes.Note
The parity check matrix
His 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 usingdecode_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 withengine_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 withonnx_load_path.
Optional:
engine_save_path(string): Path to save the built TensorRT engine. Only applicable when usingonnx_load_path. Saving the engine allows for faster initialization in subsequent runs by usingengine_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, thedecode()method automatically zero-pads single syndromes to fill the batch. Thedecode_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 theglobal_decoder. The accepted keys follow the parameter schema of the named global decoder. When constructing the decoder directly (rather than from YAML), supplyglobal_decoder_params(which may be an empty map) wheneverglobal_decoderis 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>= 0and 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_qecplugin 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.relay_solutions.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_decoderAPI 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 thecudaq_qec.Decoderinterface for Python and thecudaq::qec::decoderinterface 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.sparsematrix or a dense NumPyuint8array may be passed.params –
Heterogeneous map of parameters:
error_rate_vec(vector<double>): Per-error prior probabilities, one per column ofH(lengthblock_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 to1.0.merge_strategy(string): How to combine parallel edges that map to the same pair of detectors. One of"disallow"(default for theH-only path),"independent","smallest_weight","keep_original", or"replace".O(tensor, optional): Anum_observables x block_sizebinary matrix. When provided, the decoder returns predicted observable flips (decode_to_obs) instead of a raw error vector, andmerge_strategydefaults to"independent"to match PyMatching’s detector-error-model construction.
Chromobius Decoder
- class cudaq_qec.relay_solutions.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_decoderAPI 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 thecudaq_qec.Decoderinterface for Python and thecudaq::qec::decoderinterface 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_sizecorrection 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’sopt_resultsunder 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,subschemaordiscriminator), orNonewhen 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_configYAML files. Generated from the decoder parameter schemas registered in this installation (including loaded third-party decoder plugins), for use with standard tools such ascheck-jsonschema, the pythonjsonschemapackage, 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_argsagainst the parameter schema registered for this decodertype: unknown keys, missing required keys, and the schema’s own validation hook. RaisesRuntimeErroron the first violation. YAML parsing applies the same checks automatically; call this to vet a configuration built programmatically before using it. Also available onmulti_decoder_configto 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_matrixordem.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 itsD_sparse.- Parameters:
m2d – List of lists of measurement indices.
m2d[d]contains the measurement indices whose XOR forms detectord. Obtain this from the second element of the tuple returned byDecoderContext.x_component(),DecoderContext.z_component(), orDecoderContext.full_component().- Returns:
-1-terminated sparse vector suitable fordecoder_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.
sample_memory_circuit(code: cudaq_qec.Code, numShots: int, numRounds: int, noise: cudaq.NoiseModel | None = None) -> tuple
Sample the memory circuit of the code
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 layout — sample_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), orcode.get_num_x_stabilizers()for X-basis preparations (prepp/prepm).S(inter-round block) =num_z_stabilizers + num_x_stabilizersdetectors per round transition (num_rounds - 1blocks 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.
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 withpip 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.