Skip to content
Open
20 changes: 10 additions & 10 deletions libs/qec/include/cudaq/qec/decoder.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/****************************************************************-*- C++ -*-****
* Copyright (c) 2024 - 2025 NVIDIA Corporation & Affiliates. *
* Copyright (c) 2024 - 2026 NVIDIA Corporation & Affiliates. *
* All rights reserved. *
* *
* This source code and the accompanying materials are made available under *
Expand All @@ -11,6 +11,7 @@
#include "cuda-qx/core/extension_point.h"
#include "cuda-qx/core/heterogeneous_map.h"
#include "cuda-qx/core/tensor.h"
#include "sparse_binary_matrix.h"
#include <future>
#include <optional>
#include <vector>
Expand Down Expand Up @@ -121,7 +122,8 @@ class async_decoder_result {
/// arbitrary constructor parameters that can be unique to each specific
/// decoder.
class decoder
: public cudaqx::extension_point<decoder, const cudaqx::tensor<uint8_t> &,
: public cudaqx::extension_point<decoder,
const cudaq::qec::sparse_binary_matrix &,
const cudaqx::heterogeneous_map &> {
private:
struct rt_impl;
Expand All @@ -134,11 +136,9 @@ class decoder
decoder() = delete;

/// @brief Constructor
/// @param H Decoder's parity check matrix represented as a tensor. The tensor
/// is required be rank 2 and must be of dimensions \p syndrome_size x
/// \p block_size.
/// will use the same \p H.
decoder(const cudaqx::tensor<uint8_t> &H);
/// @param H Decoder's parity check matrix represented as a sparse binary
/// matrix.
decoder(const cudaq::qec::sparse_binary_matrix &H);

/// @brief Decode a single syndrome
/// @param syndrome A vector of syndrome measurements where the floating point
Expand Down Expand Up @@ -174,7 +174,7 @@ class decoder

/// @brief This `get` overload supports default values.
static std::unique_ptr<decoder>
get(const std::string &name, const cudaqx::tensor<uint8_t> &H,
get(const std::string &name, const cudaq::qec::sparse_binary_matrix &H,
const cudaqx::heterogeneous_map &param_map = cudaqx::heterogeneous_map());

std::size_t get_block_size() { return block_size; }
Expand Down Expand Up @@ -248,7 +248,7 @@ class decoder
std::size_t syndrome_size = 0;

/// @brief The decoder's parity check matrix
cudaqx::tensor<uint8_t> H;
sparse_binary_matrix H;

/// @brief The decoder's observable matrix in sparse format
std::vector<std::vector<uint32_t>> O_sparse;
Expand Down Expand Up @@ -400,6 +400,6 @@ inline void convert_vec_hard_to_soft(const std::vector<std::vector<t_hard>> &in,
}

std::unique_ptr<decoder>
get_decoder(const std::string &name, const cudaqx::tensor<uint8_t> &H,
get_decoder(const std::string &name, const sparse_binary_matrix &H,
const cudaqx::heterogeneous_map options = {});
} // namespace cudaq::qec
10 changes: 9 additions & 1 deletion libs/qec/include/cudaq/qec/pcm_utils.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/****************************************************************-*- C++ -*-****
* Copyright (c) 2025 NVIDIA Corporation & Affiliates. *
* Copyright (c) 2025 - 2026 NVIDIA Corporation & Affiliates. *
* All rights reserved. *
* *
* This source code and the accompanying materials are made available under *
Expand Down Expand Up @@ -111,6 +111,14 @@ get_sorted_pcm_column_indices(const cudaqx::tensor<uint8_t> &pcm,
bool pcm_is_sorted(const cudaqx::tensor<uint8_t> &pcm,
std::uint32_t num_syndromes_per_round = 0);

/// @brief Check if a PCM is sorted.
/// @param sparse_pcm The sparse PCM to check (in the same format as
/// get_sparse_pcm())
/// @param num_syndromes_per_round The number of syndromes per round.
/// @return True if the PCM is sorted, false otherwise.
bool pcm_is_sorted(const std::vector<std::vector<std::uint32_t>> &sparse_pcm,
std::uint32_t num_syndromes_per_round = 0);

/// @brief 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
/// \p column_order does not contain all of the columns in the original PCM.
Expand Down
124 changes: 124 additions & 0 deletions libs/qec/include/cudaq/qec/sparse_binary_matrix.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/****************************************************************-*- C++ -*-****
* Copyright (c) 2026 NVIDIA Corporation & Affiliates. *
* All rights reserved. *
* *
* This source code and the accompanying materials are made available under *
* the terms of the Apache License 2.0 which accompanies this distribution. *
******************************************************************************/
#pragma once

