Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
425dcde
Unify decoder construction inputs
melody-ren Jul 31, 2026
7213c2d
Fix decoder result form at construction
melody-ren Aug 1, 2026
45c26c1
Supply the measurement-to-detector map at decoder construction
melody-ren Aug 3, 2026
bbe9fe6
Resolve decoder models before applying a configuration
melody-ren Aug 3, 2026
b6160cb
Prove the decoding-server acceptance cases
melody-ren Aug 3, 2026
86fc902
Document model data as distinct from decoder parameters
melody-ren Aug 3, 2026
95f18f0
Project a Stim DEM straight to sparse, and drop the provenance string
melody-ren Aug 3, 2026
cece030
Own O- and D-derived allocation at construction
melody-ren Aug 3, 2026
608dfd3
WIP design_walkthrough.md
melody-ren Aug 4, 2026
ed867b4
Name decoder_inputs operations for what they do
melody-ren Aug 4, 2026
40481dc
remove stale info from walkthrough
melody-ren Aug 4, 2026
751e6ed
clarify some points in walkthrough
melody-ren Aug 4, 2026
2b78e58
Drive realtime streaming from the installed layer geometry
melody-ren Aug 5, 2026
6477f27
Fix CI build of the hololink bridge and the decoder Doxygen comment
melody-ren Aug 6, 2026
398733c
Resolve the Sphinx directives for the API this branch changed
melody-ren Aug 6, 2026
9092aa6
Remove the sparse helpers left behind by the O/D setters
melody-ren Aug 6, 2026
01a8ff2
Address review: reject empty leading detector rows, drop unused surface
melody-ren Aug 6, 2026
2a91fe5
Drop the design walkthrough and the unused Python output enum
melody-ren Aug 6, 2026
bae441f
Document which column a merged parallel edge reports
melody-ren Aug 6, 2026
268aa17
Merge remote-tracking branch 'upstream/main' into melodyr/enable-chro…
melody-ren Aug 6, 2026
1124859
Restore main's names where the concept did not change
melody-ren Aug 6, 2026
8bf9b11
Name our construction input decoder_init and restore decoder_inputs
melody-ren Aug 7, 2026
f0e8527
Merge remote-tracking branch 'upstream/main' into melodyr/enable-chro…
melody-ren Aug 7, 2026
4c367ac
Address review feedback
melody-ren Aug 7, 2026
73bba87
Name the sparse DEM builder for what it does
melody-ren Aug 7, 2026
b393c31
Merge remote-tracking branch 'upstream/main' into melodyr/enable-chro…
melody-ren Aug 8, 2026
4b8b980
Document the enums the decoder API now cross-references
melody-ren Aug 8, 2026
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
537 changes: 537 additions & 0 deletions design_walkthrough.md

Large diffs are not rendered by default.

5 changes: 2 additions & 3 deletions docs/sphinx/api/qec/cpp_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,11 @@ Legacy convenience wrappers (delegate to ``cpu::sample_dem``; prefer the
Decoder Interfaces
==================

.. doxygenstruct:: cudaq::qec::decoder_inputs
.. doxygenclass:: cudaq::qec::decoder_inputs
:members:

.. doxygenfunction:: cudaq::qec::d_sparse(const cudaq::M2DSparseMatrix &)

.. doxygentypedef:: cudaq::qec::decoder_init
.. doxygenfunction:: cudaq::qec::d_sparse(const cudaq::qec::sparse_binary_matrix &)

.. doxygenclass:: cudaq::qec::decoder
:members:
Expand Down
9 changes: 5 additions & 4 deletions docs/sphinx/api/qec/cpp_realtime_decoding_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Real-time decoding requires converting matrices to sparse format for efficient d
- :cpp:func:`cudaq::qec::pcm_to_sparse_vec` for converting a dense PCM to a sparse PCM.
- :cpp:func:`cudaq::qec::pcm_from_sparse_vec` for converting a sparse PCM to a dense PCM.
- :cpp:func:`cudaq::qec::d_sparse` for converting an ``M2DSparseMatrix`` (obtained from
a :cpp:struct:`cudaq::qec::decoder_inputs` component) into the ``-1``-terminated sparse
a :cpp:class:`cudaq::qec::decoder_inputs` component) into the ``-1``-terminated sparse
vector a decoder config expects for ``D_sparse``.

