Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
78e5ec8
Add visualization library to CMake build
noajshu Aug 11, 2025
482cb81
Merge pull request #36 from noajshu/codex/update-cmakelists-to-includ…
noajshu Aug 11, 2025
106ce02
Merge branch 'main' into main
noajshu Aug 12, 2025
b4c9d08
Merge remote-tracking branch 'quantum/main'
noajshu Aug 12, 2025
5c211bc
Fix CMake Python module placement and add agent instructions
noajshu Aug 13, 2025
d93e31d
Merge pull request #37 from noajshu/codex/add-agents.md-documentation…
noajshu Aug 13, 2025
7c10268
Allow decode_to_errors to accept bitstring
noajshu Aug 20, 2025
8a27808
Merge pull request #38 from noajshu/codex/update-decode_to_errors-to-…
noajshu Aug 20, 2025
1e52d57
Merge branch 'quantumlib:main' into main
noajshu Aug 20, 2025
c34dd80
clang-format
noajshu Aug 20, 2025
24c0d69
Update src/tesseract.pybind.h
noajshu Aug 20, 2025
96849b6
remove stringstream
noajshu Aug 20, 2025
3d486b0
more fixes
noajshu Aug 20, 2025
20e506d
Merge remote-tracking branch 'quantum'
noajshu Aug 29, 2025
61867e2
Handle explicit DetIndex case
noajshu Aug 29, 2025
a3e2663
Merge pull request #39 from noajshu/codex/create-detorder-enum-and-up…
noajshu Aug 29, 2025
7309377
Merge remote-tracking branch 'quantum'
noajshu Aug 29, 2025
454521f
expand det index test
noajshu Aug 29, 2025
18b12cd
Merge pull request #40 from noajshu/codex/refactor-test_build_det_ord…
noajshu Aug 29, 2025
41f7c3e
update beam climbing for when det orders > beam+1
noajshu Aug 29, 2025
e9a0774
Merge remote-tracking branch 'origin'
noajshu Aug 29, 2025
bead3cf
Merge remote-tracking branch 'quantum'
noajshu Aug 30, 2025
8f269f7
Refactor: Remove --at-most-two-errors-per-detector feature
noajshu Aug 31, 2025
628f136
undo changes to tutorial
noajshu Aug 31, 2025
41d700c
Merge branch 'main' into remove-at-most-two-errors-per-detector
noajshu Aug 31, 2025
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
4 changes: 2 additions & 2 deletions src/py/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ The `tesseract_decoder.tesseract` module provides the Tesseract decoder, which e

#### Class `tesseract.TesseractConfig`
This class holds the configuration parameters that control the behavior of the Tesseract decoder.
* `TesseractConfig(dem: stim.DetectorErrorModel, det_beam: int = INF_DET_BEAM, beam_climbing: bool = False, no_revisit_dets: bool = False, at_most_two_errors_per_detector: bool = False, verbose: bool = False, pqlimit: int = sys.maxsize, det_orders: list[list[int]] = [], det_penalty: float = 0.0)`
* `TesseractConfig(dem: stim.DetectorErrorModel, det_beam: int = INF_DET_BEAM, beam_climbing: bool = False, no_revisit_dets: bool = False, verbose: bool = False, pqlimit: int = sys.maxsize, det_orders: list[list[int]] = [], det_penalty: float = 0.0)`
* `__str__()`

Explanation of configuration arguments:
* `dem`: This is a required argument that takes a `stim.DetectorErrorModel`. It provides the logical structure of the quantum error-correcting code, including the detectors and the relationships between them. This model is essential for the decoder to understand the syndrome and potential error locations.
* `det_beam` - This integer value represents the beam search cutoff. It specifies a threshold for the number of "residual detection events" a node can have before it is pruned from the search. A lower `det_beam` value makes the search more aggressive, potentially sacrificing accuracy for speed. The default value `INF_DET_BEAM` means no beam cutoff is applied.
* `beam_climbing` - A boolean flag that, when set to `True`, enables a heuristic called "beam climbing." This optimization causes the decoder to try different `det_beam` values (up to a maximum) to find a good decoding path. This can improve the decoder's chance of finding the most likely error, even with an initial narrow beam search.
* `no_revisit_dets` - A boolean flag that, when `True`, activates a heuristic to prevent the decoder from revisiting nodes that have the same set of leftover detection events as a node it has already visited. This can help to reduce search redundancy and improve decoding speed.
* `at_most_two_errors_per_detector` - This boolean flag is a specific constraint that assumes at most two errors can affect a given detector. This can be a useful optimization for certain types of codes and noise models, as it prunes the search space by making a stronger assumption about the error distribution.