#include "cuda-qx/core/tensor.h"
#include <cstdint>
#include <vector>

namespace cudaq::qec {

/// @brief Storage layout for the sparse PCM: column-major (CSC) or row-major
/// (CSR). All non-zero entries are assumed to be 1; values are not stored.
enum class sparse_binary_matrix_layout { csc, csr };

/// @brief Sparse parity-check matrix in either CSC or CSR form.
/// Index types are uint32_t; non-zero values are always 1 and are not stored.
class sparse_binary_matrix {
public:
using index_type = std::uint32_t;

/// @brief Construct a sparse PCM in CSC form.
/// @param num_rows Number of rows.
/// @param num_cols Number of columns.
/// @param col_ptrs Column pointer array (length num_cols + 1); column j has
/// indices in \p row_indices[col_ptrs[j] .. col_ptrs[j+1]-1].
/// @param row_indices Row indices of non-zeros (length nnz).
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);

/// @brief Construct a sparse PCM in CSR form.
/// @param num_rows Number of rows.
/// @param num_cols Number of columns.
/// @param row_ptrs Row pointer array (length num_rows + 1); row i has
/// indices in \p col_indices[row_ptrs[i] .. row_ptrs[i+1]-1].
/// @param col_indices Column 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);

/// @brief Construct from nested CSC: \p nested[j] is the list of row indices
/// for column j; \p nested.size() must equal \p num_cols.
static sparse_binary_matrix
from_nested_csc(index_type num_rows, index_type num_cols,
const std::vector<std::vector<index_type>> &nested);

/// @brief Construct from nested CSR: \p nested[i] is the list of column
/// indices for row i; \p nested.size() must equal \p num_rows.
static sparse_binary_matrix
from_nested_csr(index_type num_rows, index_type num_cols,
const std::vector<std::vector<index_type>> &nested);

/// @brief Construct a sparse PCM from a dense PCM tensor (rows x columns).
/// Any non-zero entry is treated as 1.
/// @param dense Dense parity-check matrix; must have rank 2.
/// @param layout Storage layout for the sparse representation (default CSC).
sparse_binary_matrix(
const cudaqx::tensor<std::uint8_t> &dense,
sparse_binary_matrix_layout layout = sparse_binary_matrix_layout::csc);

// Default constructor
sparse_binary_matrix() = default;

// Copy constructor
sparse_binary_matrix(const sparse_binary_matrix &) = default;

// Move constructor
sparse_binary_matrix(sparse_binary_matrix &&) noexcept = default;

// Copy assignment operator
sparse_binary_matrix &operator=(const sparse_binary_matrix &) = default;

// Move assignment operator
sparse_binary_matrix &operator=(sparse_binary_matrix &&) noexcept = default;

sparse_binary_matrix_layout layout() const { return layout_; }
index_type num_rows() const { return num_rows_; }
index_type num_cols() const { return num_cols_; }
index_type num_nnz() const {
return indices_.empty() ? 0 : static_cast<index_type>(indices_.size());
}

/// @brief For CSC: ptr has length num_cols+1; for CSR: ptr has length
/// num_rows+1.
const std::vector<index_type> &ptr() const { return ptr_; }
/// @brief For CSC: row indices; for CSR: column indices.
const std::vector<index_type> &indices() const { return indices_; }

/// @brief Return a copy of this matrix in CSC layout. No-op if already CSC.
sparse_binary_matrix to_csc() const;

/// @brief Return a copy of this matrix in CSR layout. No-op if already CSR.
sparse_binary_matrix to_csr() const;

/// @brief Convert to a dense PCM tensor (rows x columns). Non-zero entries
/// are set to 1.
cudaqx::tensor<std::uint8_t> to_dense() const;

/// @brief 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_csc() const;

/// @brief Nested CSR: outer vector has size num_rows; inner vector for row i
/// lists column indices of non-zeros in that row.
std::vector<std::vector<index_type>> to_nested_csr() const;

private:
sparse_binary_matrix(sparse_binary_matrix_layout layout, index_type num_rows,
index_type num_cols, std::vector<index_type> ptr,
std::vector<index_type> indices);

sparse_binary_matrix_layout layout_ = sparse_binary_matrix_layout::csc;
index_type num_rows_ = 0;
index_type num_cols_ = 0;
std::vector<index_type> ptr_;
std::vector<index_type> indices_;
};

} // namespace cudaq::qec
3 changes: 2 additions & 1 deletion libs/qec/lib/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# ============================================================================ #
# Copyright (c) 2024 - 2025 NVIDIA Corporation & Affiliates. #
# Copyright (c) 2024 - 2026 NVIDIA Corporation & Affiliates. #
# All rights reserved. #
# #
# This source code and the accompanying materials are made available under #
Expand All @@ -18,6 +18,7 @@ add_library(${LIBRARY_NAME} SHARED
experiments.cpp
pcm_utils.cpp
plugin_loader.cpp
sparse_binary_matrix.cpp
stabilizer_utils.cpp
decoders/lut.cpp
decoders/sliding_window.cpp
Expand Down
20 changes: 10 additions & 10 deletions libs/qec/lib/decoder.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. *
* Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. *
* All rights reserved. *
* *
* This source code and the accompanying materials are made available under *
Expand All @@ -17,8 +17,10 @@
#include <filesystem>
#include <vector>

INSTANTIATE_REGISTRY(cudaq::qec::decoder, const cudaqx::tensor<uint8_t> &)
INSTANTIATE_REGISTRY(cudaq::qec::decoder, const cudaqx::tensor<uint8_t> &,
INSTANTIATE_REGISTRY(cudaq::qec::decoder,
const cudaq::qec::sparse_binary_matrix &)
INSTANTIATE_REGISTRY(cudaq::qec::decoder,
const cudaq::qec::sparse_binary_matrix &,
const cudaqx::heterogeneous_map &)

// Include decoder implementations AFTER registry instantiation
Expand Down Expand Up @@ -70,12 +72,10 @@ struct decoder::rt_impl {

void decoder::rt_impl_deleter::operator()(rt_impl *p) const { delete p; }

decoder::decoder(const cudaqx::tensor<uint8_t> &H)
decoder::decoder(const cudaq::qec::sparse_binary_matrix &H)
: H(H), pimpl(std::unique_ptr<rt_impl, rt_impl_deleter>(new rt_impl())) {
const auto H_shape = H.shape();
assert(H_shape.size() == 2 && "H tensor must be of rank 2");
syndrome_size = H_shape[0];
block_size = H_shape[1];
syndrome_size = H.num_rows();
block_size = H.num_cols();
reset_decoder();
pimpl->persistent_detector_buffer.resize(this->syndrome_size);
pimpl->persistent_soft_detector_buffer.resize(this->syndrome_size);
Expand Down Expand Up @@ -130,7 +130,7 @@ decoder::decode_async(const std::vector<float_t> &syndrome) {
}

std::unique_ptr<decoder>
decoder::get(const std::string &name, const cudaqx::tensor<uint8_t> &H,
decoder::get(const std::string &name, const cudaq::qec::sparse_binary_matrix &H,
const cudaqx::heterogeneous_map &param_map) {
auto [mutex, registry] = get_registry();
std::lock_guard<std::recursive_mutex> lock(mutex);
Expand Down Expand Up @@ -479,7 +479,7 @@ void decoder::reset_decoder() {
}

std::unique_ptr<decoder> get_decoder(const std::string &name,
const cudaqx::tensor<uint8_t> &H,
const sparse_binary_matrix &H,
const cudaqx::heterogeneous_map options) {
return decoder::get(name, H, options);
}
Expand Down
17 changes: 6 additions & 11 deletions libs/qec/lib/decoders/lut.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. *
* Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. *
* All rights reserved. *
* *
* This source code and the accompanying materials are made available under *
Expand Down Expand Up @@ -48,7 +48,7 @@ class multi_error_lut : public decoder {
bool decoding_time = false;

public:
multi_error_lut(const cudaqx::tensor<uint8_t> &H,
multi_error_lut(const cudaq::qec::sparse_binary_matrix &H,
const cudaqx::heterogeneous_map &params)
: decoder(H) {
if (params.contains("lut_error_depth")) {
Expand Down Expand Up @@ -119,12 +119,7 @@ class multi_error_lut : public decoder {

// For each error e, build a list of detectors that are set if the error
// occurs.
std::vector<std::vector<std::size_t>> H_e2d(block_size);
for (std::size_t c = 0; c < block_size; c++)
for (std::size_t r = 0; r < syndrome_size; r++)
if (H.at({r, c}) != 0)
H_e2d[c].push_back(r);

std::vector<std::vector<std::uint32_t>> H_e2d = H.to_nested_csc();
auto toggleSynForError = [&H_e2d](std::string &err_sig, std::size_t qErr) {
for (std::size_t r : H_e2d[qErr])
err_sig[r] = err_sig[r] == '1' ? '0' : '1';
Expand Down Expand Up @@ -233,7 +228,7 @@ class multi_error_lut : public decoder {

CUDAQ_EXTENSION_CUSTOM_CREATOR_FUNCTION(
multi_error_lut, static std::unique_ptr<decoder> create(
const cudaqx::tensor<uint8_t> &H,
const cudaq::qec::sparse_binary_matrix &H,
const cudaqx::heterogeneous_map &params) {
return std::make_unique<multi_error_lut>(H, params);
})
Expand All @@ -243,15 +238,15 @@ CUDAQ_REGISTER_TYPE(multi_error_lut)

class single_error_lut : public multi_error_lut {
public:
single_error_lut(const cudaqx::tensor<uint8_t> &H,
single_error_lut(const cudaq::qec::sparse_binary_matrix &H,
const cudaqx::heterogeneous_map &params)
: multi_error_lut(H, params) {}

virtual ~single_error_lut() {}

CUDAQ_EXTENSION_CUSTOM_CREATOR_FUNCTION(
single_error_lut, static std::unique_ptr<decoder> create(
const cudaqx::tensor<uint8_t> &H,
const cudaq::qec::sparse_binary_matrix &H,
const cudaqx::heterogeneous_map &params) {
return std::make_unique<single_error_lut>(H, params);
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. *
* Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. *
* All rights reserved. *
* *
* This source code and the accompanying materials are made available under *
Expand All @@ -21,22 +21,22 @@ class single_error_lut_example : public decoder {
std::map<std::string, std::size_t> single_qubit_err_signatures;

public:
single_error_lut_example(const cudaqx::tensor<uint8_t> &H,
single_error_lut_example(const cudaq::qec::sparse_binary_matrix &H,
const cudaqx::heterogeneous_map &params)
: decoder(H) {
// Decoder-specific constructor arguments can be placed in `params`.

// Build a lookup table for an error on each possible qubit
std::vector<std::vector<std::uint32_t>> H_e2d = H.to_nested_csc();

// For each qubit with a possible error, calculate an error signature.
for (std::size_t qErr = 0; qErr < block_size; qErr++) {
std::string err_sig(syndrome_size, '0');
for (std::size_t r = 0; r < syndrome_size; r++) {
bool syndrome = 0;
// Toggle syndrome on every "1" entry in the row.
// Except if there is an error on this qubit (c == qErr).
for (std::size_t c = 0; c < block_size; c++)
syndrome ^= (c != qErr) && H.at({r, c});
for (std::uint32_t c : H_e2d[qErr])
syndrome ^= 1;
err_sig[r] = syndrome ? '1' : '0';
}
// printf("Adding err_sig=%s for qErr=%lu\n", err_sig.c_str(), qErr);
Expand Down Expand Up @@ -80,7 +80,7 @@ class single_error_lut_example : public decoder {

CUDAQ_EXTENSION_CUSTOM_CREATOR_FUNCTION(
single_error_lut_example, static std::unique_ptr<decoder> create(
const cudaqx::tensor<uint8_t> &H,
const cudaq::qec::sparse_binary_matrix &H,
const cudaqx::heterogeneous_map &params) {
return std::make_unique<single_error_lut_example>(H, params);
})
Expand Down
Loading