**Usage in real-time decoding:**
Expand All @@ -62,8 +62,9 @@ Real-time decoding requires converting matrices to sparse format for efficient d
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);
const auto dem = inputs.materialize_detector_error_model();
config.H_sparse = cudaq::qec::pcm_to_sparse_vec(dem.detector_error_matrix);
config.O_sparse = cudaq::qec::pcm_to_sparse_vec(dem.observables_flips_matrix);
config.D_sparse = cudaq::qec::d_sparse(*inputs.measurement_to_detectors());

See also :ref:`parity_check_matrix_utilities` for additional PCM manipulation functions.
28 changes: 18 additions & 10 deletions docs/sphinx/api/qec/pymatching_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,26 @@
:param 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.
:param params: Heterogeneous map of parameters:
:param O: Observable-flips matrix, ``num_observables x block_size``.
Model data supplied alongside ``H``, not a decoder parameter.
Supplying it also defaults ``merge_strategy`` to
``"independent"``, matching PyMatching's detector-error-model
construction.
:param error_rate_vec: Per-error prior probabilities, one per column of
``H`` (length ``block_size``). Model data, like ``H`` and ``O``:
it describes the noise model rather than tuning the algorithm.
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``.
:param output: The result form this decoder instance produces, fixed at
construction: ``"errors"`` (default) for an error frame of length
``block_size``, or ``"observables"`` for predicted observable
flips. Supplying ``O`` does not by itself change the result form;
ask for the form you want. A decoder constructed for observable
output without an observable model is rejected at construction.
:param params: Heterogeneous map of decoder 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.
19 changes: 13 additions & 6 deletions docs/sphinx/api/qec/python_realtime_decoding_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,17 +82,24 @@ out-of-tree decoder plugins. Use ``cudaq_qec.decoder_param_schema(name)`` to
inspect a decoder's parameters and ``cudaq_qec.registered_decoder_schemas()``
to list all decoders with registered schemas.

For example, the ``pymatching`` decoder accepts ``error_rate_vec``
(per-error prior probabilities in the range ``(0, 0.5]``, length matching
the decoder ``block_size``) and ``merge_strategy`` (one of ``"disallow"``,
``"independent"``, ``"smallest_weight"``, ``"keep_original"``,
``"replace"``):
Model data is not a decoder parameter. ``H_sparse``, ``O_sparse``,
``D_sparse`` and ``error_rate_vec`` describe the model every decoder decodes
against, so they are fields of ``decoder_config`` itself; a decoder's
parameters tune its algorithm. Supplying model data under
``decoder_custom_args`` is rejected as an unknown key.

For example, the ``pymatching`` decoder's only parameter is
``merge_strategy`` (one of ``"disallow"``, ``"independent"``,
``"smallest_weight"``, ``"keep_original"``, ``"replace"``), while its prior
probabilities are model data:

.. code-block:: python

config.type = "pymatching"
# Model data: per-error priors in (0, 0.5], one per column of H.
config.error_rate_vec = [0.1, 0.1, 0.1]
# Decoder parameters: how the algorithm behaves.
config.decoder_custom_args = {
"error_rate_vec": [0.1, 0.1, 0.1],
"merge_strategy": "smallest_weight",
}

Expand Down
21 changes: 13 additions & 8 deletions docs/sphinx/api/qec/sliding_window_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,18 @@
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);
// Priors are model data, so they travel with H rather than in
// the parameter map.
auto inputs = cudaq::qec::decoder_inputs(
cudaq::qec::sparse_binary_matrix(dem.detector_error_matrix),
std::nullopt, dem.error_rates);
auto swdec =
cudaq::qec::get_decoder("sliding_window", inputs, opts);

return 0;
}
Expand All @@ -114,12 +118,13 @@
for C++, so it supports all the methods in those respective classes.

