Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions cmake/Modules/CUDA-QX.cmake
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# ============================================================================ #
# Copyright (c) 2024 NVIDIA Corporation & Affiliates. #
# Copyright (c) 2024 - 2025 NVIDIA Corporation & Affiliates. #
# All rights reserved. #
# #
# This source code and the accompanying materials are made available under #
Expand Down Expand Up @@ -43,6 +43,9 @@ resulting object files to the specified library target.
Note: This function assumes that the CUDAQ_INSTALL_DIR variable is set
to the CUDAQ installation directory.

Note: You can use DEPENDS_ON if you want to delay compilation until some other
target has been built.

Example usage:
cudaqx_add_device_code(
my_library
Expand All @@ -52,13 +55,15 @@ Example usage:
COMPILER_FLAGS
--enable-mlir
-v
DEPENDS_ON
SomeOtherTarget
)

#]=======================================================================]
function(cudaqx_add_device_code LIBRARY_NAME)
set(options)
set(oneValueArgs)
set(multiValueArgs SOURCES COMPILER_FLAGS)
set(multiValueArgs SOURCES COMPILER_FLAGS DEPENDS_ON)
cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

if(NOT DEFINED CUDAQ_INSTALL_DIR)
Expand All @@ -83,7 +88,7 @@ function(cudaqx_add_device_code LIBRARY_NAME)
set(prop "$<TARGET_PROPERTY:${LIBRARY_NAME},INCLUDE_DIRECTORIES>")
foreach(source ${ARGS_SOURCES})
get_filename_component(filename ${source} NAME_WE)
set(output_file "${CMAKE_CURRENT_BINARY_DIR}/${filename}.o")
set(output_file "${CMAKE_CURRENT_BINARY_DIR}/${LIBRARY_NAME}_${filename}.o")
cmake_path(GET output_file FILENAME baseName)

add_custom_command(
Expand All @@ -92,15 +97,15 @@ function(cudaqx_add_device_code LIBRARY_NAME)
${ARGS_COMPILER_FLAGS} -c -fPIC --enable-mlir
${CMAKE_CURRENT_SOURCE_DIR}/${source} -o ${baseName}
"$<$<BOOL:${prop}>:-I $<JOIN:${prop}, -I >>"
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${source}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${source} ${ARGS_DEPENDS_ON}
COMMENT "Compiling ${source} with nvq++"
VERBATIM
)

list(APPEND object_files ${output_file})
list(APPEND custom_targets ${filename}_target)
list(APPEND custom_targets ${LIBRARY_NAME}_${filename}_target)

add_custom_target(${filename}_target DEPENDS ${output_file})
add_custom_target(${LIBRARY_NAME}_${filename}_target DEPENDS ${output_file})
endforeach()

add_dependencies(${LIBRARY_NAME} ${custom_targets})
Expand Down
29 changes: 25 additions & 4 deletions libs/qec/include/cudaq/qec/decoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ class decoder
std::size_t get_syndrome_size() { return syndrome_size; }

/// @brief Destructor
virtual ~decoder() {}
virtual ~decoder() = default;

