CUDA-Q QEC C++ API

Code

class code : public cudaqx::extension_point<code, const heterogeneous_map&>

Base class for quantum error correcting codes in CUDA-Q.

This class provides the core interface and functionality for implementing quantum error correcting codes in CUDA-Q. It defines the basic operations that any QEC code must support and provides infrastructure for syndrome measurement and error correction experiments.

To implement a new quantum error correcting code:

  1. Create a new class that inherits from code

  2. Implement the protected virtual methods:

  3. Define quantum kernels for each required logical operation (these are the fault tolerant logical operation implementations)

  4. Register the operations in your constructor using the operation_encodings map on the base class

  5. Register your new code type using CUDAQ_EXT_PT_REGISTER_TYPE

Example implementation:

__qpu__ void x_kernel(patch p);
__qpu__ void z_kernel(patch p);
class my_code : public qec::code {
protected:
  std::size_t get_num_data_qubits() const override { return 7; }
  std::size_t get_num_ancilla_qubits() const override { return 6; }
  std::size_t get_num_ancilla_x_qubits() const override { return 3; }
  std::size_t get_num_ancilla_z_qubits() const override { return 3; }

public:
  my_code(const heterogeneous_map& options) : code() {
    // Can use user-specified options, e.g. auto d =
    options.get<int>("distance");
    operation_encodings.insert(std::make_pair(operation::x, x_kernel));
    operation_encodings.insert(std::make_pair(operation::z, z_kernel));
    // Register other required operations...

    // Define the default stabilizers!
    m_stabilizers = fromPauliWords({"XXXX", "ZZZZ"});
  }

  CUDAQ_EXTENSION_CUSTOM_CREATOR_FUNCTION(
    my_code,
    static std::unique_ptr<qec::code> create(const heterogeneous_map
&options) { return std::make_unique<my_code>(options);
    }
  )
};

CUDAQ_EXT_PT_REGISTER_TYPE(my_code)

Supported quantum operations for error correcting codes

Subclassed by cudaq::qec::repetition::repetition, cudaq::qec::steane::steane, cudaq::qec::surface_code::surface_code

Public Types

using one_qubit_encoding = cudaq::qkernel<void(patch)>

Type alias for single qubit quantum kernels.

using two_qubit_encoding = cudaq::qkernel<void(patch, patch)>

Type alias for two qubit quantum kernels.

using stabilizer_round = cudaq::qkernel<std::vector<cudaq::measure_result>(patch, const std::vector<std::size_t>&, const std::vector<std::size_t>&)>

Type alias for stabilizer measurement kernels. The two vector arguments are the flattened X and Z stabilizer schedule matrices (see get_stabilizer_schedule_x/z()): entry 0 = no support, entry k >= 1 = interaction at timestep k.

using encoding = std::variant<one_qubit_encoding, two_qubit_encoding, stabilizer_round>

Type alias for quantum operation encodings.

Public Functions

virtual std::size_t get_num_data_qubits() const = 0

Get the number of physical data qubits needed for the code.

Returns:

Number of data qubits

virtual std::size_t get_num_ancilla_qubits() const = 0

Get the total number of ancilla qubits needed.

Returns:

Total number of ancilla qubits

virtual std::size_t get_num_ancilla_x_qubits() const = 0

Get number of ancilla qubits needed for X stabilizer measurements.

Returns:

Number of X-type ancilla qubits

virtual std::size_t get_num_ancilla_z_qubits() const = 0

Get number of ancilla qubits needed for Z stabilizer measurements.

Returns:

Number of Z-type ancilla qubits

virtual std::size_t get_num_x_stabilizers() const = 0

Get number of X stabilizer that can be measured.

Returns:

Number of X-type stabilizers

virtual std::size_t get_num_z_stabilizers() const = 0

Get number of Z stabilizer that can be measured.

Returns:

Number of Z-type stabilizers

cudaqx::tensor<uint8_t> get_parity() const

Get the full parity check matrix H = (Hx | Hz)

Returns:

Tensor representing the parity check matrix

cudaqx::tensor<uint8_t> get_parity_x() const

Get the X component of the parity check matrix.

Returns:

Tensor representing Hx

cudaqx::tensor<uint8_t> get_parity_z() const

Get the Z component of the parity check matrix.

Returns:

Tensor representing Hz

inline virtual cudaqx::tensor<uint8_t> get_stabilizer_schedule_x() const

Get the X-stabilizer schedule matrix passed to the code’s stabilizer_round kernel. Same shape and support pattern as get_parity_x(); entry 0 means no support, entry k >= 1 means the ancilla-data interaction executes at timestep k, so a code can encode an optimized (e.g. hook-error-aware) gate order.

Returns:

Tensor of scheduled interactions; the default implementation returns get_parity_x() unchanged (every interaction at timestep 1, i.e. ascending qubit-index order).

inline virtual cudaqx::tensor<uint8_t> get_stabilizer_schedule_z() const

Get the Z-stabilizer schedule matrix passed to the code’s stabilizer_round kernel. See get_stabilizer_schedule_x().

Returns:

Tensor of scheduled interactions; the default implementation returns get_parity_z() unchanged.

cudaqx::tensor<uint8_t> get_pauli_observables_matrix() const

Get Lx stacked on Lz.

Returns:

Tensor representing pauli observables

cudaqx::tensor<uint8_t> get_observables_x() const

Get the Lx observables.

Returns:

Tensor representing Lx

cudaqx::tensor<uint8_t> get_observables_z() const

Get the Lz observables.

Returns:

Tensor representing Lz

inline const std::vector<cudaq::spin_op_term> &get_stabilizers() const

Get the stabilizer generators.

Returns:

Reference to stabilizers

inline bool contains_operation(operation op) const

Return true if this code contains the given operation encoding.

Public Static Functions

static std::unique_ptr<code> get(const std::string &name, const std::vector<cudaq::spin_op_term> &stabilizers, const heterogeneous_map options = {})

Factory method to create a code instance with specified stabilizers.

Parameters:
  • name – Name of the code to create

  • stabilizers – Stabilizer generators for the code

  • options – Optional code-specific configuration options

Returns:

Unique pointer to created code instance

static std::unique_ptr<code> get(const std::string &name, const heterogeneous_map options = {})

Factory method to create a code instance.

Parameters:
  • name – Name of the code to create

  • options – Optional code-specific configuration options

Returns:

Unique pointer to created code instance

struct patch

Represents a logical qubit patch for quantum error correction.

This type is for CUDA-Q kernel code only.

This structure defines a patch of qubits used in quantum error correction codes. It consists of data qubits and ancilla qubits for X and Z stabilizer measurements.

Public Members

cudaq::qview data

View of the data qubits in the patch.

cudaq::qview ancx

View of the ancilla qubits used for X stabilizer measurements.

cudaq::qview ancz

View of the ancilla qubits used for Z stabilizer measurements.

class repetition : public cudaq::qec::code

Implementation of the repetition quantum error correction code.

Public Functions

repetition(const heterogeneous_map&)

Constructs a repetition code instance.

class steane : public cudaq::qec::code

Steane code implementation.

Public Functions

steane(const heterogeneous_map&)

Constructor for the Steane code.

enum cudaq::qec::surface_code::surface_role

enumerates the role of a grid site in the surface codes stabilizer grid

Values:

enumerator amx
enumerator amz
enumerator empty
enum class cudaq::qec::surface_code::sc_orientation

Surface-code orientation: controls which Pauli type (X or Z) occupies the bulk interior and which boundaries are X-type vs Z-type.

