From 56b61d58c87f17c125d538893d08f2172ec196bd Mon Sep 17 00:00:00 2001 From: Kevin Mato Date: Wed, 19 Nov 2025 16:38:18 +0000 Subject: [PATCH 1/2] initial idea of generic sampling mechanism Signed-off-by: Kevin Mato --- libs/qec/include/cudaq/qec/experiments.h | 22 ++++ libs/qec/lib/experiments.cpp | 58 +++++++++++ libs/qec/unittests/test_qec.cpp | 123 +++++++++++++++++++++++ 3 files changed, 203 insertions(+) diff --git a/libs/qec/include/cudaq/qec/experiments.h b/libs/qec/include/cudaq/qec/experiments.h index 74ba476f..a7e7460a 100644 --- a/libs/qec/include/cudaq/qec/experiments.h +++ b/libs/qec/include/cudaq/qec/experiments.h @@ -61,6 +61,28 @@ std::tuple, cudaqx::tensor> sample_code_capacity(const code &code, std::size_t numShots, double error_probability); +/// @brief Sample measurements from a check matrix with per-mechanism error probabilities +/// @param check_matrix Binary matrix [num_checks × num_error_mechanisms] +/// @param numShots Number of measurement shots +/// @param error_probabilities Per-error-mechanism probabilities +/// @return Tuple of check measurements [numShots × num_checks] and error occurrences [numShots × num_error_mechanisms] +std::tuple, cudaqx::tensor> +dem_sampling(const cudaqx::tensor &check_matrix, + std::size_t numShots, + const std::vector &error_probabilities); + +/// @brief Sample measurements from a check matrix with seed +/// @param check_matrix Binary matrix [num_checks × num_error_mechanisms] +/// @param numShots Number of measurement shots +/// @param error_probabilities Per-error-mechanism probabilities +/// @param seed RNG seed for reproducible experiments +/// @return Tuple of check measurements [numShots × num_checks] and error occurrences [numShots × num_error_mechanisms] +std::tuple, cudaqx::tensor> +dem_sampling(const cudaqx::tensor &check_matrix, + std::size_t numShots, + const std::vector &error_probabilities, + unsigned seed); + /// @brief Sample syndrome measurements with circuit-level noise /// @param statePrep Initial state preparation operation /// @param numShots Number of measurement shots diff --git a/libs/qec/lib/experiments.cpp b/libs/qec/lib/experiments.cpp index dab4bdc7..1903563b 100644 --- a/libs/qec/lib/experiments.cpp +++ b/libs/qec/lib/experiments.cpp @@ -83,6 +83,64 @@ sample_code_capacity(const code &code, std::size_t nShots, seed); } +namespace details { +auto __dem_sampling(const cudaqx::tensor &check_matrix, + std::size_t nShots, + const std::vector &error_probabilities, + unsigned seed) { + // Validate input + if (check_matrix.rank() != 2) + throw std::invalid_argument("check_matrix must be rank-2"); + + size_t num_checks = check_matrix.shape()[0]; + size_t num_error_mechanisms = check_matrix.shape()[1]; + + if (error_probabilities.size() != num_error_mechanisms) + throw std::invalid_argument( + "error_probabilities size must match number of error mechanisms"); + + // Init RNG and distributions + std::mt19937 rng(seed); + std::vector distributions; + distributions.reserve(num_error_mechanisms); + for (double p : error_probabilities) + distributions.emplace_back(p); + + // Prepare output tensors + cudaqx::tensor errors({nShots, num_error_mechanisms}); + cudaqx::tensor checks({nShots, num_checks}); + + // Generate error occurrences + std::vector error_bits(nShots * num_error_mechanisms); + for (size_t shot = 0; shot < nShots; ++shot) { + for (size_t err = 0; err < num_error_mechanisms; ++err) { + error_bits[shot * num_error_mechanisms + err] = distributions[err](rng); + } + } + + errors.copy(error_bits.data(), errors.shape()); + + // Compute checks: Checks = Errors * CheckMatrix^T (mod 2) + checks = errors.dot(check_matrix.transpose()) % 2; + + return std::make_tuple(checks, errors); +} +} // namespace details + +std::tuple, cudaqx::tensor> +dem_sampling(const cudaqx::tensor &check_matrix, std::size_t nShots, + const std::vector &error_probabilities) { + return details::__dem_sampling(check_matrix, nShots, error_probabilities, + std::random_device()()); +} + +std::tuple, cudaqx::tensor> +dem_sampling(const cudaqx::tensor &check_matrix, std::size_t nShots, + const std::vector &error_probabilities, unsigned seed) { + return details::__dem_sampling(check_matrix, nShots, error_probabilities, + seed); +} + std::tuple, cudaqx::tensor> sample_memory_circuit(const code &code, operation statePrep, std::size_t numShots, std::size_t numRounds, diff --git a/libs/qec/unittests/test_qec.cpp b/libs/qec/unittests/test_qec.cpp index 197cfd81..a2ff7011 100644 --- a/libs/qec/unittests/test_qec.cpp +++ b/libs/qec/unittests/test_qec.cpp @@ -581,6 +581,129 @@ TEST(QECCodeTester, checkCodeCapacityWithCodeObject) { } } +TEST(QECCodeTester, checkDemSampling) { + // Test dem_sampling with per-error-mechanism probabilities + { + // Create a simple check matrix [3 checks x 5 error mechanisms] + cudaqx::tensor check_matrix({3, 5}); + std::vector matrix_data = { + 1, 0, 1, 0, 0, // check 0 triggered by errors 0, 2 + 0, 1, 1, 0, 0, // check 1 triggered by errors 1, 2 + 0, 0, 0, 1, 1 // check 2 triggered by errors 3, 4 + }; + check_matrix.copy(matrix_data.data(), check_matrix.shape()); + + int nShots = 10; + std::vector error_probs = {0.1, 0.2, 0.15, 0.05, 0.25}; + unsigned seed = 42; + + auto [checks, errors] = + cudaq::qec::dem_sampling(check_matrix, nShots, error_probs, seed); + + // Validate shapes + EXPECT_EQ(nShots, checks.shape()[0]); + EXPECT_EQ(3, checks.shape()[1]); // 3 checks + EXPECT_EQ(nShots, errors.shape()[0]); + EXPECT_EQ(5, errors.shape()[1]); // 5 error mechanisms + + // Verify determinism with same seed + auto [checks2, errors2] = + cudaq::qec::dem_sampling(check_matrix, nShots, error_probs, seed); + + for (size_t i = 0; i < nShots; ++i) { + for (size_t j = 0; j < 3; ++j) { + EXPECT_EQ(checks.at({i, j}), checks2.at({i, j})); + } + for (size_t j = 0; j < 5; ++j) { + EXPECT_EQ(errors.at({i, j}), errors2.at({i, j})); + } + } + + // Verify check computation: checks = errors * check_matrix^T (mod 2) + for (size_t shot = 0; shot < nShots; ++shot) { + for (size_t check = 0; check < 3; ++check) { + uint8_t expected = 0; + for (size_t err = 0; err < 5; ++err) { + expected ^= + (errors.at({shot, err}) & check_matrix.at({check, err})); + } + EXPECT_EQ(expected, checks.at({shot, check})); + } + } + } + + // Test with zero error probabilities + { + cudaqx::tensor check_matrix({2, 3}); + std::vector matrix_data = {1, 0, 1, 0, 1, 1}; + check_matrix.copy(matrix_data.data(), check_matrix.shape()); + + int nShots = 5; + std::vector error_probs = {0.0, 0.0, 0.0}; + + auto [checks, errors] = + cudaq::qec::dem_sampling(check_matrix, nShots, error_probs); + + // All checks should be 0 + for (size_t i = 0; i < nShots; ++i) { + for (size_t j = 0; j < 2; ++j) { + EXPECT_EQ(0, checks.at({i, j})); + } + } + + // All errors should be 0 + for (size_t i = 0; i < nShots; ++i) { + for (size_t j = 0; j < 3; ++j) { + EXPECT_EQ(0, errors.at({i, j})); + } + } + } + + // Test without seed (random) + { + cudaqx::tensor check_matrix({2, 4}); + std::vector matrix_data = {1, 1, 0, 0, 0, 0, 1, 1}; + check_matrix.copy(matrix_data.data(), check_matrix.shape()); + + int nShots = 8; + std::vector error_probs = {0.3, 0.3, 0.3, 0.3}; + + auto [checks, errors] = + cudaq::qec::dem_sampling(check_matrix, nShots, error_probs); + + // Validate shapes + EXPECT_EQ(nShots, checks.shape()[0]); + EXPECT_EQ(2, checks.shape()[1]); + EXPECT_EQ(nShots, errors.shape()[0]); + EXPECT_EQ(4, errors.shape()[1]); + } + + // Test error: mismatched probability vector size + { + cudaqx::tensor check_matrix({2, 3}); + std::vector matrix_data = {1, 0, 1, 0, 1, 1}; + check_matrix.copy(matrix_data.data(), check_matrix.shape()); + + std::vector wrong_size_probs = {0.1, 0.2}; // Should be size 3 + + EXPECT_THROW( + cudaq::qec::dem_sampling(check_matrix, 10, wrong_size_probs), + std::invalid_argument); + } + + // Test error: non-rank-2 matrix + { + cudaqx::tensor bad_matrix({5}); // rank-1 + std::vector matrix_data = {1, 0, 1, 0, 1}; + bad_matrix.copy(matrix_data.data(), bad_matrix.shape()); + + std::vector error_probs = {0.1, 0.2, 0.3, 0.4, 0.5}; + + EXPECT_THROW(cudaq::qec::dem_sampling(bad_matrix, 10, error_probs), + std::invalid_argument); + } +} + TEST(QECCodeTester, checkRepetition) { { // must provide distance From 3c8a224a088b24a38fdba06bfaaa3370a49cd6de Mon Sep 17 00:00:00 2001 From: Kevin Mato Date: Wed, 19 Nov 2025 16:43:57 +0000 Subject: [PATCH 2/2] lint Signed-off-by: Kevin Mato --- libs/qec/include/cudaq/qec/experiments.h | 18 +++++++++--------- libs/qec/lib/experiments.cpp | 4 ++-- libs/qec/unittests/test_qec.cpp | 22 ++++++++++------------ 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/libs/qec/include/cudaq/qec/experiments.h b/libs/qec/include/cudaq/qec/experiments.h index a7e7460a..f7049fbf 100644 --- a/libs/qec/include/cudaq/qec/experiments.h +++ b/libs/qec/include/cudaq/qec/experiments.h @@ -61,14 +61,15 @@ std::tuple, cudaqx::tensor> sample_code_capacity(const code &code, std::size_t numShots, double error_probability); -/// @brief Sample measurements from a check matrix with per-mechanism error probabilities +/// @brief Sample measurements from a check matrix with per-mechanism error +/// probabilities /// @param check_matrix Binary matrix [num_checks × num_error_mechanisms] /// @param numShots Number of measurement shots /// @param error_probabilities Per-error-mechanism probabilities -/// @return Tuple of check measurements [numShots × num_checks] and error occurrences [numShots × num_error_mechanisms] +/// @return Tuple of check measurements [numShots × num_checks] and error +/// occurrences [numShots × num_error_mechanisms] std::tuple, cudaqx::tensor> -dem_sampling(const cudaqx::tensor &check_matrix, - std::size_t numShots, +dem_sampling(const cudaqx::tensor &check_matrix, std::size_t numShots, const std::vector &error_probabilities); /// @brief Sample measurements from a check matrix with seed @@ -76,12 +77,11 @@ dem_sampling(const cudaqx::tensor &check_matrix, /// @param numShots Number of measurement shots /// @param error_probabilities Per-error-mechanism probabilities /// @param seed RNG seed for reproducible experiments -/// @return Tuple of check measurements [numShots × num_checks] and error occurrences [numShots × num_error_mechanisms] +/// @return Tuple of check measurements [numShots × num_checks] and error +/// occurrences [numShots × num_error_mechanisms] std::tuple, cudaqx::tensor> -dem_sampling(const cudaqx::tensor &check_matrix, - std::size_t numShots, - const std::vector &error_probabilities, - unsigned seed); +dem_sampling(const cudaqx::tensor &check_matrix, std::size_t numShots, + const std::vector &error_probabilities, unsigned seed); /// @brief Sample syndrome measurements with circuit-level noise /// @param statePrep Initial state preparation operation diff --git a/libs/qec/lib/experiments.cpp b/libs/qec/lib/experiments.cpp index 1903563b..13e92b6a 100644 --- a/libs/qec/lib/experiments.cpp +++ b/libs/qec/lib/experiments.cpp @@ -131,14 +131,14 @@ std::tuple, cudaqx::tensor> dem_sampling(const cudaqx::tensor &check_matrix, std::size_t nShots, const std::vector &error_probabilities) { return details::__dem_sampling(check_matrix, nShots, error_probabilities, - std::random_device()()); + std::random_device()()); } std::tuple, cudaqx::tensor> dem_sampling(const cudaqx::tensor &check_matrix, std::size_t nShots, const std::vector &error_probabilities, unsigned seed) { return details::__dem_sampling(check_matrix, nShots, error_probabilities, - seed); + seed); } std::tuple, cudaqx::tensor> diff --git a/libs/qec/unittests/test_qec.cpp b/libs/qec/unittests/test_qec.cpp index a2ff7011..b579d73b 100644 --- a/libs/qec/unittests/test_qec.cpp +++ b/libs/qec/unittests/test_qec.cpp @@ -587,9 +587,9 @@ TEST(QECCodeTester, checkDemSampling) { // Create a simple check matrix [3 checks x 5 error mechanisms] cudaqx::tensor check_matrix({3, 5}); std::vector matrix_data = { - 1, 0, 1, 0, 0, // check 0 triggered by errors 0, 2 - 0, 1, 1, 0, 0, // check 1 triggered by errors 1, 2 - 0, 0, 0, 1, 1 // check 2 triggered by errors 3, 4 + 1, 0, 1, 0, 0, // check 0 triggered by errors 0, 2 + 0, 1, 1, 0, 0, // check 1 triggered by errors 1, 2 + 0, 0, 0, 1, 1 // check 2 triggered by errors 3, 4 }; check_matrix.copy(matrix_data.data(), check_matrix.shape()); @@ -602,9 +602,9 @@ TEST(QECCodeTester, checkDemSampling) { // Validate shapes EXPECT_EQ(nShots, checks.shape()[0]); - EXPECT_EQ(3, checks.shape()[1]); // 3 checks + EXPECT_EQ(3, checks.shape()[1]); // 3 checks EXPECT_EQ(nShots, errors.shape()[0]); - EXPECT_EQ(5, errors.shape()[1]); // 5 error mechanisms + EXPECT_EQ(5, errors.shape()[1]); // 5 error mechanisms // Verify determinism with same seed auto [checks2, errors2] = @@ -624,8 +624,7 @@ TEST(QECCodeTester, checkDemSampling) { for (size_t check = 0; check < 3; ++check) { uint8_t expected = 0; for (size_t err = 0; err < 5; ++err) { - expected ^= - (errors.at({shot, err}) & check_matrix.at({check, err})); + expected ^= (errors.at({shot, err}) & check_matrix.at({check, err})); } EXPECT_EQ(expected, checks.at({shot, check})); } @@ -684,16 +683,15 @@ TEST(QECCodeTester, checkDemSampling) { std::vector matrix_data = {1, 0, 1, 0, 1, 1}; check_matrix.copy(matrix_data.data(), check_matrix.shape()); - std::vector wrong_size_probs = {0.1, 0.2}; // Should be size 3 + std::vector wrong_size_probs = {0.1, 0.2}; // Should be size 3 - EXPECT_THROW( - cudaq::qec::dem_sampling(check_matrix, 10, wrong_size_probs), - std::invalid_argument); + EXPECT_THROW(cudaq::qec::dem_sampling(check_matrix, 10, wrong_size_probs), + std::invalid_argument); } // Test error: non-rank-2 matrix { - cudaqx::tensor bad_matrix({5}); // rank-1 + cudaqx::tensor bad_matrix({5}); // rank-1 std::vector matrix_data = {1, 0, 1, 0, 1}; bad_matrix.copy(matrix_data.data(), bad_matrix.shape());