/// @brief Get the version of the decoder. Subclasses that are not part of the
/// standard GitHub repo should override this to provide a more tailored
Expand Down Expand Up @@ -243,6 +243,7 @@ inline void convert_vec_soft_to_tensor_hard(const std::vector<t_soft> &in,
/// @brief Convert a vector of hard probabilities to a vector of soft
/// probabilities.
/// @param in Hard probability input vector containing only 0/false or 1/true.
/// @param in_size The size of the input vector (in elements)
/// @param out Soft probability output vector in the range [0.0, 1.0]
/// @param true_val The soft probability value assigned when the input is 1
/// (default to 1.0)
Expand All @@ -253,15 +254,35 @@ template <typename t_soft, typename t_hard,
(std::is_integral<t_hard>::value ||
std::is_same<t_hard, bool>::value),
int>::type = 0>
inline void convert_vec_hard_to_soft(const std::vector<t_hard> &in,
inline void convert_vec_hard_to_soft(const t_hard *in, std::size_t in_size,
std::vector<t_soft> &out,
const t_soft true_val = 1.0,
const t_soft false_val = 0.0) {
out.resize(in.size());
for (std::size_t i = 0; i < in.size(); i++)
out.resize(in_size);
for (std::size_t i = 0; i < in_size; i++)
out[i] = static_cast<t_soft>(in[i] ? true_val : false_val);
}

/// @brief Convert a vector of hard probabilities to a vector of soft
/// probabilities.
/// @param in Hard probability input vector containing only 0/false or 1/true.
/// @param out Soft probability output vector in the range [0.0, 1.0]
/// @param true_val The soft probability value assigned when the input is 1
/// (default to 1.0)
/// @param false_val The soft probability value assigned when the input is 0
/// (default to 0.0)
template <typename t_soft, typename t_hard,
typename std::enable_if<std::is_floating_point<t_soft>::value &&
(std::is_integral<t_hard>::value ||
std::is_same<t_hard, bool>::value),
int>::type = 0>
inline void convert_vec_hard_to_soft(const std::vector<t_hard> &in,
std::vector<t_soft> &out,
const t_soft true_val = 1.0,
const t_soft false_val = 0.0) {
convert_vec_hard_to_soft(in.data(), in.size(), out, true_val, false_val);
}

/// @brief Convert a 2D vector of soft probabilities to a 2D vector of hard
/// probabilities.
/// @param in Soft probability input vector in range [0.0, 1.0]
Expand Down
56 changes: 56 additions & 0 deletions libs/qec/include/cudaq/qec/pcm_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,62 @@ namespace cudaq::qec {
std::vector<std::vector<std::uint32_t>>
dense_to_sparse(const cudaqx::tensor<uint8_t> &pcm);

/// @brief Return a sparse representation of the PCM as a string.
/// @param pcm The PCM to convert to a sparse representation.
/// @return A string that represents the PCM in a sparse format.
Comment on lines +25 to +27
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: I'm intentionally not referencing these new functions in the .rst files yet in order to keep our published docs consistent with the latest release. I'll open a new issue to track which functions need to be added to the docs when we get closer to the release.

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

/// @brief Return a PCM from a sparse representation.
/// @param sparse_str The sparse representation of the PCM.
/// @param num_rows The number of rows in the PCM.
/// @param num_cols The number of columns in the PCM.
/// @return A PCM tensor.
cudaqx::tensor<uint8_t> pcm_from_sparse_string(const std::string &sparse_str,
std::size_t num_rows,
std::size_t num_cols);

/// @brief Return a PCM from a sparse representation.
/// @param sparse_vec The sparse representation of the PCM.
/// @param num_rows The number of rows in the PCM.
/// @param num_cols The number of columns in the PCM.
/// @return A PCM tensor.
cudaqx::tensor<uint8_t>
pcm_from_sparse_vec(const std::vector<std::int64_t> &sparse_vec,
std::size_t num_rows, std::size_t num_cols);

/// @brief Return a sparse representation of the PCM.
/// @param pcm The PCM to convert to a sparse representation.
/// @return A vector of integers that represents the PCM in a sparse format.
std::vector<std::int64_t> pcm_to_sparse_vec(const cudaqx::tensor<uint8_t> &pcm);

/// @brief 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.
/// @param num_syndromes_per_round The number of syndromes per round.
/// @param num_rounds The number of rounds.
/// @param include_first_round Whether to include the first round in the
/// detector matrix.
/// @return The detector matrix format is CSR-like, with -1 values
/// indicating the end of a row.
std::vector<std::int64_t>
generate_timelike_sparse_detector_matrix(std::uint32_t num_syndromes_per_round,
std::uint32_t num_rounds,
bool include_first_round = false);

/// @brief 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.
/// @param num_syndromes_per_round The number of syndromes per round.
/// @param num_rounds The number of rounds.
/// @param first_round_matrix User specified detector matrix for the first
/// round.
/// @return The detector matrix format is CSR-like, with -1 values
/// indicating the end of a row.
std::vector<std::int64_t> 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);

/// @brief Return a vector of column indices that would sort the PCM columns
/// in topological order.
/// @param row_indices For each column, a vector of row indices that have a
Expand Down
10 changes: 5 additions & 5 deletions libs/qec/lib/codes/surface_code_device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ __qpu__ void prepm(patch logicalQubit) {
__qpu__ std::vector<cudaq::measure_result>
stabilizer(patch logicalQubit, const std::vector<std::size_t> &x_stabilizers,
const std::vector<std::size_t> &z_stabilizers) {
for (std::size_t i = 0; i < logicalQubit.ancx.size(); i++)
reset(logicalQubit.ancx[i]);
for (std::size_t i = 0; i < logicalQubit.ancz.size(); i++)
reset(logicalQubit.ancz[i]);

h(logicalQubit.ancx);
for (std::size_t xi = 0; xi < logicalQubit.ancx.size(); ++xi)
for (std::size_t di = 0; di < logicalQubit.data.size(); ++di)
Expand All @@ -69,11 +74,6 @@ stabilizer(patch logicalQubit, const std::vector<std::size_t> &x_stabilizers,
// z flips are triggered by x-stabilizers (ancx)
auto results = mz(logicalQubit.ancz, logicalQubit.ancx);

for (std::size_t i = 0; i < logicalQubit.ancx.size(); i++)
reset(logicalQubit.ancx[i]);
for (std::size_t i = 0; i < logicalQubit.ancz.size(); i++)
reset(logicalQubit.ancz[i]);

return results;
}

Expand Down
128 changes: 128 additions & 0 deletions libs/qec/lib/pcm_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -583,4 +583,132 @@ pcm_extend_to_n_rounds(const cudaqx::tensor<uint8_t> &pcm,

return std::make_pair(new_pcm, column_list);
}

std::string pcm_to_sparse_string(const cudaqx::tensor<uint8_t> &pcm) {
// Output the string like:
// 2,5,7,-1,2,5,32,-1,...
std::stringstream ss;
for (std::size_t r = 0; r < pcm.shape()[0]; r++) {
const uint8_t *row = &pcm.at({r, 0});
for (std::size_t c = 0; c < pcm.shape()[1]; c++) {
if (row[c] == 1) {
ss << c << ",";
}
}
ss << "-1,";
}
std::string result = ss.str();
result.pop_back(); // trim the final comma
return result;
}

std::vector<std::int64_t>
pcm_to_sparse_vec(const cudaqx::tensor<uint8_t> &pcm) {
std::vector<std::int64_t> sparse_vec;
for (std::size_t r = 0; r < pcm.shape()[0]; r++) {
for (std::size_t c = 0; c < pcm.shape()[1]; c++) {
if (pcm.at({r, c}) == 1) {
sparse_vec.push_back(static_cast<std::int64_t>(c));
}
}
sparse_vec.push_back(-1);
}
return sparse_vec;
}

cudaqx::tensor<uint8_t> pcm_from_sparse_string(const std::string &sparse_str,
std::size_t num_rows,
std::size_t num_cols) {
cudaqx::tensor<uint8_t> pcm(std::vector<std::size_t>{num_rows, num_cols});
std::stringstream ss(sparse_str);
std::string item;
std::uint32_t row = 0;
while (std::getline(ss, item, ',')) {
if (item == "-1") {
row++;
continue;
}
std::uint32_t col = std::stoul(item);
pcm.at({row, col}) = 1;
}
return pcm;
}

cudaqx::tensor<uint8_t>
pcm_from_sparse_vec(const std::vector<std::int64_t> &sparse_vec,
std::size_t num_rows, std::size_t num_cols) {
cudaqx::tensor<uint8_t> pcm(std::vector<std::size_t>{num_rows, num_cols});
std::uint64_t row = 0;
for (std::int64_t col : sparse_vec) {
if (col < 0) {
row++;
continue;
}
pcm.at({row, static_cast<uint64_t>(col)}) = 1;
}
return pcm;
}

std::vector<std::int64_t>
generate_timelike_sparse_detector_matrix(std::uint32_t num_syndromes_per_round,
std::uint32_t num_rounds,
bool include_first_round) {
std::vector<std::int64_t> detector_matrix;
if (include_first_round) {
for (std::uint32_t i = 0; i < num_syndromes_per_round; i++) {
detector_matrix.push_back(i);
detector_matrix.push_back(-1);
}
}
// Every round after this is a XOR of the prior round's syndrome with the
// current round's syndrome.
for (std::uint32_t i = 1; i < num_rounds; i++) {
for (std::uint32_t j = 0; j < num_syndromes_per_round; j++) {
detector_matrix.push_back((i - 1) * num_syndromes_per_round + j);
detector_matrix.push_back(i * num_syndromes_per_round + j);
detector_matrix.push_back(-1);
}
}

return detector_matrix;
}

std::vector<std::int64_t> 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) {
if (first_round_matrix.size() == 0) {
throw std::invalid_argument("generate_timelike_sparse_detector_matrix: "
"first_round_matrix must be non-empty");
}

for (std::uint32_t i = 0; i < first_round_matrix.size(); i++) {
bool index_parity = (i % 2 == 0);
// even elements should be >= 0
if (index_parity && (first_round_matrix[i] < 0)) {
throw std::invalid_argument(
"generate_timelike_sparse_detector_matrix: first_round_matrix should "
"have one index per row (row end indicated by -1)");
}
// odd elements should be -1
if (!index_parity && (first_round_matrix[i] != -1)) {
throw std::invalid_argument(
"generate_timelike_sparse_detector_matrix: first_round_matrix should "
"have one index per row (row end indicated by -1)");
}
}

std::vector<std::int64_t> detector_matrix(first_round_matrix);

// Every round after this is a XOR of the prior round's syndrome with the
// current round's syndrome.
for (std::uint32_t i = 1; i < num_rounds; i++) {
for (std::uint32_t j = 0; j < num_syndromes_per_round; j++) {
detector_matrix.push_back((i - 1) * num_syndromes_per_round + j);
detector_matrix.push_back(i * num_syndromes_per_round + j);
detector_matrix.push_back(-1);
}
}

return detector_matrix;
}
} // namespace cudaq::qec
5 changes: 5 additions & 0 deletions libs/qec/unittests/test_decoders.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ TEST(DecoderUtils, CovertHardToSoft) {
for (int i = 0; i < out.size(); i++)
ASSERT_EQ(out[i], expected_out[i]);

cudaq::qec::convert_vec_hard_to_soft(in.data(), in.size(), out);
ASSERT_EQ(out.size(), expected_out.size());
for (int i = 0; i < out.size(); i++)
ASSERT_EQ(out[i], expected_out[i]);

expected_out = {0.9, 0.1, 0.9, 0.9};
cudaq::qec::convert_vec_hard_to_soft(in, out, 0.9f, 0.1f);
ASSERT_EQ(out.size(), expected_out.size());
Expand Down
40 changes: 40 additions & 0 deletions libs/qec/unittests/test_qec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1431,6 +1431,46 @@ TEST(QECCodeTester, checkVersion) {
std::string::npos);
}

TEST(PCMUtilsTester, checkPCMToSparseString) {
// Generate a random PCM.
std::size_t n_rounds = 4;
std::size_t n_errs_per_round = 30;
std::size_t n_syndromes_per_round = 10;
std::size_t weight = 3;
std::mt19937_64 rng(13);
cudaqx::tensor<uint8_t> pcm = cudaq::qec::generate_random_pcm(
n_rounds, n_errs_per_round, n_syndromes_per_round, weight,
std::move(rng));
// Get the string version of the PCM
auto sparse_str = cudaq::qec::pcm_to_sparse_string(pcm);
// Get the PCM from the string
auto pcm_from_str = cudaq::qec::pcm_from_sparse_string(
sparse_str, n_rounds * n_syndromes_per_round,
n_rounds * n_errs_per_round);
// Verify that the original and the from-string PCM are the same
check_pcm_equality(pcm, pcm_from_str, true);
}

TEST(PCMUtilsTester, checkPCMToSparseVec) {
// Generate a random PCM.
std::size_t n_rounds = 7;
std::size_t n_errs_per_round = 10;
std::size_t n_syndromes_per_round = 100;
std::size_t weight = 3;
std::mt19937_64 rng(13);
cudaqx::tensor<uint8_t> pcm = cudaq::qec::generate_random_pcm(
n_rounds, n_errs_per_round, n_syndromes_per_round, weight,
std::move(rng));
// Get the string version of the PCM
auto sparse_vec = cudaq::qec::pcm_to_sparse_vec(pcm);
// Get the PCM from the string
auto pcm_from_vec = cudaq::qec::pcm_from_sparse_vec(
sparse_vec, n_rounds * n_syndromes_per_round,
n_rounds * n_errs_per_round);
// Verify that the original and the from-string PCM are the same
check_pcm_equality(pcm, pcm_from_vec, true);
}

// Test detector_error_model methods
TEST(DetectorErrorModelTest, NumDetectors) {
cudaq::qec::detector_error_model dem;
Expand Down
Loading