* `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.
* `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 `sys.maxsize`, which means the size is effectively unbounded.
* `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.
Expand Down
10 changes: 5 additions & 5 deletions src/py/tesseract_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def test_create_tesseract_config():
assert config.dem == _DETECTOR_ERROR_MODEL
assert config.det_beam == 5
assert config.no_revisit_dets is True
assert config.at_most_two_errors_per_detector is False

assert config.verbose is False
assert config.merge_errors is True
assert config.pqlimit == 200000
Expand All @@ -71,7 +71,7 @@ def test_create_tesseract_config_with_dem():
assert config.dem == _DETECTOR_ERROR_MODEL
assert config.det_beam == 5
assert config.no_revisit_dets is True
assert config.at_most_two_errors_per_detector is False

assert config.verbose is False
assert config.merge_errors is True
assert config.pqlimit == 200000
Expand All @@ -94,7 +94,7 @@ def test_create_tesseract_config_with_dem_and_custom_args():
assert config.dem == _DETECTOR_ERROR_MODEL
assert config.det_beam == 100
assert config.no_revisit_dets is True
assert config.at_most_two_errors_per_detector is False

assert config.verbose is False
assert config.merge_errors is False
assert config.pqlimit == 200000
Expand Down Expand Up @@ -167,7 +167,7 @@ def test_create_tesseract_config_no_dem():
assert config.dem == stim.DetectorErrorModel()
assert config.det_beam == 5
assert config.no_revisit_dets is True
assert config.at_most_two_errors_per_detector is False

assert config.verbose is False
assert config.merge_errors is True
assert config.pqlimit == 200000
Expand All @@ -184,7 +184,7 @@ def test_create_tesseract_config_no_dem_with_custom_args():
assert config.dem == stim.DetectorErrorModel()
assert config.det_beam == 15
assert config.no_revisit_dets is True
assert config.at_most_two_errors_per_detector is False

assert config.verbose is True
assert config.merge_errors is True
assert config.pqlimit == 200000
Expand Down
31 changes: 2 additions & 29 deletions src/tesseract.cc
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ std::string TesseractConfig::str() {
ss << "dem=DetectorErrorModel_Object" << ", ";
ss << "det_beam=" << config.det_beam << ", ";
ss << "no_revisit_dets=" << config.no_revisit_dets << ", ";
ss << "at_most_two_errors_per_detector=" << config.at_most_two_errors_per_detector << ", ";

ss << "verbose=" << config.verbose << ", ";
ss << "merge_errors=" << config.merge_errors << ", ";
ss << "pqlimit=" << config.pqlimit << ", ";
Expand Down Expand Up @@ -256,16 +256,11 @@ void TesseractDecoder::flip_detectors_and_block_errors(

for (int oei : d2e[min_detector]) {
detector_cost_tuples[oei].error_blocked = 1;
if (!config.at_most_two_errors_per_detector && oei == ei) break;
if (oei == ei) break;
}

for (int d : edets[ei]) {
detectors[d] = !detectors[d];
if (!detectors[d] && config.at_most_two_errors_per_detector) {
for (int oei : d2e[d]) {
detector_cost_tuples[oei].error_blocked = 1;
}
}
}
}
}
Expand Down Expand Up @@ -398,12 +393,6 @@ void TesseractDecoder::decode_to_errors(const std::vector<uint64_t>& detections,
}
}

if (config.at_most_two_errors_per_detector) {
for (int ei : d2e[min_detector]) {
next_detector_cost_tuples[ei].error_blocked = 1;
}
}

size_t prev_ei = std::numeric_limits<size_t>::max();
std::vector<double> detector_cost_cache(num_detectors, -1);

Expand All @@ -415,11 +404,6 @@ void TesseractDecoder::decode_to_errors(const std::vector<uint64_t>& detections,
int fired = detectors[d] ? 1 : -1;
for (int oei : d2e[d]) {
next_detector_cost_tuples[oei].detectors_count += fired;

if (config.at_most_two_errors_per_detector &&
next_detector_cost_tuples[oei].error_blocked == 2) {
next_detector_cost_tuples[oei].error_blocked = 0;
}
}
}
}
Expand All @@ -440,17 +424,6 @@ void TesseractDecoder::decode_to_errors(const std::vector<uint64_t>& detections,
for (int oei : d2e[d]) {
next_detector_cost_tuples[oei].detectors_count += fired;
}

if (!next_detectors[d] && config.at_most_two_errors_per_detector) {
for (int oei : d2e[d]) {
next_detector_cost_tuples[oei].error_blocked =
next_detector_cost_tuples[oei].error_blocked == 1
? 1
: 2; // we store '2' value to indicate an error that was blocked due to the
// '--at-most-two-error-per-detector' heuristic, in order to revert it in
// the next decoding iteration
}
}
}

if (next_num_detectors > max_num_detectors) continue;
Expand Down
2 changes: 1 addition & 1 deletion src/tesseract.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ struct TesseractConfig {
int det_beam = DEFAULT_DET_BEAM;
bool beam_climbing = false;
bool no_revisit_dets = true;
bool at_most_two_errors_per_detector = false;

bool verbose = false;
bool merge_errors = true;
size_t pqlimit = DEFAULT_PQLIMIT;
Expand Down
33 changes: 11 additions & 22 deletions src/tesseract.pybind.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,33 +35,30 @@ std::unique_ptr<TesseractDecoder> _compile_tesseract_decoder_helper(const Tesser

TesseractConfig tesseract_config_maker_no_dem(
int det_beam = INF_DET_BEAM, bool beam_climbing = false, bool no_revisit_dets = false,
bool at_most_two_errors_per_detector = false, bool verbose = false, bool merge_errors = true,
bool verbose = false, bool merge_errors = true,
size_t pqlimit = std::numeric_limits<size_t>::max(),
std::vector<std::vector<size_t>> det_orders = std::vector<std::vector<size_t>>(),
double det_penalty = 0.0, bool create_visualization = false) {
stim::DetectorErrorModel empty_dem;
if (det_orders.empty()) {
det_orders = build_det_orders(empty_dem, 20, DetOrder::DetBFS, 2384753);
}
return TesseractConfig({empty_dem, det_beam, beam_climbing, no_revisit_dets,
at_most_two_errors_per_detector, verbose, merge_errors, pqlimit,
det_orders, det_penalty, create_visualization});
return TesseractConfig({empty_dem, det_beam, beam_climbing, no_revisit_dets, verbose,
merge_errors, pqlimit, det_orders, det_penalty, create_visualization});
}

TesseractConfig tesseract_config_maker(
py::object dem, int det_beam = INF_DET_BEAM, bool beam_climbing = false,
bool no_revisit_dets = false, bool at_most_two_errors_per_detector = false,
bool verbose = false, bool merge_errors = true,
bool no_revisit_dets = false, bool verbose = false, bool merge_errors = true,
size_t pqlimit = std::numeric_limits<size_t>::max(),
std::vector<std::vector<size_t>> det_orders = std::vector<std::vector<size_t>>(),
double det_penalty = 0.0, bool create_visualization = false) {
stim::DetectorErrorModel input_dem = parse_py_object<stim::DetectorErrorModel>(dem);
if (det_orders.empty()) {
det_orders = build_det_orders(input_dem, 20, DetOrder::DetBFS, 2384753);
}
return TesseractConfig({input_dem, det_beam, beam_climbing, no_revisit_dets,
at_most_two_errors_per_detector, verbose, merge_errors, pqlimit,
det_orders, det_penalty, create_visualization});
return TesseractConfig({input_dem, det_beam, beam_climbing, no_revisit_dets, verbose,
merge_errors, pqlimit, det_orders, det_penalty, create_visualization});
}

}; // namespace
Expand All @@ -83,8 +80,7 @@ void add_tesseract_module(py::module& root) {
)pbdoc")
.def(py::init(&tesseract_config_maker_no_dem), py::arg("det_beam") = 5,
py::arg("beam_climbing") = false, py::arg("no_revisit_dets") = true,
py::arg("at_most_two_errors_per_detector") = false, py::arg("verbose") = false,
py::arg("merge_errors") = true, py::arg("pqlimit") = 200000,
py::arg("verbose") = false, py::arg("merge_errors") = true, py::arg("pqlimit") = 200000,
py::arg("det_orders") = std::vector<std::vector<size_t>>(), py::arg("det_penalty") = 0.0,
py::arg("create_visualization") = false,
R"pbdoc(
Expand All @@ -99,9 +95,7 @@ void add_tesseract_module(py::module& root) {
If True, enables a beam climbing heuristic.
no_revisit_dets : bool, default=False
If True, prevents the decoder from revisiting a syndrome pattern more than once.
at_most_two_errors_per_detector : bool, default=False
If True, an optimization is enabled that assumes at most two errors
are correlated with each detector.

verbose : bool, default=False
If True, enables verbose logging from the decoder.
merge_errors : bool, default=True
Expand All @@ -118,8 +112,7 @@ void add_tesseract_module(py::module& root) {
)pbdoc")
.def(py::init(&tesseract_config_maker), py::arg("dem"), py::arg("det_beam") = 5,
py::arg("beam_climbing") = false, py::arg("no_revisit_dets") = true,
py::arg("at_most_two_errors_per_detector") = false, py::arg("verbose") = false,
py::arg("merge_errors") = true, py::arg("pqlimit") = 200000,
py::arg("verbose") = false, py::arg("merge_errors") = true, py::arg("pqlimit") = 200000,
py::arg("det_orders") = std::vector<std::vector<size_t>>(), py::arg("det_penalty") = 0.0,
py::arg("create_visualization") = false,
R"pbdoc(
Expand All @@ -135,9 +128,7 @@ void add_tesseract_module(py::module& root) {
If True, enables a beam climbing heuristic.
no_revisit_dets : bool, default=False
If True, prevents the decoder from revisiting a syndrome pattern more than once.
at_most_two_errors_per_detector : bool, default=False
If True, an optimization is enabled that assumes at most two errors
are correlated with each detector.

verbose : bool, default=False
If True, enables verbose logging from the decoder.
merge_errors : bool, default=True
Expand All @@ -160,9 +151,7 @@ void add_tesseract_module(py::module& root) {
"Whether to use a beam climbing heuristic.")
.def_readwrite("no_revisit_dets", &TesseractConfig::no_revisit_dets,
"Whether to prevent revisiting same syndrome patterns during decoding.")
.def_readwrite("at_most_two_errors_per_detector",
&TesseractConfig::at_most_two_errors_per_detector,
"Whether to assume at most two errors per detector for optimization.")

.def_readwrite("verbose", &TesseractConfig::verbose,
"If True, the decoder will print verbose output.")
.def_readwrite("merge_errors", &TesseractConfig::merge_errors,
Expand Down
46 changes: 21 additions & 25 deletions src/tesseract_main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ struct Args {
double det_penalty = 0;
bool beam_climbing = false;
bool no_revisit_dets = false;
bool at_most_two_errors_per_detector = false;

size_t pqlimit;

bool verbose = false;
Expand Down Expand Up @@ -289,7 +289,7 @@ struct Args {
config.det_penalty = det_penalty;
config.beam_climbing = beam_climbing;
config.no_revisit_dets = no_revisit_dets;
config.at_most_two_errors_per_detector = at_most_two_errors_per_detector;

config.pqlimit = pqlimit;
config.verbose = verbose;
}
Expand Down Expand Up @@ -445,10 +445,7 @@ int main(int argc, char* argv[]) {
.help("Use no-revisit-dets heuristic")
.flag()
.store_into(args.no_revisit_dets);
program.add_argument("--at-most-two-errors-per-detector")
.help("Use heuristic limitation of at most 2 errors per detector")
.flag()
.store_into(args.at_most_two_errors_per_detector);

program.add_argument("--pqlimit")
.help("Maximum size of the priority queue (default = infinity)")
.metavar("N")
Expand Down Expand Up @@ -594,25 +591,24 @@ int main(int argc, char* argv[]) {

bool print_final_stats = true;
if (!args.stats_out_fname.empty()) {
nlohmann::json stats_json = {
{"circuit_path", args.circuit_path},
{"dem_path", args.dem_path},
{"max_errors", args.max_errors},
{"sample_seed", args.sample_seed},
{"at_most_two_errors_per_detector", args.at_most_two_errors_per_detector},
{"det_beam", args.det_beam},
{"det_penalty", args.det_penalty},
{"beam_climbing", args.beam_climbing},
{"no_revisit_dets", args.no_revisit_dets},
{"pqlimit", args.pqlimit},
{"num_det_orders", args.num_det_orders},
{"det_order_seed", args.det_order_seed},
{"total_time_seconds", total_time_seconds},
{"num_errors", num_errors},
{"num_low_confidence", num_low_confidence},
{"num_shots", shot},
{"num_threads", args.num_threads},
{"sample_num_shots", args.sample_num_shots}};
nlohmann::json stats_json = {{"circuit_path", args.circuit_path},
{"dem_path", args.dem_path},
{"max_errors", args.max_errors},
{"sample_seed", args.sample_seed},

{"det_beam", args.det_beam},
{"det_penalty", args.det_penalty},
{"beam_climbing", args.beam_climbing},
{"no_revisit_dets", args.no_revisit_dets},
{"pqlimit", args.pqlimit},
{"num_det_orders", args.num_det_orders},
{"det_order_seed", args.det_order_seed},
{"total_time_seconds", total_time_seconds},
{"num_errors", num_errors},
{"num_low_confidence", num_low_confidence},
{"num_shots", shot},
{"num_threads", args.num_threads},
{"sample_num_shots", args.sample_num_shots}};

if (args.stats_out_fname == "-") {
std::cout << stats_json << std::endl;
Expand Down
25 changes: 12 additions & 13 deletions src/tesseract_sinter_compat.pybind.h
Original file line number Diff line number Diff line change
Expand Up @@ -292,28 +292,27 @@ void pybind_sinter_compat(py::module& root) {
R"pbdoc(Checks if two TesseractSinterDecoder instances are not equal.)pbdoc")
.def(py::pickle(
[](const TesseractSinterDecoder& self) -> py::tuple { // __getstate__
return py::make_tuple(
std::string(self.config.dem.str()), self.config.det_beam, self.config.beam_climbing,
self.config.no_revisit_dets, self.config.at_most_two_errors_per_detector,
self.config.verbose, self.config.merge_errors, self.config.pqlimit,
self.config.det_orders, self.config.det_penalty, self.config.create_visualization);
return py::make_tuple(std::string(self.config.dem.str()), self.config.det_beam,
self.config.beam_climbing, self.config.no_revisit_dets,
self.config.verbose, self.config.merge_errors,
self.config.pqlimit, self.config.det_orders,
self.config.det_penalty, self.config.create_visualization);
},
[](py::tuple t) { // __setstate__
if (t.size() != 11) {
if (t.size() != 10) {
throw std::runtime_error("Invalid state for TesseractSinterDecoder!");
}
TesseractConfig config;
config.dem = stim::DetectorErrorModel(t[0].cast<std::string>());
config.det_beam = t[1].cast<int>();
config.beam_climbing = t[2].cast<bool>();
config.no_revisit_dets = t[3].cast<bool>();
config.at_most_two_errors_per_detector = t[4].cast<bool>();
config.verbose = t[5].cast<bool>();
config.merge_errors = t[6].cast<bool>();
config.pqlimit = t[7].cast<size_t>();
config.det_orders = t[8].cast<std::vector<std::vector<size_t>>>();
config.det_penalty = t[9].cast<double>();
config.create_visualization = t[10].cast<bool>();
config.verbose = t[4].cast<bool>();
config.merge_errors = t[5].cast<bool>();
config.pqlimit = t[6].cast<size_t>();
config.det_orders = t[7].cast<std::vector<std::vector<size_t>>>();
config.det_penalty = t[8].cast<double>();
config.create_visualization = t[9].cast<bool>();
return TesseractSinterDecoder(config);
}));

Expand Down
Loading