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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,9 @@ sinter collect \

Sinter efficiently manages the execution of these tasks, and Tesseract is used for decoding. For more usage examples, see the tests in `src/py/tesseract_sinter_compat_test.py`.

The compiled Sinter integration reports low-confidence decoding outcomes as discards. Use
`errors + discards` when calculating a conservative logical failure count.

## Good Starting Points for Tesseract Configurations:
The [Tesseract paper](https://arxiv.org/pdf/2503.10988) recommends two setup for starting your exploration with tesseract:

Expand Down
3 changes: 3 additions & 0 deletions src/py/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,9 @@ print(estimated_dem)
### Sinter Integration
The Tesseract Python interface is compatible with the Sinter framework, which is a powerful tool for large-scale decoding, benchmarking, and error-rate estimation.

The compiled Sinter integration reports low-confidence decoding outcomes as discards. Use
`errors + discards` when calculating a conservative logical failure count.

#### The TesseractSinterDecoder Object
All Sinter examples rely on this utility function to provide the Sinter-compatible Tesseract decoder.
The default decoder dictionary also includes sparsified variants:
Expand Down
58 changes: 56 additions & 2 deletions src/py/tesseract_sinter_compat_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import stim
import tesseract_decoder
from sinter._decoding._decoding import sample_decode
from sinter._decoding._stim_then_decode_sampler import StimThenDecodeSampler
from tesseract_decoder import (TesseractSinterDecoder,
make_tesseract_sinter_decoders_dict)

Expand Down Expand Up @@ -104,7 +105,7 @@ def test_decode_shots_bit_packed():

# Extract the expected predictions from the DEM
expected_predictions = np.zeros(
(num_shots, (dem.num_observables + 7) // 8), dtype=np.uint8
(num_shots, (dem.num_observables + 7) // 8 + 1), dtype=np.uint8
)
expected_predictions[0][0] |= 1 << 0 # Logical observable L0 is flipped

Expand Down Expand Up @@ -149,7 +150,7 @@ def test_decode_shots_bit_packed_multi_shot():
)

expected_predictions = np.zeros(
(num_shots, (dem.num_observables + 7) // 8), dtype=np.uint8
(num_shots, (dem.num_observables + 7) // 8 + 1), dtype=np.uint8
)
# Expected flip for shot 0 is L0
expected_predictions[0][0] |= 1 << 0
Expand All @@ -162,6 +163,59 @@ def test_decode_shots_bit_packed_multi_shot():
assert np.array_equal(predictions, expected_predictions)


def test_decode_shots_bit_packed_marks_low_confidence_shots_for_discard():
dem = stim.DetectorErrorModel("""
error(0.1) D0 L0
detector(0, 0, 0) D0
detector(0, 0, 1) D1
""")
compiled_decoder = TesseractSinterDecoder().compile_decoder_for_dem(dem=dem)
detections = np.array([[0b01], [0b10]], dtype=np.uint8)

predictions = compiled_decoder.decode_shots_bit_packed(
bit_packed_detection_event_data=detections
)

expected_predictions = np.array(
[
[0b1, 0],
[0, 1],
],
dtype=np.uint8,
)
assert np.array_equal(predictions, expected_predictions)


def test_sinter_discards_low_confidence_shots():
circuit = stim.Circuit("""
R 0 1
X_ERROR(1) 0
M 0 1
DETECTOR rec[-2]
DETECTOR rec[-1]
OBSERVABLE_INCLUDE(0) rec[-2]
""")
dem = stim.DetectorErrorModel("""
error(0.1) D1 L0
detector D0
detector D1
""")

sampler = StimThenDecodeSampler(
decoder=TesseractSinterDecoder(),
count_observable_error_combos=False,
count_detection_events=False,
tmp_dir=None,
).compiled_sampler_for_task(
sinter.Task(circuit=circuit, detector_error_model=dem)
)
result = sampler.sample(5)

assert result.shots == 5
assert result.discards == 5
assert result.errors == 0


def test_decode_via_files_sanity_check():
"""
Tests the 'decode_via_files' method by simulating a small circuit and
Expand Down
16 changes: 10 additions & 6 deletions src/tesseract_sinter_compat.pybind.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,11 @@ struct TesseractSinterCompiledDecoder {

const size_t num_shots = bit_packed_detection_event_data.shape(0);
const uint64_t num_observable_bytes = (num_observables + 7) / 8;
const uint64_t num_result_bytes = num_observable_bytes + 1;

// Result buffer to store the predicted observables for all shots.
// Sinter interprets a nonzero trailing byte as a discard flag for the shot.
auto result_array =
py::array_t<uint8_t>({(py::ssize_t)num_shots, (py::ssize_t)num_observable_bytes});
py::array_t<uint8_t>({(py::ssize_t)num_shots, (py::ssize_t)num_result_bytes});
auto result_buffer = result_array.mutable_data();

const uint8_t* detections_data = bit_packed_detection_event_data.data();
Expand All @@ -81,13 +82,14 @@ struct TesseractSinterCompiledDecoder {
std::vector<int> predictions = decoder->decode(detections);

// Store predictions into the output buffer
uint8_t* single_result_buffer = result_buffer + shot * num_observable_bytes;
std::fill(single_result_buffer, single_result_buffer + num_observable_bytes, 0);
uint8_t* single_result_buffer = result_buffer + shot * num_result_bytes;
std::fill(single_result_buffer, single_result_buffer + num_result_bytes, 0);
for (size_t obs_index : predictions) {
if (obs_index >= 0 && obs_index < num_observables) {
single_result_buffer[obs_index / 8] ^= (1 << (obs_index % 8));
}
}
single_result_buffer[num_observable_bytes] = decoder->low_confidence_flag;
}

// Return the result.
Expand Down Expand Up @@ -321,8 +323,10 @@ void pybind_sinter_compat(py::module& root) {
`(num_shots, ceil(num_detectors / 8))`. Each byte contains
8 bits of detection event data. A `1` in bit `k` of byte `j`
indicates that detector `8j + k` fired.
:return: A 2D numpy array of shape `(num_shots, ceil(num_observables / 8))`
containing the predicted observable flips in a bit-packed format.
:return: A 2D numpy array of shape
`(num_shots, ceil(num_observables / 8) + 1)`. The first bytes contain
predicted observable flips in bit-packed format. The final byte is nonzero
when Sinter should discard the shot because decoding had low confidence.
)pbdoc")
.def_readwrite("num_detectors", &TesseractSinterCompiledDecoder::num_detectors,
R"pbdoc(The number of detectors in the decoder's underlying DEM.)pbdoc")
Expand Down