Geometry names follow the NVIDIA/Ising-Decoding naming convention (https://github.com/NVIDIA/Ising-Decoding): Ising’s code_rotation convention uses first character = bulk syndrome type, second character = rotated_type. By geometry: XV and ZH place X-type stabilizers on the left/right boundaries (Z on top/bottom); XH and ZV place X-type on top/bottom (Z on left/right). XV vs ZH (and XH vs ZV) differ in the bulk X/Z checkerboard.

Observable convention: get_spin_op_observables() returns the valid logical pair for the orientation. XV/ZH use X obs along the top row and Z obs along the left column; XH/ZV swap them (X along the left column, Z along the top row). See get_spin_op_observables() for details.

Values:

enumerator XV
enumerator XH
enumerator ZV
enumerator ZH
struct vec2d

describes the 2d coordinate on the stabilizer grid

class stabilizer_grid

Generates and keeps track of the 2d grid of stabilizers in the rotated surface code. Following same layout convention as in: https://arxiv.org/abs/2311.10687 Grid layout is arranged from left to right, top to bottom (row major storage) grid_length = 4 example:

(0,0)   (0,1)   (0,2)   (0,3)
(1,0)   (1,1)   (1,2)   (1,3)
(2,0)   (2,1)   (2,2)   (2,3)
(3,0)   (3,1)   (3,2)   (3,3)
Each entry on the grid can be an X stabilizer, Z stabilizer, or empty, as is needed on the edges. The diagrams below show the default ZH orientation. Other orientations assign X/Z roles according to sc_orientation. The grid length of 4 corresponds to a distance 3 surface code, which results in:
e(0,0)  e(0,1)  Z(0,2)  e(0,3)
X(1,0)  Z(1,1)  X(1,2)  e(1,3)
e(2,0)  X(2,1)  Z(2,2)  X(2,3)
e(3,0)  Z(3,1)  e(3,2)  e(3,3)

This is seen through the print_stabilizer_grid() member function. To get rid of the empty sites, the print_stabilizer_coords() function is used:

                Z(0,2)
X(1,0)  Z(1,1)  X(1,2)
        X(2,1)  Z(2,2)  X(2,3)
        Z(3,1)

and to get the familiar visualization of the distance three surface code, the print_stabilizer_indices results in:

        Z0
X0  Z1  X1
    X2  Z2  X3
    Z3

The data qubits are located at the four corners of each of the weight-4 stabilizers. They are also organized with index increasing from left to right, top to bottom:

d0  d1  d2
d3  d4  d5
d6  d7  d8

Public Functions

stabilizer_grid(uint32_t distance, sc_orientation orientation = sc_orientation::ZH)

Construct the grid from the code’s distance.

stabilizer_grid()

Empty constructor.

sc_orientation get_orientation() const

Get the orientation used to construct this stabilizer grid.

void print_stabilizer_grid() const

Print a 2d grid of stabilizer roles.

void print_stabilizer_coords() const

Print a 2d grid of stabilizer coords.

void print_stabilizer_indices() const

Print a 2d grid of stabilizer indices.

void print_data_grid() const

Print a 2d grid of data qubit indices.

void print_stabilizer_maps() const

Print the coord <–> indices maps.

void print_stabilizers() const

Print the stabilizers in sparse pauli format.

std::vector<cudaq::spin_op_term> get_spin_op_stabilizers() const

Get the stabilizers as a vector of cudaq::spin_op_terms.

cudaqx::tensor<uint8_t> get_cnot_schedule_x() const

Get the CNOT schedule matrix for the X stabilizers.

Note

The per-plaquette CNOT order is chosen per the grid’s orientation so that mid-round ancilla faults (“hook errors”), which propagate onto the data qubits of the remaining CNOTs, land perpendicular to the same-type logical operator instead of along it, following the standard zigzag schedule of https://arxiv.org/abs/1404.3747. Rows are ordered to match the rows of to_parity_matrix() / code::get_parity_x().

Returns:

Tensor with the same shape and support pattern as the X block of the parity check matrix (num_x_stabilizers x distance^2). Entry 0 means the ancilla does not touch that data qubit; entry k in [1, 4] means the ancilla-data CNOT executes at timestep k of the stabilizer round.

cudaqx::tensor<uint8_t> get_cnot_schedule_z() const

Get the CNOT schedule matrix for the Z stabilizers.

Note

See get_cnot_schedule_x() for the hook-error rationale and row ordering.

Returns:

Tensor with the same shape and support pattern as the Z block of the parity check matrix (num_z_stabilizers x distance^2). Entry 0 means the ancilla does not touch that data qubit; entry k in [1, 4] means the data-ancilla CNOT executes at timestep k of the stabilizer round.

std::vector<std::size_t> get_cnot_schedule_pairs_x() const

Get the X-stabilizer CNOT schedule as a flat list of (stabilizer index, data index) pairs, ordered by timestep within each stabilizer — the replay format for kernels that take an explicit CNOT pair list. Stabilizer indices match the rows of get_cnot_schedule_x().

std::vector<std::size_t> get_cnot_schedule_pairs_z() const

Z-stabilizer counterpart of get_cnot_schedule_pairs_x().

std::vector<cudaq::spin_op_term> get_spin_op_observables() const

Get the observables as a vector of cudaq::spin_op_terms.

Note

Returns the correct logical pair for the grid’s orientation. For XV and ZH the X observable runs along the top row of data qubits and the Z observable along the left column; for XH and ZV the assignment is swapped (X along the left column, Z along the top row) to match their boundary types. The returned X/Z observables commute with the stabilizers and anticommute with each other for every orientation.

Returns:

The X logical observable first, followed by the Z logical observable.

Public Members

uint32_t distance = 0

The distance of the code determines the number of data qubits per dimension.

uint32_t grid_length = 0

length of the stabilizer grid for distance = d data qubits, the stabilizer grid has length d+1

std::vector<surface_role> roles

flattened vector of the stabilizer grid sites roles’ grid idx -> role stored in row major order

std::vector<vec2d> x_stab_coords

x stab index -> 2d coord

std::vector<vec2d> z_stab_coords

z stab index -> 2d coord

std::map<vec2d, size_t> x_stab_indices

2d coord -> z stab index

std::map<vec2d, size_t> z_stab_indices

2d coord -> z stab index

std::vector<vec2d> data_coords

data index -> 2d coord data qubits are in an offset 2D coord system from stabilizers

std::map<vec2d, size_t> data_indices

2d coord -> data index

std::vector<std::vector<size_t>> x_stabilizers

Each element is an X stabilizer specified by the data qubits it has support on In surface code, can have weight 2 or weight 4 stabs So {x,z}_stabilizer[i].size() == 2 || 4.

std::vector<std::vector<size_t>> z_stabilizers

Each element is an Z stabilizer specified by the data qubits it has support on.

class surface_code : public cudaq::qec::code

surface_code implementation

Public Functions

surface_code(const heterogeneous_map&)

Constructor for the surface_code.

virtual cudaqx::tensor<uint8_t> get_stabilizer_schedule_x() const override

Get the hook-error-aware X-stabilizer CNOT schedule matrix. See stabilizer_grid::get_cnot_schedule_x().

virtual cudaqx::tensor<uint8_t> get_stabilizer_schedule_z() const override

Get the hook-error-aware Z-stabilizer CNOT schedule matrix. See stabilizer_grid::get_cnot_schedule_z().

Public Members

stabilizer_grid grid

Extension creator function for the surface_code.

Grid to keep track of topological arrangement of qubits.

Detector Error Model

struct detector_error_model

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.

Shared size parameters among the matrix types.

  • detector_error_matrix: num_detectors x num_error_mechanisms [d, e]

  • error_rates: num_error_mechanisms

  • observables_flips_matrix: num_observables x num_error_mechanisms [k, e]

Note

The C++ API for this class may change in the future. The Python API is more likely to be backwards compatible.

Public Functions

std::size_t num_detectors() const

Return the number of rows in the detector_error_matrix.

std::size_t num_error_mechanisms() const

Return the number of columns in the detector_error_matrix, error_rates, and observables_flips_matrix.

std::size_t num_observables() const

Return the number of rows in the observables_flips_matrix.

void canonicalize_for_rounds(uint32_t num_syndromes_per_round, bool remove_zero_syndrome_errors = false)

Put the detector_error_matrix into canonical form for round-based decoding: topologically order the columns and merge columns that share the same detector AND observable signature, composing their rates so the canonicalized model matches the input.

Note

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

Parameters:
  • num_syndromes_per_round – Number of syndromes per round, used to order columns by the rounds their detectors span; 0 disables the per-round key.

  • remove_zero_syndrome_errors – If false (default), zero-syndrome columns that still flip an observable (undetectable logical errors) are retained so the observable-flip probability is preserved; if true, all columns with no detector signature are dropped (appropriate when the DEM is consumed only for round-based decoding).

void canonicalize_for_rounds_with_boundary(uint32_t num_syndromes_per_round, uint32_t num_boundary_syndromes, bool remove_zero_syndrome_errors)

Boundary-aware variant of canonicalize_for_rounds for memory-experiment DEMs whose first and last detector layers (the boundaries) are narrower than the interior layers.

Parameters:
  • num_syndromes_per_round – Interior-layer width (syndromes per interior round).

  • num_boundary_syndromes – Width of the leading and trailing boundary layers: the first and last num_boundary_syndromes rows form the boundary rounds and each intermediate block of num_syndromes_per_round rows is an interior round.

  • remove_zero_syndrome_errors – Same meaning as in the scalar overload.

Throws:

std::invalid_argument – if num_syndromes_per_round is 0 or num_boundary_syndromes > num_syndromes_per_round.

Public Members

cudaqx::tensor<uint8_t> 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.

std::vector<double> error_rates

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

cudaqx::tensor<uint8_t> 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.

std::optional<std::vector<std::size_t>> 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.

struct decoder_context

Lazy handle returned by decoder_context_from_memory_circuit.

Stores the raw (uncanonicalized) circuit analysis. Call a component method to canonicalize exactly the stabilizer type needed and obtain a decoder_inputs:

Public Functions

std::size_t num_measurements() const

Total number of measurements per shot (column count of m2d/m2o).

decoder_inputs x_component() const

Canonicalize X-stabilizer detectors; return decoder_inputs.

decoder_inputs z_component() const

Canonicalize Z-stabilizer detectors; return decoder_inputs.

decoder_inputs full_component() const

Canonicalize both stabilizer types with boundary awareness; return decoder_inputs.

detector_error_model cudaq::qec::dem_from_memory_circuit(const code &code, operation statePrep, std::size_t numRounds, cudaq::noise_model &noise, bool decompose_errors = false)

Given a memory circuit setup, generate a DEM.

Parameters:
  • code – QEC Code to sample

  • statePrep – Initial state preparation operation

  • numRounds – Number of stabilizer measurement rounds

  • noise – Noise model to apply

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

Returns:

Detector error model

detector_error_model cudaq::qec::x_dem_from_memory_circuit(const code &code, operation statePrep, std::size_t numRounds, cudaq::noise_model &noise, bool decompose_errors = false)

Given a memory circuit setup, generate a DEM for X stabilizers.

Parameters:
  • code – QEC Code to sample

  • statePrep – Initial state preparation operation

  • numRounds – Number of stabilizer measurement rounds

  • noise – Noise model to apply

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

Returns:

Detector error model

detector_error_model cudaq::qec::z_dem_from_memory_circuit(const code &code, operation statePrep, std::size_t numRounds, cudaq::noise_model &noise, bool decompose_errors = false)

Given a memory circuit setup, generate a DEM for Z stabilizers.

Parameters:
  • code – QEC Code to sample

  • statePrep – Initial state preparation operation

  • numRounds – Number of stabilizer measurement rounds

  • noise – Noise model to apply

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

Returns:

Detector error model

decoder_context cudaq::qec::decoder_context_from_memory_circuit(const code &code, operation statePrep, std::size_t numRounds, cudaq::noise_model &noise, bool decompose_errors = false)

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

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

detector_error_model cudaq::qec::dem_from_stim_text(const std::string &dem_text, bool use_decomp_suggestions = false)

Parse the Stim DEM string dem_text into detector/observable flip matrices and error rates. DEM-native decoders should consume raw DEM text instead. By default (use_decomp_suggestions = false) the ‘^’ separators are ignored and each error instruction produces a single column. If use_decomp_suggestions is true, error mechanisms that carry an explicit graphlike decomposition (components separated by ‘^’) are expanded into one column per component, each inheriting the probability of the parent instruction. error_ids is always left as nullopt. Note that this is a lossy approximation of the original DEM.

Detector Error Model (DEM) Sampling

CPU sampling (host tensors):

std::tuple<cudaqx::tensor<uint8_t>, cudaqx::tensor<uint8_t>> cudaq::qec::dem_sampler::cpu::sample_dem(const cudaqx::tensor<uint8_t> &check_matrix, std::size_t numShots, const std::vector<double> &error_probabilities)

Sample measurements from a check matrix (CPU, per-mechanism probs)

Parameters:
  • check_matrix – Binary matrix [num_checks × num_error_mechanisms]

  • numShots – Number of measurement shots

  • error_probabilities – Per-error-mechanism probabilities

Returns:

(checks [numShots × num_checks], errors [numShots × num_mechanisms])

std::tuple<cudaqx::tensor<uint8_t>, cudaqx::tensor<uint8_t>> cudaq::qec::dem_sampler::cpu::sample_dem(const cudaqx::tensor<uint8_t> &check_matrix, std::size_t numShots, const std::vector<double> &error_probabilities, unsigned seed)

Sample measurements from a check matrix with seed (CPU)

GPU sampling (device pointers, requires cuStabilizer):

bool cudaq::qec::dem_sampler::gpu::sample_dem(const uint8_t *d_check_matrix, size_t num_checks, size_t num_error_mechanisms, const double *d_error_probabilities, size_t num_shots, unsigned seed, uint8_t *d_checks_out, uint8_t *d_errors_out, std::uintptr_t stream_handle = 0)

GPU DEM sampling with caller-provided device pointers.

The function composes (all GPU memory is allocated and freed internally per call):

  1. pack_check_matrix_rowwise (dense uint8 → bitpacked uint32)

  2. custabilizerSampleProbArraySparseCompute (sparse Bernoulli sampling)

  3. custabilizerGF2SparseDenseMatrixMultiply (syndrome = errors * H^T)

  4. unpack_syndromes_gpu (bitpacked uint32 → uint8)

  5. csr_to_dense_fused (CSR errors → dense uint8)

Parameters:
  • d_check_matrix – Device pointer [num_checks × num_error_mechanisms]

  • num_checks – Number of checks (rows of H)

  • num_error_mechanisms – Number of error mechanisms (columns of H)

  • d_error_probabilities – Device pointer [num_error_mechanisms]

  • num_shots – Number of samples

  • seed – RNG seed

  • d_checks_out – Device pointer [num_shots × num_checks] (OUTPUT)

  • d_errors_out – Device pointer [num_shots × num_error_mechanisms] (OUT)

  • stream_handle – Optional CUDA stream handle (uintptr_t cast), 0 for default stream

Returns:

true on success, false if cuStabilizer is unavailable

Legacy convenience wrappers (delegate to cpu::sample_dem; prefer the dem_sampler::cpu::sample_dem overloads above):

std::tuple<cudaqx::tensor<uint8_t>, cudaqx::tensor<uint8_t>> cudaq::qec::dem_sampling(const cudaqx::tensor<uint8_t> &check_matrix, std::size_t numShots, const std::vector<double> &error_probabilities)

Sample a detector error model on CPU (legacy API).

Parameters:
  • check_matrix – Binary matrix [num_checks x num_error_mechanisms]

  • numShots – Number of independent Monte-Carlo shots

  • error_probabilities – Per-error-mechanism Bernoulli probabilities

Returns:

Tuple of (checks, errors)

std::tuple<cudaqx::tensor<uint8_t>, cudaqx::tensor<uint8_t>> cudaq::qec::dem_sampling(const cudaqx::tensor<uint8_t> &check_matrix, std::size_t numShots, const std::vector<double> &error_probabilities, unsigned seed)

Sample a detector error model on CPU (legacy API, seeded).

Parameters:
  • check_matrix – Binary matrix [num_checks x num_error_mechanisms]

  • numShots – Number of independent Monte-Carlo shots

  • error_probabilities – Per-error-mechanism Bernoulli probabilities

  • seed – RNG seed for reproducibility

Returns:

Tuple of (checks, errors)

Decoder Interfaces

struct decoder_inputs

Finalized decoder inputs: a canonicalized DEM and measurement maps.

std::vector<std::int64_t> cudaq::qec::d_sparse(const cudaq::M2DSparseMatrix &m2d)

Flatten an M2DSparseMatrix into the -1-terminated sparse vector a realtime decoder config expects for its D_sparse.

using cudaq::qec::decoder_init = std::variant<sparse_binary_matrix, std::string>

Decoder construction input: either a parity-check matrix or raw Stim DEM text.

class decoder : public cudaqx::extension_point<decoder, const decoder_init&, const cudaqx::heterogeneous_map&>

The decoder base class should be subclassed by specific decoder implementations. The heterogeneous_map provides a placeholder for arbitrary constructor parameters that can be unique to each specific decoder.

Public Types

enum decode_result_type

Indicates whether decode() returns a full error frame (length block_size) or an already-projected observable frame (length num_observables). Decoders that accept an “O” observable matrix in their constructor params should call set_result_type(decode_to_obs); all others default to decode_to_errs.

Note: even in decode_to_obs mode, set_O_sparse() must still be called so that enqueue_syndrome() knows num_observables and can size the corrections buffer correctly.

Values:

enumerator decode_to_errs

result.size() == block_size; enqueue_syndrome projects via O_sparse

enumerator decode_to_obs

result.size() == num_observables; enqueue_syndrome uses result directly; set_O_sparse() still required

Public Functions

decoder(cudaq::qec::sparse_binary_matrix H)

Constructor.

Parameters:

H – Decoder’s parity check matrix. Taken by value so rvalue arguments are moved into the base member.

virtual decoder_result decode(const std::vector<float_t> &syndrome) = 0

Decode a single syndrome.

Parameters:

syndrome – A vector of syndrome measurements where the floating point value is the probability that the syndrome measurement is a |1>. The length of the syndrome vector should be equal to syndrome_size.

Returns:

Vector of length block_size with soft probabilities of errors in each index.

virtual decoder_result decode(const cudaqx::tensor<uint8_t> &syndrome)

Decode a single syndrome.

Parameters:

syndrome – An order-1 tensor of syndrome measurements where a 1 bit represents that the syndrome measurement is a |1>. The length of the syndrome vector should be equal to syndrome_size.

Returns:

Vector of length block_size of errors in each index.

virtual std::future<decoder_result> decode_async(const std::vector<float_t> &syndrome)

Decode a single syndrome.

Parameters:

syndrome – A vector of syndrome measurements where the floating point value is the probability that the syndrome measurement is a |1>.

Returns:

std::future of a vector of length block_size with soft probabilities of errors in each index.

virtual std::vector<decoder_result> decode_batch(const std::vector<std::vector<float_t>> &syndrome)

Decode multiple independent syndromes (may be done in serial or parallel depending on the specific implementation)

Parameters:

syndrome – A vector of N syndrome measurements where the floating point value is the probability that the syndrome measurement is a |1>.

Returns:

2-D vector of size N x block_size with soft probabilities of errors in each index.

inline decode_result_type get_result_type() const

Returns the type of result produced by decode(). Defaults to decode_to_errs. Decoders that project to observables internally (i.e., constructed with an “O” param) should call set_result_type(decode_to_obs) in their constructor.

uint32_t get_num_msyn_per_decode() const

Get the number of measurement syndromes per decode call. This depends on D_sparse, so you must have called set_D_sparse() first.

inline int get_cuda_device_id() const

The CUDA device this decoder was pinned to at construction via the “cuda_device_id” parameter, or -1 when no pin was requested. Construction pins the constructing thread persistently (the thread that creates a decoder is the thread expected to drive its decode calls).

void set_O_sparse(const std::vector<std::vector<uint32_t>> &O_sparse)

Set the observable matrix.

void set_O_sparse(const std::vector<int64_t> &O_sparse)

Set the observable matrix, using a single long vector with -1 as row terminators.

void set_D_sparse(const std::vector<std::vector<uint32_t>> &D_sparse)

Set the D_sparse matrix.

void set_D_sparse(const std::vector<int64_t> &D_sparse)

Set the D_sparse matrix, using a single long vector with -1 as row terminators.

void set_decoder_id(uint32_t decoder_id)

Set the decoder id.

uint32_t get_decoder_id() const

Get the decoder id.

virtual bool enqueue_syndrome(const uint8_t *syndrome, std::size_t syndrome_length)

Enqueue a syndrome for decoding (pointer version)

Returns:

True if enough syndromes have been enqueued to trigger a decode.

virtual bool enqueue_syndrome(const std::vector<uint8_t> &syndrome)

Enqueue a syndrome for decoding (vector version)

Returns:

True if enough syndromes have been enqueued to trigger a decode.

virtual const uint8_t *get_obs_corrections() const

Get the current observable corrections.

std::size_t get_num_observables() const

Get the number of observables.

virtual void clear_corrections()

Clear any stored corrections.

virtual void reset_decoder()

Reset the decoder, clearing all per-shot memory and corrections.

inline virtual bool supports_graph_dispatch() const

Returns true if this decoder supports graph-based realtime dispatch via capture_decode_graph().

inline virtual void *capture_decode_graph(int reserved_sms = 0)

Capture a CUDA graph for realtime dispatch.

Returns a pointer to a cudaq::qec::realtime::graph_resources struct (caller must include realtime/graph_resources.h to interpret it). Returns nullptr if graph dispatch is not supported. The decoder retains ownership of the returned pointer.

inline virtual void release_decode_graph(void *graph_resources)

Release graph resources previously returned by capture_decode_graph().

virtual ~decoder() = default

Destructor.

virtual std::string get_version() const

Get the version of the decoder. Subclasses that are not part of the standard GitHub repo should override this to provide a more tailored version string.

Returns:

A string containing the version of the decoder

Public Static Functions

static std::unique_ptr<decoder> get(const std::string &name, const decoder_init &init, const cudaqx::heterogeneous_map &param_map = cudaqx::heterogeneous_map())

Construct a registered decoder by name.

Parameters:
  • name – The registered decoder name.

  • init – A parity-check matrix or raw Stim DEM string.

  • param_map – Optional decoder-specific parameters.

struct decoder_result

Decoder results.

Public Members

bool converged = false

Whether or not the decoder converged.

std::vector<float_t> result

Vector of length block_size with soft probabilities of errors in each index.

std::optional<cudaqx::heterogeneous_map> opt_results

Optional additional results from the decoder stored in a heterogeneous map. For equality comparison, this field is treated as a boolean flag - two decoder_results are considered equal only if both have empty opt_results (either std::nullopt or an empty map). If either result has non-empty opt_results, they are considered not equal.

Built-in Decoders

NVIDIA QLDPC Decoder

class 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 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 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.

PyMatching Decoder

class 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 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 (__qpu__ functions) to interact with real-time decoders.

void cudaq::qec::decoding::enqueue_syndromes(std::uint64_t decoder_id, const std::vector<cudaq::measure_result> &syndromes, std::uint64_t tag = 0)

Enqueue syndromes for decoding.

Parameters:
  • decoder_id – The ID of the decoder to use.

  • syndromes – The syndromes to enqueue.

  • tag – The tag to use for the syndrome (currently useful for logging only)

std::vector<bool> cudaq::qec::decoding::get_corrections(std::uint64_t decoder_id, std::uint64_t return_size, bool reset = false)

Get the corrections for a given decoder.

Parameters:
  • decoder_id – The ID of the decoder to use.

  • return_size – The number of bits to return (in bits). This is expected to match the number of observables in the decoder.

  • reset – Whether to reset the decoder corrections after retrieving them.

Returns:

The corrections (detected bit flips) for the given decoder, based on all of the decoded syndromes since the last time any corrections were reset.

void cudaq::qec::decoding::reset_decoder(std::uint64_t decoder_id)

Reset the decoder. This clears any queued syndromes and resets any corrections back to 0.

Parameters:

decoder_id – The ID of the decoder to reset.

Configuration API

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

struct graph_resources

Resources returned by decoder::capture_decode_graph().

The decoder plugin captures a CUDA graph internally and populates this struct. The host dispatcher (libcudaq-realtime-host-dispatch) uses graph_exec / stream to launch the graph, and writes per-slot I/O addresses into h_mailbox before each launch. function_id is used by the host dispatcher to route RPC requests to the correct graph worker.

Public Members

void **d_mailbox = nullptr

device-mapped pinned pointer

void **h_mailbox = nullptr

host pointer to same pinned memory

enum class cudaq::qec::decoding::config::DecoderTransport

Transport type for a decoder session. cpu_roce: CpuRoceTransceiver / SoftRoCE (dev, CI, no GPU required) gpu_roce: GpuRoceTransceiver / DOCA (production, real ConnectX)

Values:

enumerator cpu_roce
enumerator gpu_roce
struct decoder_config

Configuration structure for decoder options.

Public Functions

cudaqx::heterogeneous_map decoder_custom_args_to_heterogeneous_map() const

Return the parameter map a decoder’s constructor should receive: the stored custom args with schema-declared defaults materialized (see materialize_default_args in cudaq/qec/decoder_config_schema.h) when a schema is registered for type, so programmatically built configs get the same defaulting the YAML parse path applies.

void validate_custom_args() const

Validate decoder_custom_args against the parameter schema registered for type: unknown keys, missing required keys, and the schema’s own validate hook (if any). Throws std::runtime_error on the first violation. YAML parsing applies the same checks automatically; call this to vet a configuration built programmatically before using it.

Public Members

DecoderTransport transport = DecoderTransport::cpu_roce

Transport used to receive syndromes and send corrections for this decoder. Defaults to cpu_roce. Set to gpu_roce for decoders where syndrome bits are DMA’d directly to GPU VRAM (e.g. nv_qldpc_decoder with RelayBP).

std::optional<int> cuda_device_id

CUDA device this decoder is pinned to at construction (see the “cuda_device_id” decoder parameter). Placement knob common to any GPU-accelerated decoder, hence at this level rather than inside the per-decoder custom args. Unset = unpinned.

class multi_decoder_config

Public Functions

void validate_custom_args() const

Validate every decoder’s custom args (see decoder_config::validate_custom_args).

int cudaq::qec::decoding::config::configure_decoders(multi_decoder_config &config)

Configure the decoders (multi_decoder_config variant). This function configures both local decoders, and if running on remote target hardware, will submit the configuration to the remote target for further processing.

Parameters:

config – The configuration to use.

Returns:

0 on success, non-zero on failure.

int cudaq::qec::decoding::config::configure_decoders_from_file(const char *config_file)

Configure the decoders from a file. This function configures both local decoders, and if running on remote target hardware, will submit the configuration to the remote target for further processing.

Parameters:

config_file – The file to read the configuration from.

Returns:

0 on success, non-zero on failure.

int cudaq::qec::decoding::config::configure_decoders_from_str(const char *config_str)

Configure the decoders from a string. This function configures both local decoders, and if running on remote target hardware, will submit the configuration to the remote target for further processing.

Parameters:

config_str – The string to read the configuration from.

Returns:

0 on success, non-zero on failure.

void cudaq::qec::decoding::config::finalize_decoders()

Finalize the decoders. This function finalizes local decoders.

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() for converting a dense PCM to a sparse PCM.

  • cudaq::qec::pcm_from_sparse_vec() for converting a sparse PCM to a dense PCM.

  • cudaq::qec::d_sparse() for converting an M2DSparseMatrix (obtained from a cudaq::qec::decoder_inputs component) into the -1-terminated sparse vector a decoder config expects for D_sparse.

    Usage in real-time decoding:

    auto ctx = cudaq::qec::decoder_context_from_memory_circuit(
        code, statePrep, numRounds, noise);
    auto inputs = ctx.z_component(); // or x_component() / full_component()
    config.H_sparse = cudaq::qec::pcm_to_sparse_vec(inputs.dem.detector_error_matrix);
    config.O_sparse = cudaq::qec::pcm_to_sparse_vec(inputs.dem.observables_flips_matrix);
    config.D_sparse = cudaq::qec::d_sparse(inputs.m2d);
    

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

Realtime Pipeline API

The realtime pipeline API provides the reusable host-side runtime for low-latency QEC pipelines that combine GPU inference with optional CPU post-processing. The published reference is generated from cudaq/qec/realtime/pipeline.h.

Note

This API is experimental and subject to change.

Configuration

struct core_pinning

CPU core affinity settings for pipeline threads.

Public Members

int dispatcher = -1

Core for the host dispatcher thread. -1 disables pinning.

int consumer = -1

Core for the consumer (completion) thread. -1 disables pinning.

int worker_base = -1

Base core for worker threads. Workers pin to base, base+1, etc. -1 disables pinning.

struct pipeline_stage_config

Configuration for a single pipeline stage.

Public Members

int num_workers = 8

Number of GPU worker threads (max 64).

int num_slots = 32

Number of ring buffer slots.

size_t slot_size = 16384

Size of each ring buffer slot in bytes.

core_pinning cores

CPU core affinity settings.

void *external_ringbuffer = nullptr

When non-null, the pipeline uses this caller-owned ring buffer (cudaq_ringbuffer_t*) instead of allocating its own. The caller is responsible for lifetime. ring_buffer_injector is unavailable in this mode (the FPGA/emulator owns the producer side).

GPU Stage

struct gpu_worker_resources

Per-worker GPU resources returned by the gpu_stage_factory.

Each worker owns a captured CUDA graph, a dedicated stream, and optional pre/post launch callbacks for DMA staging or result extraction.

Public Members

cudaGraphExec_t graph_exec = nullptr

Instantiated CUDA graph for this worker.

cudaStream_t stream = nullptr

Dedicated CUDA stream for graph launches.

void (*pre_launch_fn)(void *user_data, void *slot_dev, cudaStream_t stream) = nullptr

Optional callback invoked before graph launch (e.g. DMA copy).

void *pre_launch_data = nullptr

Opaque user data passed to pre_launch_fn.

void (*post_launch_fn)(void *user_data, void *slot_dev, cudaStream_t stream) = nullptr

Optional callback invoked after graph launch.

void *post_launch_data = nullptr

Opaque user data passed to post_launch_fn.

uint32_t function_id = 0

RPC function ID that this worker handles.

void *user_context = nullptr

Opaque user context passed to cpu_stage_callback.

using cudaq::qec::realtime::experimental::gpu_stage_factory = std::function<gpu_worker_resources(int worker_id)>

Factory called once per worker during start().

Param worker_id:

Zero-based worker index assigned by the pipeline.

Return:

GPU resources for the given worker. Any handles, callbacks, and user data returned here must remain valid until the pipeline stops.

CPU Stage

struct cpu_stage_context

Context passed to the CPU stage callback for each completed GPU workload.

The callback reads gpu_output, performs post-processing (e.g. MWPM decoding), and writes the result into response_buffer.

Public Members

int worker_id

Index of the worker thread invoking this callback.

int origin_slot

Ring buffer slot that originated this request.

const void *gpu_output

Pointer to GPU inference output (nullptr in poll mode).

size_t gpu_output_size

Size of GPU output in bytes.

void *response_buffer

Destination buffer for the RPC response.

size_t max_response_size

Maximum number of bytes that can be written to response_buffer.

void *user_context

Opaque user context from gpu_worker_resources::user_context.

using cudaq::qec::realtime::experimental::cpu_stage_callback = std::function<size_t(const cpu_stage_context &ctx)>

CPU stage callback type.

Param ctx:

Poll-mode view of the current worker state and response buffer.

Return:

Number of bytes written into ctx.response_buffer. Return 0 if no GPU result is ready yet (poll again). Return DEFERRED_COMPLETION to release the worker immediately while deferring slot completion to a later complete_deferred() call.

static constexpr size_t cudaq::qec::realtime::experimental::DEFERRED_COMPLETION = SIZE_MAX

Sentinel return value from cpu_stage_callback: release the worker (idle_mask) but do NOT signal slot completion (tx_flags). The caller is responsible for calling realtime_pipeline::complete_deferred(slot) once the deferred work (e.g. a separate decode thread) finishes.

Note

External rings use the overload that also takes the request ID.

Completion

struct completion

Metadata for a completed (or errored) pipeline request.

Public Members

uint64_t request_id

Original request ID from the RPC header.

int slot

Ring buffer slot that held this request.

bool success

True if the request completed without CUDA errors.

int cuda_error

CUDA error code (0 on success).

using cudaq::qec::realtime::experimental::completion_callback = std::function<void(const completion &c)>

Callback invoked by the consumer thread for each completed request.

Note

For an external ring, the callback runs synchronously on the CPU worker or deferred-completion thread instead.

Param c:

Metadata for the completed or errored request.

Ring Buffer Injector

class ring_buffer_injector

Writes RPC-framed requests into the pipeline’s ring buffer, simulating FPGA DMA deposits.

Created via realtime_pipeline::create_injector(). The parent realtime_pipeline must outlive the injector. Not available when the pipeline is configured with an external ring buffer.

Public Functions

~ring_buffer_injector()

Destroy the injector state.

ring_buffer_injector(ring_buffer_injector&&) noexcept

Move-construct an injector.

ring_buffer_injector &operator=(ring_buffer_injector&&) noexcept

Move-assign an injector.

bool try_submit(uint32_t function_id, const void *payload, size_t payload_size, uint64_t request_id)

Try to submit a request without blocking.

Parameters:
  • function_id – RPC function identifier.

  • payload – Pointer to the payload data.

  • payload_size – Size of the payload in bytes.

  • request_id – Caller-assigned request identifier.

Returns:

True if accepted, false if all slots are busy (backpressure).

void submit(uint32_t function_id, const void *payload, size_t payload_size, uint64_t request_id)

Submit a request, spinning until a slot becomes available.

Parameters:
  • function_id – RPC function identifier.

  • payload – Pointer to the payload data.

  • payload_size – Size of the payload in bytes.

  • request_id – Caller-assigned request identifier.

uint64_t backpressure_stalls() const

Return the cumulative number of backpressure stalls.

Returns:

Number of times submit() had to spin-wait for a free slot.

Pipeline

class realtime_pipeline

Orchestrates GPU inference and CPU post-processing for low-latency realtime QEC decoding.

The pipeline manages a ring buffer, a host dispatcher thread, per-worker GPU streams with captured CUDA graphs, optional CPU worker threads for post-processing (e.g. PyMatching), and a consumer thread for completion signaling. It supports both an internal ring buffer (for software testing via ring_buffer_injector) and an external ring buffer (for FPGA RDMA).

Note

External transports own TX flag consumption, so the consumer thread is only created for an internal ring.

Public Functions

explicit realtime_pipeline(const pipeline_stage_config &config)

Construct a pipeline and allocate ring buffer resources.

Note

Construction allocates the backing ring buffer or binds the caller-provided external ring so ringbuffer_bases can be queried before start.

Parameters:

config – Stage configuration (slots, slot size, workers, etc.).

~realtime_pipeline()

Stop the pipeline if needed and release owned resources.

void set_gpu_stage(gpu_stage_factory factory)

Register the GPU stage factory. Must be called before start().

Parameters:

factory – Callback that returns gpu_worker_resources per worker.

void set_cpu_stage(cpu_stage_callback callback)

Register the CPU worker callback. Must be called before start().

Parameters:

callback – Function invoked by each worker thread to poll for and process completed GPU workloads. If not set, the pipeline operates in GPU-only mode with completion signaled via cudaLaunchHostFunc.

void set_completion_handler(completion_callback handler)

Register the completion callback. Must be called before start().

Note

For an external ring, the handler runs on the CPU worker or deferred-completion thread instead.

Parameters:

handler – Function invoked by the consumer thread for each completed or errored request.

void start()

Allocate resources, build dispatcher config, and spawn all threads.

Throws:
  • std::logic_error – If the GPU stage factory was not registered.

  • std::logic_error – If GPU-only mode is requested with an external ring buffer.

void stop()

Signal shutdown, join all threads, free resources.

Note

Safe to call multiple times. Subsequent calls are no-ops once the pipeline has fully stopped.

ring_buffer_injector create_injector()

Create a software injector for testing without FPGA hardware.

Throws:

std::logic_error – if the pipeline uses an external ring buffer.

Returns:

A ring_buffer_injector bound to this pipeline’s ring buffer.

Stats stats() const

Thread-safe, lock-free stats snapshot.

Returns:

Current pipeline statistics.

void complete_deferred(int slot)

Signal that deferred processing for a slot is complete.

Call from any thread after the cpu_stage callback returned DEFERRED_COMPLETION and the deferred work has finished writing the response into the slot’s ring buffer area.

Note

This overload is for an internal ring buffer.

Parameters:

slot – Ring buffer slot index to complete.

Throws:
  • std::logic_error – If the pipeline uses an external ring buffer.

  • std::out_of_range – If slot is not a valid ring slot.

void complete_deferred(int slot, uint64_t request_id)

Signal external-ring deferred processing for a slot is complete.

Publishes the caller-written TX response, accounts the completion, and invokes the registered completion callback synchronously.

Parameters:
  • slot – Ring buffer slot index to complete.

  • request_id – Original RPC request identifier.

Throws:
  • std::logic_error – If the pipeline uses an internal ring buffer.

  • std::out_of_range – If slot is not a valid ring slot.

ring_buffer_bases ringbuffer_bases() const

Return the host and device base addresses of the RX data ring.

Note

In external-ring mode these pointers are the caller-provided ring addresses. In internal mode they refer to the owned mapped ring buffer.

Returns:

Struct containing both base pointers.

struct ring_buffer_bases

Host and device base addresses of the RX data ring.

Public Members

uint8_t *rx_data_host

Host-mapped base pointer for the RX data ring.

uint8_t *rx_data_dev

Device-mapped base pointer for the RX data ring.

struct Stats

Pipeline throughput and backpressure statistics.

Public Members

uint64_t submitted

Total requests submitted to the ring buffer.

Note

External transports own submission, so this remains zero when an external ring buffer is used.

uint64_t completed

Total requests that completed (success or error).

uint64_t dispatched

Total packets dispatched by the host dispatcher.

uint64_t backpressure_stalls

Cumulative producer backpressure stalls.

Parity Check Matrix Utilities

The utilities below create, convert, inspect, and transform parity-check matrices (PCMs). CUDA-Q QEC supports dense matrices as cudaqx::tensor<std::uint8_t> and sparse matrices as sparse_binary_matrix. Decoder entry points accept either representation and store the PCM internally as a sparse matrix.

sparse_binary_matrix stores a binary matrix in compressed sparse column (CSC) or compressed sparse row (CSR) layout without storing values for its nonzero entries. Input indices are preserved as supplied. Use cudaq::qec::sparse_binary_matrix::canonicalize() when duplicate indices should be combined over GF(2) and each compressed row or column should be sorted. Canonicalization preserves the matrix layout. The matrix uses std::uint32_t indices, so each dimension and the number of stored entries must fit in that type.

Sparse utility overloads operate without materializing the full input as a dense tensor. Use cudaq::qec::generate_random_pcm_sparse() when a generated PCM would be impractical to allocate densely. The dense generator remains available and rejects dimensions whose products overflow std::size_t.

enum class cudaq::qec::sparse_binary_matrix_layout

Storage layout for the sparse PCM: Compressed Sparse Column (CSC) or Compressed Sparse Row (CSR). All non-zero entries are assumed to be 1; values are not stored.

Values:

enumerator csc
enumerator csr
class sparse_binary_matrix

Sparse parity-check matrix in either CSC or CSR form.

Input index lists are stored as given: not required to be sorted or GF(2)-unique. Consumers that require cuSPARSE-style compressed groups can call validate_sorted_unique_indices; consumers that need GF(2)-collapsed per-group indices can call canonicalize on entry.

index_type is uint32_t, so each dimension and nnz must fit in ~4×10^9.

Public Functions

sparse_binary_matrix(const cudaqx::tensor<std::uint8_t> &dense, sparse_binary_matrix_layout layout = sparse_binary_matrix_layout::csc)

Construct from a rank-2 dense PCM (any non-zero treated as 1). Intentionally not explicit so call sites that take sparse_binary_matrix accept a dense cudaqx::tensor unchanged.

inline const std::vector<index_type> &ptr() const

For CSC: ptr has length num_cols+1; for CSR: ptr has length num_rows+1.

inline const std::vector<index_type> &indices() const

For CSC: row indices; for CSR: column indices.

void validate_sorted_unique_indices(const char *context = "sparse_binary_matrix") const

Throw if each compressed column/row does not have strictly increasing indices. This rejects duplicate entries in the stored layout.

sparse_binary_matrix canonicalize() const

Return a GF(2)-canonical copy: each compressed column/row has its indices sorted ascending, and duplicate indices are XOR-merged. An index that appears k times is kept iff k is odd. The output has the same layout as the input and is idempotent under further canonicalize calls.

Use this when a PCM source, such as a DEM decomposition, may emit duplicate indices within a compressed group and the caller wants GF(2) duplicate-collapse semantics before passing the matrix to consumers that require at most one entry per row/column.

sparse_binary_matrix to_csc() const

Return a copy of this matrix in CSC layout. No-op if already CSC.

sparse_binary_matrix to_csr() const

Return a copy of this matrix in CSR layout. No-op if already CSR.

cudaqx::tensor<std::uint8_t> to_dense() const

Convert to a dense PCM tensor (rows x columns). Non-zero entries are set to 1.

std::vector<std::vector<index_type>> to_nested_csc() const

Nested CSC: outer vector has size num_cols; inner vector for column j lists row indices of non-zeros in that column.

std::vector<std::vector<index_type>> to_nested_csr() const

Nested CSR: outer vector has size num_rows; inner vector for row i lists column indices of non-zeros in that row.

Public Static Functions

static sparse_binary_matrix from_csc(index_type num_rows, index_type num_cols, std::vector<index_type> col_ptrs, std::vector<index_type> row_indices)

Construct a sparse PCM in CSC form.

Parameters:
  • num_rows – Number of rows.

  • num_cols – Number of columns.

  • col_ptrs – Column pointer array (length num_cols + 1); column j has indices in row_indices[col_ptrs[j] .. col_ptrs[j+1]-1].

  • row_indices – Row indices of non-zeros (length nnz).

static sparse_binary_matrix from_csr(index_type num_rows, index_type num_cols, std::vector<index_type> row_ptrs, std::vector<index_type> col_indices)

Construct a sparse PCM in CSR form.

Parameters:
  • num_rows – Number of rows.

  • num_cols – Number of columns.

  • row_ptrs – Row pointer array (length num_rows + 1); row i has indices in col_indices[row_ptrs[i] .. row_ptrs[i+1]-1].

  • col_indices – Column indices of non-zeros (length nnz).

static sparse_binary_matrix from_nested_csc(index_type num_rows, index_type num_cols, const std::vector<std::vector<index_type>> &nested)

Construct from nested CSC: nested[j] is the list of row indices for column j; nested.size() must equal num_cols.

static sparse_binary_matrix from_nested_csr(index_type num_rows, index_type num_cols, const std::vector<std::vector<index_type>> &nested)

Construct from nested CSR: nested[i] is the list of column indices for row i; nested.size() must equal num_rows.

cudaqx::tensor<uint8_t> cudaq::qec::to_parity_matrix(const std::vector<cudaq::spin_op_term> &stabilizers, stabilizer_type type = stabilizer_type::XZ)

Convert stabilizers to a parity check matrix

Returns:

Tensor representing the parity check matrix

cudaqx::tensor<uint8_t> cudaq::qec::to_parity_matrix(const std::vector<std::string> &words, stabilizer_type type = stabilizer_type::XZ)
std::vector<std::vector<std::uint32_t>> cudaq::qec::dense_to_sparse(const cudaqx::tensor<uint8_t> &pcm)

Return a sparse representation of the PCM.

Parameters:

pcm – The PCM to convert to a sparse representation.

Returns:

A vector of vectors that sparsely represents the PCM. The size of the outer vector is the number of columns in the PCM, and the i-th element contains an inner vector of the row indices of the non-zero elements in the i-th column of the PCM.

cudaqx::tensor<uint8_t> cudaq::qec::generate_random_pcm(std::size_t n_rounds, std::size_t n_errs_per_round, std::size_t n_syndromes_per_round, int weight, std::mt19937_64 &&rng)

Generate a random PCM with the given parameters.

The PCM has shape (n_rounds * n_syndromes_per_round) × (n_rounds * n_errs_per_round).

Parameters:
  • n_rounds – The number of rounds in the PCM.

  • n_errs_per_round – The number of errors per round in the PCM.

  • n_syndromes_per_round – The number of syndromes per round in the PCM.

  • weight – The column weight of the PCM.

  • rng – The random number generator to use (e.g. std::mt19937_64(your_seed))

Returns:

A random PCM with the given parameters.

sparse_binary_matrix cudaq::qec::generate_random_pcm_sparse(std::size_t n_rounds, std::size_t n_errs_per_round, std::size_t n_syndromes_per_round, int weight, std::mt19937_64 &&rng)

Same distribution as generate_random_pcm, but constructs a CSC sparse_binary_matrix directly without allocating a dense rank-2 tensor. Intended for large PCMs whose dense form would be impractical or impossible to allocate.

std::vector<std::int64_t> cudaq::qec::generate_timelike_sparse_detector_matrix(std::uint32_t num_syndromes_per_round, std::uint32_t num_rounds, bool include_first_round = false)

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

Parameters:
  • num_syndromes_per_round – The number of syndromes per round.

  • num_rounds – The number of rounds.

  • include_first_round – Whether to include the first round in the detector matrix.

Returns:

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

std::vector<std::int64_t> cudaq::qec::generate_timelike_sparse_detector_matrix(std::uint32_t num_syndromes_per_round, std::uint32_t num_rounds, std::vector<std::int64_t> first_round_matrix)

Generate a sparse detector matrix for a given number of syndromes per round and number of rounds. Timelike here means that each round of syndrome bits are xor’d against the preceding round. The first round is supplied by the user, to allow for a mixture of detectors and non-detectors.

Parameters:
  • num_syndromes_per_round – The number of syndromes per round.

  • num_rounds – The number of rounds.

  • first_round_matrix – User specified detector matrix for the first round.

Returns:

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

std::tuple<cudaqx::tensor<uint8_t>, std::uint32_t, std::uint32_t> cudaq::qec::get_pcm_for_rounds(const cudaqx::tensor<uint8_t> &pcm, std::uint32_t num_syndromes_per_round, std::uint32_t start_round, std::uint32_t end_round, bool straddle_start_round = false, bool straddle_end_round = false, std::uint32_t num_boundary_syndromes = 0)

Get a sub-PCM for a range of rounds. It is recommended (but not required) that you call sort_pcm_columns() before calling this function.

Parameters:
  • pcm – The PCM to get a sub-PCM for.

  • num_syndromes_per_round – The number of syndromes per round.

  • start_round – The start round (0-based).

  • end_round – The end round (0-based).

  • straddle_start_round – Whether to include columns that straddle the start_round (defaults to false)

  • straddle_end_round – Whether to include columns that straddle the end_round (defaults to false)

  • num_boundary_syndromes – Width of the narrower first/last boundary rounds for a non-uniform [B | K*S | B] detector layout (0 == uniform).

Returns:

A tuple with the new PCM with the columns in the range [start_round, end_round], the first column included, and the last column included.

std::tuple<cudaqx::tensor<uint8_t>, std::uint32_t, std::uint32_t> cudaq::qec::get_pcm_for_rounds(const sparse_binary_matrix &pcm, std::uint32_t num_syndromes_per_round, std::uint32_t start_round, std::uint32_t end_round, bool straddle_start_round = false, bool straddle_end_round = false, bool pcm_is_canonical = false, std::uint32_t num_boundary_syndromes = 0)

Same semantics as the overload taking a dense tensor pcm, but reads from pcm as sparse_binary_matrix so the full dense PCM is not required (only the returned sub-matrix is dense). Parameter meanings match the dense overload.

Warning

When pcm_is_canonical is true the precondition is not checked. select_pcm_columns_for_round_range reads .front() / .back() of each CSC column to derive first/last round; those are only the true min/max row if the column list is sorted-unique. Passing a non-canonical PCM with this flag (e.g. a raw DEM decomposition, a hand-built sparse matrix with duplicate or unsorted indices, or a canonical CSR that the caller forgot to re-canonicalize after conversion) silently produces wrong round assignments. If unsure, leave the flag false.

Parameters:
  • pcm – The PCM to get a sub-PCM for.

  • num_syndromes_per_round – The number of syndromes per round.

  • start_round – The start round (0-based).

  • end_round – The end round (0-based).

  • straddle_start_round – Whether to include columns that straddle the start_round (defaults to false)

  • straddle_end_round – Whether to include columns that straddle the end_round (defaults to false)

  • pcm_is_canonical – If true, the caller asserts pcm has sorted-unique per-group indices (i.e. is the output of sparse_binary_matrix::canonicalize or was constructed canonically). In that case the per-call canonicalization step is skipped — useful for callers like sliding_window that canonicalize once at construction and then call this function in a per-window loop on the same matrix. Default false preserves the original “canonicalize on entry” behavior.

  • num_boundary_syndromes – Width of the narrower first/last boundary rounds for a non-uniform [B | K*S | B] detector layout (0 == uniform).

Returns:

A tuple with the new PCM with the columns in the range [start_round, end_round], the first column included, and the last column included.

std::vector<std::uint32_t> cudaq::qec::get_sorted_pcm_column_indices(const std::vector<std::vector<std::uint32_t>> &row_indices, std::uint32_t num_syndromes_per_round = 0)

Return a vector of column indices that would sort the PCM columns in topological order.

This function tries to make a matrix that is close to a block diagonal matrix from its input. Columns are first sorted by the index of the first non-zero entry in the column, and if those match, then they are sorted by the index of the last non-zero entry in the column. This ping pong continues for the indices of the second non-zero element and the second-to-last non-zero element, and so forth.

Parameters:
  • row_indices – For each column, a vector of row indices that have a non-zero value in that column.

  • num_syndromes_per_round – The number of syndromes per round. (Defaults to 0, which means that no secondary per-round sorting will occur.)

std::vector<std::uint32_t> cudaq::qec::get_sorted_pcm_column_indices(const std::vector<std::vector<std::uint32_t>> &row_indices, std::uint32_t num_syndromes_per_round, std::uint32_t num_boundary_syndromes)

Boundary-aware overload of the above: the first and last num_boundary_syndromes rows form the boundary rounds and each interior round spans num_syndromes_per_round rows. throws std::invalid_argument if num_syndromes_per_round is zero or num_boundary_syndromes > num_syndromes_per_round.

std::vector<std::uint32_t> cudaq::qec::get_sorted_pcm_column_indices(const cudaqx::tensor<uint8_t> &pcm, std::uint32_t num_syndromes_per_round = 0)

Return a vector of column indices that would sort the PCM columns in topological order.

Parameters:
  • pcm – The PCM to sort.

  • num_syndromes_per_round – The number of syndromes per round. (Defaults to 0, which means that no secondary per-round sorting will occur.)

std::pair<cudaqx::tensor<uint8_t>, std::vector<std::uint32_t>> cudaq::qec::pcm_extend_to_n_rounds(const cudaqx::tensor<uint8_t> &pcm, std::size_t num_syndromes_per_round, std::uint32_t n_rounds)

Extend a PCM to the given number of rounds.

Parameters:
  • pcm – The PCM to extend.

  • num_syndromes_per_round – The number of syndromes per round.

  • n_rounds – The number of rounds to extend the PCM to.

Returns:

A pair of the new PCM and the list of column indices from the original PCM that were used to form the new PCM.

cudaqx::tensor<uint8_t> cudaq::qec::pcm_from_sparse_string(const std::string &sparse_str, std::size_t num_rows, std::size_t num_cols)

Return a PCM from a sparse representation.

Parameters:
  • sparse_str – The sparse representation of the PCM.

  • num_rows – The number of rows in the PCM.

  • num_cols – The number of columns in the PCM.

Returns:

A PCM tensor.

cudaqx::tensor<uint8_t> cudaq::qec::pcm_from_sparse_vec(const std::vector<std::int64_t> &sparse_vec, std::size_t num_rows, std::size_t num_cols)

Return a PCM from a sparse representation.

Parameters:
  • sparse_vec – The sparse representation of the PCM, where -1 separates rows.

  • num_rows – The number of rows in the PCM.

  • num_cols – The number of columns in the PCM.

Returns:

A PCM tensor.

bool cudaq::qec::pcm_is_sorted(const cudaqx::tensor<uint8_t> &pcm, std::uint32_t num_syndromes_per_round = 0)

Check if a PCM is sorted.

Parameters:
  • pcm – The PCM to check.

  • num_syndromes_per_round – The number of syndromes per round.

Returns:

True if the PCM is sorted, false otherwise.

bool cudaq::qec::pcm_is_sorted(const std::vector<std::vector<std::uint32_t>> &sparse_pcm, std::uint32_t num_syndromes_per_round, std::uint32_t num_boundary_syndromes)

Boundary-aware overload of pcm_is_sorted: the first and last num_boundary_syndromes rows form the boundary rounds and each interior round spans num_syndromes_per_round rows (a [B | K*S | B] layout).

Parameters:
  • sparse_pcm – The sparse PCM to check (in the same format as dense_to_sparse())

  • num_syndromes_per_round – The interior-round width.

  • num_boundary_syndromes – The width of the first/last boundary rounds.

Returns:

True if the PCM is sorted for this boundary layout, false otherwise.

std::string cudaq::qec::pcm_to_sparse_string(const cudaqx::tensor<uint8_t> &pcm)

Return a sparse representation of the PCM as a string.

Parameters:

pcm – The PCM to convert to a sparse representation.

Returns:

A string that represents the PCM in a sparse format.

std::string cudaq::qec::pcm_to_sparse_string(const sparse_binary_matrix &pcm)

Sparse overload of pcm_to_sparse_string. Duplicate stored entries are canonicalized with GF(2) cancellation before serialization.

std::vector<std::int64_t> cudaq::qec::pcm_to_sparse_vec(const cudaqx::tensor<uint8_t> &pcm)

Return a sparse representation of the PCM.

Parameters:

pcm – The PCM to convert to a sparse representation.

Returns:

A vector of integers that represents the PCM in a sparse format, where -1 separates rows.

std::vector<std::int64_t> cudaq::qec::pcm_to_sparse_vec(const sparse_binary_matrix &pcm)

Sparse overload of pcm_to_sparse_vec. Duplicate stored entries are canonicalized with GF(2) cancellation before serialization.

cudaqx::tensor<uint8_t> cudaq::qec::reorder_pcm_columns(const cudaqx::tensor<uint8_t> &pcm, const std::vector<std::uint32_t> &column_order, uint32_t row_begin = 0, uint32_t row_end = std::numeric_limits<uint32_t>::max())

Reorder the columns of a PCM according to the given column order. Note: this may return a subset of the columns in the original PCM if the column_order does not contain all of the columns in the original PCM.

Parameters:
  • pcm – The PCM to reorder.

  • column_order – The column order to use for reordering.

  • row_begin – The first row to include in the reordering. Leave at the default value to include all rows.

  • row_end – The last row to include in the reordering. Leave at the default value to include all rows.

Returns:

A new PCM with the columns reordered according to the given column order.

sparse_binary_matrix cudaq::qec::reorder_pcm_columns(const sparse_binary_matrix &pcm, const std::vector<std::uint32_t> &column_order, uint32_t row_begin = 0, uint32_t row_end = std::numeric_limits<uint32_t>::max())

Sparse overload of reorder_pcm_columns. Returns CSC.

cudaqx::tensor<uint8_t> cudaq::qec::shuffle_pcm_columns(const cudaqx::tensor<uint8_t> &pcm, std::mt19937_64 &&rng)

Randomly permute the columns of a PCM.

Parameters:
  • pcm – The PCM to permute.

  • rng – The random number generator to use (e.g. std::mt19937_64(your_seed))

Returns:

A new PCM with the columns permuted randomly.

sparse_binary_matrix cudaq::qec::shuffle_pcm_columns(const sparse_binary_matrix &pcm, std::mt19937_64 &&rng)

Sparse overload of shuffle_pcm_columns.

std::pair<cudaqx::tensor<uint8_t>, std::vector<double>> cudaq::qec::simplify_pcm(const cudaqx::tensor<uint8_t> &pcm, const std::vector<double> &weights, std::uint32_t num_syndromes_per_round = 0)

Simplify a PCM by removing duplicate columns and 0-weight columns, and combine the probability weight vectors accordingly.

Parameters:
  • pcm – The PCM to simplify.

  • weights – The probability weight vectors to combine.

  • num_syndromes_per_round – The number of syndromes per round. (Defaults to 0, which means that no secondary per-round sorting will occur.)

Returns:

A new PCM with the columns sorted in topological order, and the probability weight vectors combined accordingly.

cudaqx::tensor<uint8_t> cudaq::qec::sort_pcm_columns(const cudaqx::tensor<uint8_t> &pcm, std::uint32_t num_syndromes_per_round = 0)

Sort the columns of a PCM in topological order.

Parameters:
  • pcm – The PCM to sort.

  • num_syndromes_per_round – The number of syndromes per round. (Defaults to 0, which means that no secondary per-round sorting will occur.)

Returns:

A new PCM with the columns sorted in topological order.

Logger

The QEC logger API currently lives in cudaq::qec::detail and is used by the CUDA_QEC_* macros exposed in cudaq/qec/logger.h.

enum class cudaq::qec::detail::log_level

Severity levels supported by the QEC logger.

Values:

enumerator trace
enumerator debug
enumerator info
enumerator warn
enumerator error
enum class cudaq::qec::detail::forward_drop_policy

Queue backpressure policy for forwarded records.

Values:

enumerator drop_newest
enumerator drop_oldest
struct forwarded_log_record

Log payload forwarded to an optional background callback.

constexpr std::size_t cudaq::qec::detail::realtime_forwarder_default_message_capacity = 512

Default forwarded message payload capacity in bytes.

struct forwarder_config

Runtime configuration for asynchronous forwarding.

Public Members

std::function<void(forwarded_log_record&&)> callback

Callback executed on the forwarder worker thread.

The record is passed as an rvalue: the worker owns a fresh, per-record instance that is discarded after the callback returns, so a callback may move out of it (e.g. std::move(record) to re-queue it) to avoid copying the heap-allocated file_name/message strings. Read-only callbacks taking const ForwardedLogRecord & remain valid.

std::size_t queue_capacity = 1024

Bounded queue capacity (records), clamped to at least 1.

std::size_t message_capacity = realtime_forwarder_default_message_capacity

Max forwarded message bytes copied on producer path.

Values are clamped to [1, realtime_forwarder_max_message_capacity].

forward_drop_policy drop_policy = forward_drop_policy::drop_newest

Overflow policy when the forwarder queue is saturated.

struct forwarder_stats

Runtime counters for asynchronous forwarding behavior.

Public Members

std::uint64_t enqueued_records = 0

Records successfully enqueued to the forwarder queue.

std::uint64_t dropped_records = 0

Records dropped due to queue saturation/overflow policy.

std::uint64_t truncated_records = 0

Records whose message payload was truncated to fit capacity.

std::uint64_t forward_failures = 0

Callback invocations that threw exceptions.

bool cudaq::qec::detail::should_log(const log_level level)

Return true if the given level is currently enabled.

void cudaq::qec::detail::set_forwarder(forwarder_config config)

Install or replace the optional async forwarding callback.

void cudaq::qec::detail::set_forwarder()

Enable forwarding with a default worker callback to stdout/stderr.

void cudaq::qec::detail::clear_forwarder()

Disable forwarding and tear down the forwarding worker.

bool cudaq::qec::detail::is_forwarder_enabled()

Return true when forwarding is enabled.

std::size_t cudaq::qec::detail::get_forwarder_message_capacity()

Return active forwarded-message capacity in bytes.

forwarder_stats cudaq::qec::detail::get_forwarder_stats()

Return a snapshot of forwarding counters.

void cudaq::qec::detail::set_log_level(log_level level)

Override log level at runtime.

log_level cudaq::qec::detail::get_log_level()

Return the current runtime log level.

void cudaq::qec::detail::flush_logs()

Flush logger sinks and pending forwarded records.

void cudaq::qec::detail::trace(const std::string_view msg)

Emit a preformatted trace message.

void cudaq::qec::detail::info(const std::string_view msg)

Emit a preformatted info message.

void cudaq::qec::detail::debug(const std::string_view msg)

Emit a preformatted debug message.

void cudaq::qec::detail::warn(const std::string_view msg)

Emit a preformatted warning message.

void cudaq::qec::detail::error(const std::string_view msg)

Emit a preformatted error message.

std::string cudaq::qec::detail::path_to_file_name(const std::string_view full_file_path)

Extract filename from a path-like string.

Common

using cudaq::qec::float_t = double
enum class cudaq::qec::operation

Enum describing all supported logical operations.

Values:

enumerator x

Logical X gate.

enumerator y

Logical Y gate.

enumerator z

Logical Z gate.

enumerator h

Logical Hadamard gate.

enumerator s

Logical S gate.

enumerator cx

Logical controlled-X gate.

enumerator cy

Logical controlled-Y gate.

enumerator cz

Logical controlled-Z gate.

enumerator stabilizer_round

Stabilizer measurement round.

enumerator prep0

Prepare logical |0⟩ state.

enumerator prep1

Prepare logical |1⟩ state.

enumerator prepp

Prepare logical |+⟩ state.

enumerator prepm

Prepare logical |-⟩ state.

enum class cudaq::qec::stabilizer_type

Values:

enumerator XZ
enumerator X
enumerator Z
std::tuple<cudaqx::tensor<uint8_t>, cudaqx::tensor<uint8_t>> cudaq::qec::sample_code_capacity(const cudaqx::tensor<uint8_t> &H, std::size_t numShots, double error_probability)

Sample syndrome measurements with code capacity noise.

Parameters:
  • H – Parity check matrix of a QEC code

  • numShots – Number of measurement shots

  • error_probability – Probability of bit flip on data

Returns:

Tuple containing syndrome measurements and data qubit measurements

std::tuple<cudaqx::tensor<uint8_t>, cudaqx::tensor<uint8_t>> cudaq::qec::sample_code_capacity(const cudaqx::tensor<uint8_t> &H, std::size_t numShots, double error_probability, unsigned seed)

Sample syndrome measurements with code capacity noise.

Parameters:
  • H – Parity check matrix of a QEC code

  • numShots – Number of measurement shots

  • error_probability – Probability of bit flip on data

  • seed – RNG seed for reproducible experiments

Returns:

Tuple containing syndrome measurements and data qubit measurements

std::tuple<cudaqx::tensor<uint8_t>, cudaqx::tensor<uint8_t>> cudaq::qec::sample_code_capacity(const code &code, std::size_t numShots, double error_probability)

Sample syndrome measurements with code capacity noise.

Parameters:
  • code – QEC Code to sample

  • numShots – Number of measurement shots

  • error_probability – Probability of bit flip on data

Returns:

Tuple containing syndrome measurements and data qubit measurements

std::tuple<cudaqx::tensor<uint8_t>, cudaqx::tensor<uint8_t>> cudaq::qec::sample_code_capacity(const code &code, std::size_t numShots, double error_probability, unsigned seed)

Sample syndrome measurements with code capacity noise.

Parameters:
  • code – QEC Code to sample

  • numShots – Number of measurement shots

  • error_probability – Probability of bit flip on data

  • seed – RNG seed for reproducible experiments

Returns:

Tuple containing syndrome measurements and data qubit measurements

std::tuple<cudaqx::tensor<uint8_t>, cudaqx::tensor<uint8_t>> cudaq::qec::sample_memory_circuit(const code &code, std::size_t numShots, std::size_t numRounds = 1)

Sample syndrome measurements starting from |0⟩ state.

Parameters:
  • code – QEC Code to sample

  • numShots – Number of measurement shots

  • numRounds – Number of stabilizer measurement rounds

Returns:

Tuple of (syndromeTensor, dataResults), equivalent to the operation::prep0 overload of sample_memory_circuit.

std::tuple<cudaqx::tensor<uint8_t>, cudaqx::tensor<uint8_t>> cudaq::qec::sample_memory_circuit(const code &code, std::size_t numShots, std::size_t numRounds, cudaq::noise_model &noise)

Sample syndrome measurements from |0⟩ state with noise.

Parameters:
  • code – QEC Code to sample

  • numShots – Number of measurement shots

  • numRounds – Number of stabilizer measurement rounds

  • noise – Noise model to apply

Returns:

Tuple of (syndromeTensor, dataResults), equivalent to the operation::prep0 overload of sample_memory_circuit.

std::tuple<cudaqx::tensor<uint8_t>, cudaqx::tensor<uint8_t>> cudaq::qec::sample_memory_circuit(const code &code, operation statePrep, std::size_t numShots, std::size_t numRounds = 1)

Sample syndrome measurements from the memory circuit.

Parameters:
  • code – QEC Code to sample

  • statePrep – Initial state preparation operation

  • numShots – Number of measurement shots

  • numRounds – Number of stabilizer measurement rounds

Returns:

Tuple of (syndromeTensor, dataResults), equivalent to the operation::prep0 overload of sample_memory_circuit.

std::tuple<cudaqx::tensor<uint8_t>, cudaqx::tensor<uint8_t>> cudaq::qec::sample_memory_circuit(const code &code, operation statePrep, std::size_t numShots, std::size_t numRounds, cudaq::noise_model &noise)

Sample syndrome measurements with circuit-level noise.

Parameters:
  • code – QEC Code to sample

  • statePrep – Initial state preparation operation

  • numShots – Number of measurement shots

  • numRounds – Number of stabilizer measurement rounds

  • noise – Noise model to apply

Returns:

Tuple of (syndromeTensor, dataResults). syndromeTensor has shape (numShots, numDetectors): numFixed boundary detectors (the stabilizer type matching statePrep’s basis, since only that type is deterministic at the circuit’s endpoints), then one detector block per each of the numRounds - 1 inter-round transitions, then numFixed more boundary detectors. dataResults has shape (numShots, numDataQubits).

std::tuple<cudaqx::tensor<uint8_t>, cudaqx::tensor<uint8_t>> cudaq::qec::x_sample_memory_circuit(const code &code, operation statePrep, std::size_t numShots, std::size_t numRounds, cudaq::noise_model &noise)

Sample syndrome measurements with circuit-level noise, keeping only the X stabilizer syndromes.

Parameters:
  • code – QEC Code to sample

  • statePrep – Initial state preparation operation

  • numShots – Number of measurement shots

  • numRounds – Number of stabilizer measurement rounds

  • noise – Noise model to apply

Returns:

Tuple of (syndromeTensor, dataResults), same layout as sample_memory_circuit but restricted to X-stabilizer detector values.

std::tuple<cudaqx::tensor<uint8_t>, cudaqx::tensor<uint8_t>> cudaq::qec::z_sample_memory_circuit(const code &code, operation statePrep, std::size_t numShots, std::size_t numRounds, cudaq::noise_model &noise)

Sample syndrome measurements with circuit-level noise, keeping only the Z stabilizer syndromes.

Parameters:
  • code – QEC Code to sample

  • statePrep – Initial state preparation operation

  • numShots – Number of measurement shots

  • numRounds – Number of stabilizer measurement rounds

  • noise – Noise model to apply

Returns:

Tuple of (syndromeTensor, dataResults), same layout as sample_memory_circuit but restricted to Z-stabilizer detector values.