Skip to content
Draft
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,8 @@ tesseract_config = tesseract.TesseractConfig(
```
`DetIndex` is the default detector ordering. You can also pass `DetBFS` or `DetCoordinate`
explicitly.
Detector orders are complete detector-ID permutations in traversal order:
`order[position] = detector_id`.
These values balance decoding speed and accuracy across the benchmarks reported in the paper and can be adjusted for specific use cases.

The Sinter decoder dictionary also provides sparsified variants:
Expand Down
25 changes: 23 additions & 2 deletions src/common.cc
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ std::string vector_to_string(const std::vector<int>& vec) {
return ss.str();
}

void preserve_dem_index_spaces(stim::DetectorErrorModel& dem, size_t num_detectors,
size_t num_observables) {
if (dem.count_detectors() < num_detectors) {
const std::vector<double> no_coordinates;
dem.append_detector_instruction(
no_coordinates, stim::DemTarget::relative_detector_id(num_detectors - 1), /*tag=*/"");
}
if (dem.count_observables() < num_observables) {
dem.append_logical_observable_instruction(stim::DemTarget::observable_id(num_observables - 1),
/*tag=*/"");
}
}

} // namespace

namespace tesseract_decoder {
Expand Down Expand Up @@ -138,6 +151,9 @@ stim::DetectorErrorModel common::flatten(const stim::DetectorErrorModel& dem) {

stim::DetectorErrorModel common::merge_indistinguishable_errors(
const stim::DetectorErrorModel& dem, std::vector<size_t>& error_index_map) {
const stim::DetectorErrorModel flat_dem = flatten(dem);
const size_t num_detectors = flat_dem.count_detectors();
const size_t num_observables = flat_dem.count_observables();
stim::DetectorErrorModel out_dem;

error_index_map.clear();
Expand All @@ -146,7 +162,7 @@ stim::DetectorErrorModel common::merge_indistinguishable_errors(
std::unordered_map<Symptom, size_t, Symptom::hash> merged_index_by_symptom;
std::vector<Error> merged_errors;

for (const stim::DemInstruction& instruction : flatten(dem).instructions) {
for (const stim::DemInstruction& instruction : flat_dem.instructions) {
switch (instruction.type) {
case stim::DemInstructionType::DEM_ERROR: {
Error error(instruction);
Expand Down Expand Up @@ -186,15 +202,19 @@ stim::DetectorErrorModel common::merge_indistinguishable_errors(
error.symptom.as_dem_instruction_targets(),
/*tag=*/"");
}
preserve_dem_index_spaces(out_dem, num_detectors, num_observables);
return out_dem;
}

stim::DetectorErrorModel common::remove_zero_probability_errors(
const stim::DetectorErrorModel& dem, std::vector<size_t>& error_index_map) {
const stim::DetectorErrorModel flat_dem = flatten(dem);
const size_t num_detectors = flat_dem.count_detectors();
const size_t num_observables = flat_dem.count_observables();
stim::DetectorErrorModel out_dem;
error_index_map.clear();
size_t output_error_index = 0;
for (const stim::DemInstruction& instruction : flatten(dem).instructions) {
for (const stim::DemInstruction& instruction : flat_dem.instructions) {
switch (instruction.type) {
case stim::DemInstructionType::DEM_ERROR:
if (instruction.arg_data[0] > 0) {
Expand All @@ -214,6 +234,7 @@ stim::DetectorErrorModel common::remove_zero_probability_errors(
throw std::invalid_argument("Unrecognized instruction type: " + instruction.str());
}
}
preserve_dem_index_spaces(out_dem, num_detectors, num_observables);
return out_dem;
}

Expand Down
4 changes: 2 additions & 2 deletions src/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,14 @@ bool is_flat(const stim::DetectorErrorModel& dem);
stim::DetectorErrorModel flatten(const stim::DetectorErrorModel& dem);

// Makes a new (flattened) dem where identical error mechanisms have been
// merged.
// merged, while preserving detector and observable counts.
// `error_index_map[old_error_index]` gives the corresponding merged DEM error
// index in the returned DEM.
stim::DetectorErrorModel merge_indistinguishable_errors(const stim::DetectorErrorModel& dem,
std::vector<size_t>& error_index_map);

// Returns a copy of the given error model with any zero-probability DEM_ERROR
// instructions removed.
// instructions removed, while preserving detector and observable counts.
// `error_index_map[old_error_index]` gives the corresponding retained DEM error
// index in the returned DEM, or `std::numeric_limits<size_t>::max()` if the
// error was removed.
Expand Down
25 changes: 25 additions & 0 deletions src/common.test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

#include "common.h"

#include <limits>

#include "gtest/gtest.h"
#include "stim.h"

Expand Down Expand Up @@ -112,6 +114,29 @@ TEST(common, RemoveZeroProbabilityErrors) {
EXPECT_NEAR(flat.instructions[1].arg_data[0], 0.2, 1e-9);
}

TEST(common, RemoveZeroProbabilityErrorsPreservesIndexSpaces) {
stim::DetectorErrorModel dem("error(0) D2 L1");

std::vector<size_t> error_index_map;
stim::DetectorErrorModel cleaned = common::remove_zero_probability_errors(dem, error_index_map);

EXPECT_EQ(cleaned.count_errors(), 0);
EXPECT_EQ(cleaned.count_detectors(), 3);
EXPECT_EQ(cleaned.count_observables(), 2);
EXPECT_EQ(error_index_map, (std::vector<size_t>{std::numeric_limits<size_t>::max()}));
}

TEST(common, MergeIndistinguishableErrorsPreservesIndexSpaces) {
stim::DetectorErrorModel dem("error(0.1) D2 D2 L1 L1");

std::vector<size_t> error_index_map;
stim::DetectorErrorModel merged = common::merge_indistinguishable_errors(dem, error_index_map);

EXPECT_EQ(merged.count_errors(), 1);
EXPECT_EQ(merged.count_detectors(), 3);
EXPECT_EQ(merged.count_observables(), 2);
}

// Helper function to compare the two methods.
void assert_merged_probabilities_are_equal(double p1, double p2) {
// Merge probabilities using the exclusive OR formula.
Expand Down
4 changes: 2 additions & 2 deletions src/py/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Explanation of configuration arguments:
* `verbose` - A boolean flag that, when `True`, enables verbose logging. This is useful for debugging and understanding the decoder's internal behavior, as it will print information about the search process.
* `merge_errors` - A boolean flag that, when `True`, merges error channels with identical syndrome patterns before decoding. This is enabled by default.
* `pqlimit` - An integer that sets a limit on the number of nodes in the priority queue. This can be used to constrain the memory usage of the decoder. The default value is `200000`.
* `det_orders` - A list of lists of integers, where each inner list represents an ordering of the detectors. This is used for "ensemble reordering," an optimization that tries different detector orderings to improve the search's convergence. The default is an empty list, meaning a single, fixed ordering is used.
* `det_orders` - A list of complete detector-ID permutations in traversal order: `order[position] = detector_id`. This is used for "ensemble reordering," an optimization that tries different detector orderings to improve the search's convergence. The default is an empty list, meaning a single, fixed ordering is used.
* `det_penalty` - A floating-point value that adds a cost for each residual detection event. This encourages the decoder to prioritize paths that resolve more detection events, steering the search towards more complete solutions. The default value is `0.0`, meaning no penalty is applied.
* `create_visualization` - A boolean flag that enables decoder visualization output when set to `True`. The default value is `False`.
* `sparsify_errors` - Enables per-shot sparse error activation. When enabled, all errors up to `sparsify_base_degree` are always active, and selected higher-degree errors are reactivated per shot.
Expand Down Expand Up @@ -286,7 +286,7 @@ The `tesseract_decoder.utils` module provides various helper functions used thro

#### Functions
* `utils.get_detector_coords(dem: stim.DetectorErrorModel) -> list[list[float]]`
* Extracts 3D coordinates for each detector from a `stim.DetectorErrorModel`.
* Extracts arbitrary-dimensional coordinates indexed by detector ID from a `stim.DetectorErrorModel`. Missing detector coordinates are returned as empty lists.

**Example Usage**:

Expand Down
25 changes: 25 additions & 0 deletions src/py/tesseract_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,31 @@
""")


@pytest.mark.parametrize(
"detector_order, message",
[
([0], "has size"),
([0, 0], "more than once"),
([0, 2], "out-of-range detector ID"),
],
)
def test_detector_orders_must_be_permutations(detector_order, message):
config = tesseract_decoder.tesseract.TesseractConfig(
_DETECTOR_ERROR_MODEL, det_orders=[detector_order]
)
with pytest.raises(ValueError, match=message):
config.compile_decoder()


def test_selected_detector_order_index_must_be_in_range():
config = tesseract_decoder.tesseract.TesseractConfig(
_DETECTOR_ERROR_MODEL, det_orders=[[1, 0]]
)
decoder = config.compile_decoder()
with pytest.raises(IndexError, match="Detector order index 1"):
decoder.decode_to_errors(np.zeros(2, dtype=bool), 1, 0)


def test_create_tesseract_config():
config = tesseract_decoder.tesseract.TesseractConfig(_DETECTOR_ERROR_MODEL)
assert config.dem == _DETECTOR_ERROR_MODEL
Expand Down
84 changes: 78 additions & 6 deletions src/py/utils_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,20 @@ def test_build_detector_graph():
]


def test_build_detector_graph_uses_positive_parity_reduced_symptoms():
dem = stim.DetectorErrorModel("""
error(0) D0 D1
error(0.1) D0 D0 D1
error(0.2) D1 D2 D3
""")
assert tesseract_decoder.utils.build_detector_graph(dem) == [
[],
[2, 3],
[1, 3],
[1, 2],
]


def test_build_det_orders_default_index():
res = tesseract_decoder.utils.build_det_orders(
_DETECTOR_ERROR_MODEL_10, num_det_orders=1, seed=0
Expand All @@ -55,21 +69,79 @@ def test_build_det_orders_default_index():


def test_build_det_orders_bfs():
path_dem = stim.DetectorErrorModel("""
error(0.1) D0 D4
error(0.1) D4 D1
error(0.1) D1 D3
error(0.1) D3 D2
""")
graph = tesseract_decoder.utils.build_detector_graph(path_dem)
orders = tesseract_decoder.utils.build_det_orders(
path_dem,
num_det_orders=16,
method=tesseract_decoder.utils.DetOrder.DetBFS,
seed=0,
)
for order in orders:
assert sorted(order) == list(range(5))
distance = [None] * len(graph)
distance[order[0]] = 0
frontier = [order[0]]
for detector in frontier:
for neighbor in graph[detector]:
if distance[neighbor] is None:
distance[neighbor] = distance[detector] + 1
frontier.append(neighbor)
assert [distance[detector] for detector in order] == sorted(
distance[detector] for detector in order
)


def test_build_det_orders_bfs_empty_dem():
assert tesseract_decoder.utils.build_det_orders(
_DETECTOR_ERROR_MODEL,
num_det_orders=1,
stim.DetectorErrorModel(),
num_det_orders=3,
method=tesseract_decoder.utils.DetOrder.DetBFS,
seed=0,
) == [[0, 1]]
) == [[], [], []]


def test_build_det_orders_coordinate():
assert tesseract_decoder.utils.build_det_orders(
_DETECTOR_ERROR_MODEL,
dem = stim.DetectorErrorModel("""
detector(2) D3
detector(0) D0
detector(3) D1
detector(1) D2
""")
order = tesseract_decoder.utils.build_det_orders(
dem,
num_det_orders=1,
method=tesseract_decoder.utils.DetOrder.DetCoordinate,
seed=0,
)[0]
assert order in ([0, 2, 3, 1], [1, 3, 2, 0])


def test_detector_coords_are_keyed_and_allow_missing_or_short_coordinates():
dem = stim.DetectorErrorModel("""
detector(2, 20) D2
detector(0) D0
detector(99) D2
error(0.1) D3
""")
assert tesseract_decoder.utils.get_detector_coords(dem) == [
[0],
[],
[2, 20],
[],
]
order = tesseract_decoder.utils.build_det_orders(
dem,
num_det_orders=1,
method=tesseract_decoder.utils.DetOrder.DetCoordinate,
seed=0,
) == [[0, 1]]
)[0]
assert order[2:] == [1, 3]


def test_build_det_orders_index():
Expand Down
Loading
Loading