:param H: Parity check matrix (tensor format)
:param params: Heterogeneous map of parameters:
:param error_rate_vec: Per-error prior probabilities, one per column of
``H`` (length ``block_size``), each in the 0-1 range. Model data
supplied alongside ``H``, not a decoder parameter. The decoder
slices it to each window's error columns and passes the slice to
that window's inner decoder as part of its model.
:param params: Heterogeneous map of decoder 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.)
Expand Down
25 changes: 13 additions & 12 deletions docs/sphinx/components/qec/introduction.rst
Original file line number Diff line number Diff line change
Expand Up @@ -632,9 +632,9 @@ To implement a new decoder:
// Decoder-specific members

public:
my_decoder(const qec::sparse_binary_matrix& H,
const heterogeneous_map& params)
: decoder(H) {
my_decoder(qec::decoder_inputs inputs,
const heterogeneous_map& params)
: decoder(std::move(inputs)) {
// Initialize decoder
}

Expand All @@ -651,18 +651,19 @@ To implement a new decoder:
CUDAQ_EXTENSION_CUSTOM_CREATOR_FUNCTION(
my_decoder,
static std::unique_ptr<decoder> create(
const qec::decoder_init& init,
qec::decoder_inputs inputs,
const heterogeneous_map& params) {
return qec::make_pcm_decoder<my_decoder>(init, params);
return qec::make_pcm_decoder<my_decoder>(std::move(inputs), params);
}
)

CUDAQ_EXT_PT_REGISTER_TYPE(my_decoder)

The :code:`make_pcm_decoder` helper dispatches :code:`decoder_init`. It
passes a stored sparse PCM directly to the decoder constructor; when the
variant contains Stim DEM text, it parses the DEM and constructs the sparse
detector matrix before invoking the same constructor.
The :code:`make_pcm_decoder` helper is a transitional adapter for matrix-family
decoders. The factory always receives :code:`decoder_inputs`; the helper passes
that stable input handle to the decoder and supplies legacy constructor
defaults while decoder implementations migrate to reading model data directly
from the owned inputs.

Example: Lookup Table Decoder
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -676,11 +677,12 @@ Here's a simple lookup table decoder for the Steane code:
std::map<std::string, std::size_t> single_qubit_err_signatures;

public:
single_error_lut(const qec::sparse_binary_matrix& H,
single_error_lut(qec::decoder_inputs inputs,
const heterogeneous_map& params)
: decoder(H) {
: decoder(std::move(inputs)) {
// Canonicalize before using each sparse column as an error
// signature so duplicate row indices cancel over GF(2).
const auto& H = get_inputs().detector_error_matrix();
auto H_e2d = H.canonicalize().to_nested_csc();

for (std::size_t qErr = 0; qErr < block_size; qErr++) {
Expand Down Expand Up @@ -1501,4 +1503,3 @@ Additional Noise Models
noise.add_all_qubit_channel(
"x", cudaq::depolarization2(/*probability*/ 0.01),
/*numControls*/ 1);

4 changes: 2 additions & 2 deletions docs/sphinx/examples/qec/cpp/real_time_complete.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
// Save decoder configuration to YAML file
void save_dem(const cudaq::qec::decoder_inputs &inputs,
const std::string &filename) {
const auto &dem = inputs.dem;
const auto dem = inputs.materialize_detector_error_model();
// Create decoder config
cudaq::qec::decoding::config::decoder_config config;
config.id = 0;
Expand All @@ -39,7 +39,7 @@ void save_dem(const cudaq::qec::decoder_inputs &inputs,
config.syndrome_size = dem.num_detectors();
config.H_sparse = cudaq::qec::pcm_to_sparse_vec(dem.detector_error_matrix);
config.O_sparse = cudaq::qec::pcm_to_sparse_vec(dem.observables_flips_matrix);
config.D_sparse = cudaq::qec::d_sparse(inputs.m2d);
config.D_sparse = cudaq::qec::d_sparse(*inputs.measurement_to_detectors());

// Decoder parameters are a plain heterogeneous_map; keys are governed by
// the parameter schema the decoder registered.
Expand Down
Loading
Loading