Skip to content

Commit 1b2c61c

Browse files
wip
Signed-off-by: vedika-saravanan <vsaravanan@nvidia.com>
1 parent 9919167 commit 1b2c61c

12 files changed

Lines changed: 228 additions & 86 deletions

File tree

libs/qec/include/cudaq/qec/decoder.h

Lines changed: 107 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@
1212
#include "cuda-qx/core/heterogeneous_map.h"
1313
#include "cuda-qx/core/tensor.h"
1414
#include "sparse_binary_matrix.h"
15+
#include "cudaq/qec/detector_error_model.h"
1516
#include <functional>
1617
#include <future>
1718
#include <memory>
1819
#include <optional>
1920
#include <string>
21+
#include <string_view>
22+
#include <variant>
2023
#include <vector>
2124

2225
namespace cudaq::qec {
@@ -27,6 +30,22 @@ using float_t = CUDAQX_QEC_FLOAT_TYPE;
2730
using float_t = double;
2831
#endif
2932

33+
/// @brief Construction input for a decoder: either an explicit parity-check
34+
/// matrix (\c cudaq::qec::sparse_binary_matrix) or a Stim detector error model string
35+
/// (\c std::string_view).
36+
///
37+
/// Parity-check-matrix-based decoders (LUT, sliding_window, TRT, PyMatching,
38+
/// nv-qldpc, ...) accept either alternative: a DEM string is parsed into a
39+
/// parity-check matrix via \c dem_from_stim_text. Decoders that require the raw
40+
/// DEM (e.g. Chromobius, which needs detector color/basis annotations) require
41+
/// the string alternative and reject a bare matrix.
42+
///
43+
/// @note The string alternative is non-owning. The referenced buffer must stay
44+
/// alive for the duration of the \c get_decoder / \c decoder::get call;
45+
/// decoders parse it during construction and do not retain the view.
46+
using decoder_init =
47+
std::variant<cudaq::qec::sparse_binary_matrix, std::string_view>;
48+
3049
/// @brief Validates that all keys in a heterogeneous map are found in a list of
3150
/// acceptable types
3251
/// @param config The heterogeneous map to validate
@@ -125,8 +144,7 @@ class async_decoder_result {
125144
/// arbitrary constructor parameters that can be unique to each specific
126145
/// decoder.
127146
class decoder
128-
: public cudaqx::extension_point<decoder,
129-
const cudaq::qec::sparse_binary_matrix &,
147+
: public cudaqx::extension_point<decoder, const decoder_init &,
130148
const cudaqx::heterogeneous_map &> {
131149
private:
132150
struct rt_impl;
@@ -175,11 +193,35 @@ class decoder
175193
virtual std::vector<decoder_result>
176194
decode_batch(const std::vector<std::vector<float_t>> &syndrome);
177195

178-
/// @brief This `get` overload supports default values.
196+
/// @brief Construct a registered decoder by name.
197+
/// @param name The registered decoder name.
198+
/// @param init Either a parity-check matrix or a Stim DEM string (see
199+
/// \c decoder_init). The variant is forwarded to the decoder's creator, so
200+
/// parity-check-matrix-based decoders and DEM-native decoders (Chromobius)
201+
/// share a single entry point.
202+
/// @param param_map Optional decoder-specific parameters.
179203
static std::unique_ptr<decoder>
180-
get(const std::string &name, const cudaq::qec::sparse_binary_matrix &H,
204+
get(const std::string &name, const decoder_init &init,
181205
const cudaqx::heterogeneous_map &param_map = cudaqx::heterogeneous_map());
182206

207+
static std::unique_ptr<decoder>
208+
get(const std::string &name, const cudaq::qec::sparse_binary_matrix &H,
209+
const cudaqx::heterogeneous_map &param_map = cudaqx::heterogeneous_map()) {
210+
return get(name, decoder_init{H}, param_map);
211+
}
212+
213+
static std::unique_ptr<decoder>
214+
get(const std::string &name, const cudaqx::tensor<uint8_t> &H,
215+
const cudaqx::heterogeneous_map &param_map = cudaqx::heterogeneous_map()) {
216+
return get(name, cudaq::qec::sparse_binary_matrix(H), param_map);
217+
}
218+
219+
static std::unique_ptr<decoder>
220+
get(const std::string &name, std::string_view stim_dem_text,
221+
const cudaqx::heterogeneous_map &param_map = cudaqx::heterogeneous_map()) {
222+
return get(name, decoder_init{stim_dem_text}, param_map);
223+
}
224+
183225
std::size_t get_block_size() { return block_size; }
184226
std::size_t get_syndrome_size() { return syndrome_size; }
185227

@@ -428,10 +470,26 @@ inline void convert_vec_hard_to_soft(const std::vector<std::vector<t_hard>> &in,
428470
}
429471

430472
std::unique_ptr<decoder>
431-
get_decoder(const std::string &name, const cudaq::qec::sparse_binary_matrix &H,
473+
get_decoder(const std::string &name, const decoder_init &init,
432474
const cudaqx::heterogeneous_map options = {});
433475

434-
struct detector_error_model;
476+
inline std::unique_ptr<decoder>
477+
get_decoder(const std::string &name, const cudaq::qec::sparse_binary_matrix &H,
478+
const cudaqx::heterogeneous_map options = {}) {
479+
return get_decoder(name, decoder_init{H}, options);
480+
}
481+
482+
inline std::unique_ptr<decoder>
483+
get_decoder(const std::string &name, const cudaqx::tensor<uint8_t> &H,
484+
const cudaqx::heterogeneous_map options = {}) {
485+
return get_decoder(name, cudaq::qec::sparse_binary_matrix(H), options);
486+
}
487+
488+
inline std::unique_ptr<decoder>
489+
get_decoder(const std::string &name, std::string_view stim_dem_text,
490+
const cudaqx::heterogeneous_map options = {}) {
491+
return get_decoder(name, decoder_init{stim_dem_text}, options);
492+
}
435493

436494
/// @brief DEM-derived defaults; pointers alias into the source `dem`.
437495
struct dem_default_values {
@@ -440,26 +498,55 @@ struct dem_default_values {
440498
};
441499

442500
/// @brief Return DEM defaults for any key not already supplied by the user.
443-
/// Shared by `get_decoder_from_stim_dem` and its Python binding.
501+
/// Shared by `make_pcm_decoder` and the Python binding.
444502
dem_default_values dem_defaults_for_missing_keys(
445503
const std::function<bool(const std::string &)> &contains_user_key,
446504
const detector_error_model &dem);
447505

448-
/// @brief Construct a decoder by name from a Stim detector error model text.
506+
/// @brief Extract the Stim DEM text from a \c decoder_init, throwing if it holds
507+
/// a parity-check matrix instead. Use this in the create() function of decoders
508+
/// that require a raw DEM (e.g. Chromobius), which cannot be reconstructed from
509+
/// a bare parity-check matrix.
510+
std::string_view require_dem_text(const decoder_init &init);
511+
512+
/// @brief Build a parity-check-matrix-based decoder from a \c decoder_init.
513+
///
514+
/// If \p init holds a sparse matrix, it is used directly as the parity-check matrix.
515+
/// If it holds a Stim DEM string, it is parsed via \c dem_from_stim_text and the
516+
/// derived observables (`"O"`) and per-error rates (`"error_rate_vec"`) are
517+
/// injected into \p params unless the user already supplied them (user values
518+
/// win). This is the shared implementation behind the create() function of every
519+
/// parity-check-matrix-based decoder, giving them DEM-string support for free.
449520
///
450-
/// Thin wrapper over \c dem_from_stim_text: parses the DEM and forwards to
451-
/// the existing H-based \c decoder::get after injecting two derived entries
452-
/// into \p options if they are not already present:
453-
/// - `"O"` : `cudaqx::tensor<uint8_t>` observables_flips_matrix
454-
/// - `"error_rate_vec"` : `std::vector<double>` per-error probabilities
455-
/// User-supplied values for either key win over the DEM-derived ones.
521+
/// @note The DEM parse is lossy: detector annotations, decomposition
522+
/// separators, and `error_ids` are dropped. Sufficient for matching-style /
523+
/// parity-check-matrix decoders (LUT, NV, sliding_window, TRT, PyMatching).
524+
/// Decoders that need full DEM metadata (e.g. Chromobius detector color/basis)
525+
/// must consume the string directly via \c require_dem_text.
526+
template <typename DecoderT>
527+
std::unique_ptr<decoder>
528+
make_pcm_decoder(const decoder_init &init,
529+
const cudaqx::heterogeneous_map &params) {
530+
if (const auto *H = std::get_if<cudaq::qec::sparse_binary_matrix>(&init))
531+
return std::make_unique<DecoderT>(*H, params);
532+
533+
const auto dem =
534+
dem_from_stim_text(std::string(std::get<std::string_view>(init)));
535+
cudaqx::heterogeneous_map merged = params;
536+
const auto defaults = dem_defaults_for_missing_keys(
537+
[&](const std::string &key) { return merged.contains(key); }, dem);
538+
if (defaults.O)
539+
merged.insert("O", *defaults.O);
540+
if (defaults.error_rate_vec)
541+
merged.insert("error_rate_vec", *defaults.error_rate_vec);
542+
return std::make_unique<DecoderT>(
543+
cudaq::qec::sparse_binary_matrix(dem.detector_error_matrix), merged);
544+
}
545+
546+
/// @brief Construct a decoder by name from a Stim detector error model string.
456547
///
457-
/// @note Lossy: detector annotations, decomposition separators, and
458-
/// `error_ids` are dropped. Sufficient for matching-style / H-based
459-
/// decoders (LUT, NV, sliding_window, TRT, PyMatching). Decoders that
460-
/// need full DEM metadata (e.g. Chromobius detector color/basis) require
461-
/// the planned \c detector_coords extension on
462-
/// \c cudaq::qec::detector_error_model; tracked as a follow-up.
548+
/// @deprecated Prefer \c get_decoder, which now accepts a Stim DEM string
549+
/// directly via \c decoder_init. Retained as a thin convenience alias.
463550
std::unique_ptr<decoder>
464551
get_decoder_from_stim_dem(const std::string &name,
465552
const std::string &stim_dem_text,

libs/qec/lib/decoder.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
INSTANTIATE_REGISTRY(cudaq::qec::decoder,
2121
const cudaq::qec::sparse_binary_matrix &)
2222
INSTANTIATE_REGISTRY(cudaq::qec::decoder,
23-
const cudaq::qec::sparse_binary_matrix &,
23+
const cudaq::qec::decoder_init &,
2424
const cudaqx::heterogeneous_map &)
2525

2626
// Include decoder implementations AFTER registry instantiation
@@ -131,7 +131,7 @@ decoder::decode_async(const std::vector<float_t> &syndrome) {
131131
}
132132

133133
std::unique_ptr<decoder>
134-
decoder::get(const std::string &name, const cudaq::qec::sparse_binary_matrix &H,
134+
decoder::get(const std::string &name, const decoder_init &init,
135135
const cudaqx::heterogeneous_map &param_map) {
136136
auto [mutex, registry] = get_registry();
137137
std::lock_guard<std::recursive_mutex> lock(mutex);
@@ -141,7 +141,7 @@ decoder::get(const std::string &name, const cudaq::qec::sparse_binary_matrix &H,
141141
"invalid decoder requested: " + name +
142142
". Run with CUDAQ_LOG_LEVEL=info (environment variable) to see "
143143
"additional plugin diagnostics at startup.");
144-
return iter->second(H, param_map);
144+
return iter->second(init, param_map);
145145
}
146146

147147
static uint32_t calculate_num_msyn_per_decode(
@@ -480,9 +480,9 @@ void decoder::reset_decoder() {
480480
}
481481

482482
std::unique_ptr<decoder> get_decoder(const std::string &name,
483-
const cudaq::qec::sparse_binary_matrix &H,
483+
const decoder_init &init,
484484
const cudaqx::heterogeneous_map options) {
485-
return decoder::get(name, H, options);
485+
return decoder::get(name, init, options);
486486
}
487487

488488
// Constructor function for auto-loading plugins

libs/qec/lib/decoder_stim_dem.cpp

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,28 +24,24 @@ dem_default_values dem_defaults_for_missing_keys(
2424
return out;
2525
}
2626

27+
std::string_view require_dem_text(const decoder_init &init) {
28+
if (const auto *dem_text = std::get_if<std::string_view>(&init))
29+
return *dem_text;
30+
throw std::runtime_error(
31+
"This decoder requires a Stim detector error model string; a "
32+
"parity-check matrix cannot be used to reconstruct the detector "
33+
"annotations it needs.");
34+
}
35+
2736
std::unique_ptr<decoder>
2837
get_decoder_from_stim_dem(const std::string &name,
2938
const std::string &stim_dem_text,
3039
const cudaqx::heterogeneous_map &options) {
31-
if (!decoder::is_registered(name))
32-
throw std::runtime_error(
33-
"get_decoder_from_stim_dem: decoder \"" + name +
34-
"\" is not registered. Run with CUDAQ_LOG_LEVEL=info to see plugin "
35-
"diagnostics at startup.");
36-
37-
auto dem = dem_from_stim_text(stim_dem_text);
38-
39-
cudaqx::heterogeneous_map merged = options;
40-
// Keep in sync with the Python binding in py_decoder.cpp.
41-
auto defaults = dem_defaults_for_missing_keys(
42-
[&](const std::string &key) { return merged.contains(key); }, dem);
43-
if (defaults.O)
44-
merged.insert("O", *defaults.O);
45-
if (defaults.error_rate_vec)
46-
merged.insert("error_rate_vec", *defaults.error_rate_vec);
47-
48-
return decoder::get(name, dem.detector_error_matrix, merged);
40+
// Retained for backward compatibility: get_decoder now accepts a Stim DEM
41+
// string directly via decoder_init. The string_view aliases stim_dem_text,
42+
// which outlives this call.
43+
return get_decoder(name, decoder_init{std::string_view{stim_dem_text}},
44+
options);
4945
}
5046

5147
} // namespace cudaq::qec

libs/qec/lib/decoders/lut.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,9 @@ class multi_error_lut : public decoder {
228228

229229
CUDAQ_EXTENSION_CUSTOM_CREATOR_FUNCTION(
230230
multi_error_lut, static std::unique_ptr<decoder> create(
231-
const cudaq::qec::sparse_binary_matrix &H,
231+
const cudaq::qec::decoder_init &init,
232232
const cudaqx::heterogeneous_map &params) {
233-
return std::make_unique<multi_error_lut>(H, params);
233+
return cudaq::qec::make_pcm_decoder<multi_error_lut>(init, params);
234234
})
235235
};
236236

@@ -246,9 +246,9 @@ class single_error_lut : public multi_error_lut {
246246

247247
CUDAQ_EXTENSION_CUSTOM_CREATOR_FUNCTION(
248248
single_error_lut, static std::unique_ptr<decoder> create(
249-
const cudaq::qec::sparse_binary_matrix &H,
249+
const cudaq::qec::decoder_init &init,
250250
const cudaqx::heterogeneous_map &params) {
251-
return std::make_unique<single_error_lut>(H, params);
251+
return cudaq::qec::make_pcm_decoder<single_error_lut>(init, params);
252252
})
253253
};
254254

libs/qec/lib/decoders/plugins/example/single_error_lut_example.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,10 @@ class single_error_lut_example : public decoder {
7777

7878
CUDAQ_EXTENSION_CUSTOM_CREATOR_FUNCTION(
7979
single_error_lut_example, static std::unique_ptr<decoder> create(
80-
const cudaq::qec::sparse_binary_matrix &H,
80+
const cudaq::qec::decoder_init &init,
8181
const cudaqx::heterogeneous_map &params) {
82-
return std::make_unique<single_error_lut_example>(H, params);
82+
return cudaq::qec::make_pcm_decoder<single_error_lut_example>(init,
83+
params);
8384
})
8485
};
8586

libs/qec/lib/decoders/plugins/pymatching/pymatching.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,9 +247,9 @@ class pymatching : public decoder {
247247

248248
CUDAQ_EXTENSION_CUSTOM_CREATOR_FUNCTION(
249249
pymatching, static std::unique_ptr<decoder> create(
250-
const cudaq::qec::sparse_binary_matrix &H,
250+
const cudaq::qec::decoder_init &init,
251251
const cudaqx::heterogeneous_map &params) {
252-
return std::make_unique<pymatching>(H, params);
252+
return cudaq::qec::make_pcm_decoder<pymatching>(init, params);
253253
})
254254
};
255255

libs/qec/lib/decoders/plugins/trt_decoder/trt_decoder.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -432,9 +432,9 @@ class trt_decoder : public decoder {
432432

433433
CUDAQ_EXTENSION_CUSTOM_CREATOR_FUNCTION(
434434
trt_decoder, static std::unique_ptr<decoder> create(
435-
const cudaq::qec::sparse_binary_matrix &H,
435+
const cudaq::qec::decoder_init &init,
436436
const cudaqx::heterogeneous_map &params) {
437-
return std::make_unique<trt_decoder>(H, params);
437+
return cudaq::qec::make_pcm_decoder<trt_decoder>(init, params);
438438
})
439439

440440
private:

libs/qec/lib/decoders/sliding_window.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,9 @@ class sliding_window : public decoder {
142142
// Plugin registration macros
143143
CUDAQ_EXTENSION_CUSTOM_CREATOR_FUNCTION(
144144
sliding_window, static std::unique_ptr<decoder> create(
145-
const cudaq::qec::sparse_binary_matrix &H,
145+
const cudaq::qec::decoder_init &init,
146146
const cudaqx::heterogeneous_map &params) {
147-
return std::make_unique<sliding_window>(H, params);
147+
return cudaq::qec::make_pcm_decoder<sliding_window>(init, params);
148148
})
149149
};
150150

0 commit comments

Comments
 (0)