From 53c530bd6af2949310efb41dcaf7a35db106e681 Mon Sep 17 00:00:00 2001 From: noajshu Date: Tue, 26 May 2026 21:56:43 +0000 Subject: [PATCH 1/2] feat(python): expose error model sparsification and add sinter decoders Exposes the per-shot error model sparsification functionality (added in PR 254) to the Python module and Sinter compatibility layer. Changes: - Moved the sparsify reactivate limit (M) heuristic calculation from the CLI to `TesseractConfig::get_sparsify_reactivate_limit()` in C++ core. - Added validation for sparsification parameters in decoder initialization. - Simplified CLI sparsification parameter resolution. - Bound sparsification parameters (`sparsify_errors`, `sparsify_base_degree`, `sparsify_max_degree`, `sparsify_reactivate_limit`) and the heuristic resolution method in `TesseractConfig` Python bindings. - Extended `TesseractSinterDecoder` to support sparsification config, maintaining pickling/serialization compatibility for distributed Sinter runs. - Registered new sparsified decoders in Sinter decoders dictionary: `tesseract-long-beam-sparsify3`, `tesseract-long-beam-sparsify2`, `tesseract-short-beam-sparsify3`, and `tesseract-short-beam-sparsify2`. - Added comprehensive Python unit tests for core sparsify config, validation, and Sinter end-to-end decoding. - Fixed pre-existing CMake build failure under strict compilers by upgrading external HiGHS dependency to v1.14.0. TAG=agy CONV=365755ec-6af0-4d9c-a7e2-4a363289b763 --- CMakeLists.txt | 4 +- src/py/tesseract_sinter_compat_test.py | 91 +++++++++++++++++++++++++ src/py/tesseract_test.py | 80 ++++++++++++++++++++++ src/tesseract.cc | 29 ++++++++ src/tesseract.h | 1 + src/tesseract.pybind.h | 47 +++++++++++-- src/tesseract_main.cc | 12 +--- src/tesseract_sinter_compat.pybind.h | 92 +++++++++++++++++++++++--- 8 files changed, 331 insertions(+), 25 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1a0a7ced..b7a4270b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,8 +19,8 @@ FetchContent_MakeAvailable(stim) # HiGHS FetchContent_Declare( highs - URL https://github.com/ERGO-Code/HiGHS/archive/refs/tags/v1.9.0.tar.gz - URL_HASH SHA256=dff575df08d88583c109702c7c5c75ff6e51611e6eacca8b5b3fdfba8ecc2cb4 + URL https://github.com/ERGO-Code/HiGHS/archive/refs/tags/v1.14.0.tar.gz + URL_HASH SHA256=05931e8dd8c8cac514da8297003c31a206a0004d542b7da500810b85c87c20b9 ) FetchContent_MakeAvailable(highs) diff --git a/src/py/tesseract_sinter_compat_test.py b/src/py/tesseract_sinter_compat_test.py index 8a4e15a4..5015f31b 100644 --- a/src/py/tesseract_sinter_compat_test.py +++ b/src/py/tesseract_sinter_compat_test.py @@ -687,5 +687,96 @@ def test_sinter_collect_different_dems(): assert results.json_metadata["d"] == expected_distances[i] +def test_tesseract_sinter_decoder_sparsify_attributes(): + decoder = TesseractSinterDecoder( + sparsify_errors=True, + sparsify_base_degree=2, + sparsify_max_degree=4, + sparsify_reactivate_limit=10, + ) + assert decoder.sparsify_errors is True + assert decoder.sparsify_base_degree == 2 + assert decoder.sparsify_max_degree == 4 + assert decoder.sparsify_reactivate_limit == 10 + + # Test equality + decoder2 = TesseractSinterDecoder( + sparsify_errors=True, + sparsify_base_degree=2, + sparsify_max_degree=4, + sparsify_reactivate_limit=10, + ) + assert decoder == decoder2 + + decoder3 = TesseractSinterDecoder( + sparsify_errors=True, + sparsify_base_degree=3, # different + sparsify_max_degree=4, + sparsify_reactivate_limit=10, + ) + assert decoder != decoder3 + + # Test pickle + import pickle + + dumped = pickle.dumps(decoder) + loaded = pickle.loads(dumped) + assert decoder == loaded + + +def test_make_tesseract_sinter_decoders_dict_contains_sparsify(): + decoders = make_tesseract_sinter_decoders_dict() + assert "tesseract-long-beam-sparsify3" in decoders + assert "tesseract-long-beam-sparsify2" in decoders + assert "tesseract-short-beam-sparsify3" in decoders + assert "tesseract-short-beam-sparsify2" in decoders + + d_long3 = decoders["tesseract-long-beam-sparsify3"] + assert d_long3.sparsify_errors is True + assert d_long3.sparsify_base_degree == 3 + assert d_long3.sparsify_max_degree == -1 + assert d_long3.sparsify_reactivate_limit == -1 + assert d_long3.det_beam == 20 + + d_short2 = decoders["tesseract-short-beam-sparsify2"] + assert d_short2.sparsify_errors is True + assert d_short2.sparsify_base_degree == 2 + assert d_short2.sparsify_max_degree == -1 + assert d_short2.sparsify_reactivate_limit == -1 + assert d_short2.det_beam == 15 + + +@pytest.mark.parametrize( + "decoder_name", + [ + "tesseract-long-beam-sparsify3", + "tesseract-long-beam-sparsify2", + "tesseract-short-beam-sparsify3", + "tesseract-short-beam-sparsify2", + ], +) +def test_sinter_decode_with_sparsify_decoders(decoder_name): + # Test that the new decoders can actually run and decode a simple repetition code. + circuit = stim.Circuit.generated( + "repetition_code:memory", + rounds=3, + distance=3, + after_clifford_depolarization=0.01, + ) + + result = sample_decode( + circuit_obj=circuit, + circuit_path=None, + dem_obj=circuit.detector_error_model(decompose_errors=True), + dem_path=None, + num_shots=100, + decoder=decoder_name, + custom_decoders=make_tesseract_sinter_decoders_dict(), + ) + assert result.discards == 0 + assert result.shots == 100 + assert 0 <= result.errors <= 10 + + if __name__ == "__main__": raise SystemExit(pytest.main([__file__])) diff --git a/src/py/tesseract_test.py b/src/py/tesseract_test.py index bb62bead..fbde3692 100644 --- a/src/py/tesseract_test.py +++ b/src/py/tesseract_test.py @@ -286,5 +286,85 @@ def test_test_simplex_decode_batch_with_mismatched_syndrome_size(): ) +def test_create_tesseract_config_sparsify_defaults(): + config = tesseract_decoder.tesseract.TesseractConfig() + assert config.sparsify_errors is False + assert config.sparsify_base_degree == -1 + assert config.sparsify_max_degree == -1 + assert config.sparsify_reactivate_limit == -1 + + +def test_create_tesseract_config_sparsify_custom(): + config = tesseract_decoder.tesseract.TesseractConfig( + sparsify_errors=True, + sparsify_base_degree=2, + sparsify_max_degree=4, + sparsify_reactivate_limit=10, + ) + assert config.sparsify_errors is True + assert config.sparsify_base_degree == 2 + assert config.sparsify_max_degree == 4 + assert config.sparsify_reactivate_limit == 10 + + +def test_get_sparsify_reactivate_limit_heuristic(): + # We need a DEM to test this because it depends on number of detectors. + # _DETECTOR_ERROR_MODEL has 2 detectors (D0, D1) + config = tesseract_decoder.tesseract.TesseractConfig( + _DETECTOR_ERROR_MODEL, + sparsify_errors=True, + sparsify_base_degree=2, # k=2 + sparsify_reactivate_limit=-1, # use heuristic + ) + # Heuristic formula: round((4.5^(k-2) / 3) * num_detectors) + # For k=2, num_detectors=2: + # round((4.5^0 / 3) * 2) = round((1/3) * 2) = round(2/3) = round(0.666...) = 1 + assert config.get_sparsify_reactivate_limit() == 1 + + # Test with k=3 + config.sparsify_base_degree = 3 + # round((4.5^1 / 3) * 2) = round((4.5/3) * 2) = round(1.5 * 2) = round(3.0) = 3 + assert config.get_sparsify_reactivate_limit() == 3 + + # Test when sparsify_errors is False + config.sparsify_errors = False + assert config.get_sparsify_reactivate_limit() == -1 + + # Test when explicitly set + config.sparsify_errors = True + config.sparsify_reactivate_limit = 5 + assert config.get_sparsify_reactivate_limit() == 5 + + # Test validation: sparsify_base_degree < 0 throws + config.sparsify_reactivate_limit = -1 + config.sparsify_base_degree = -1 + with pytest.raises(ValueError, match="sparsify_base_degree must be >= 0"): + config.get_sparsify_reactivate_limit() + + +def test_get_sparsify_reactivate_limit_empty_dem(): + config = tesseract_decoder.tesseract.TesseractConfig( + sparsify_errors=True, + sparsify_base_degree=2, + sparsify_reactivate_limit=-1, + ) + assert config.get_sparsify_reactivate_limit() == 0 + + +def test_decoder_compilation_validation(): + # sparsify_base_degree < 0 throws + config = tesseract_decoder.tesseract.TesseractConfig( + _DETECTOR_ERROR_MODEL, sparsify_errors=True, sparsify_base_degree=-1 + ) + with pytest.raises(ValueError, match="sparsify_base_degree must be >= 0"): + config.compile_decoder() + + # sparsify_max_degree < sparsify_base_degree throws + config.sparsify_base_degree = 3 + config.sparsify_max_degree = 2 + with pytest.raises(ValueError, match="sparsify_max_degree must be >= sparsify_base_degree"): + config.compile_decoder() + + if __name__ == "__main__": raise SystemExit(pytest.main([__file__])) diff --git a/src/tesseract.cc b/src/tesseract.cc index 91651e01..ea78b651 100644 --- a/src/tesseract.cc +++ b/src/tesseract.cc @@ -17,6 +17,7 @@ #include #include // For boost::hash_range #include +#include #include #include // For std::hash (though not strictly necessary here, but good practice) #include @@ -71,6 +72,22 @@ std::string TesseractConfig::str() { return ss.str(); } +int TesseractConfig::get_sparsify_reactivate_limit() const { + if (sparsify_reactivate_limit >= 0) { + return sparsify_reactivate_limit; + } + if (!sparsify_errors) { + return -1; + } + if (sparsify_base_degree < 0) { + throw std::invalid_argument( + "sparsify_base_degree must be >= 0 when sparsify_errors is enabled."); + } + double k = sparsify_base_degree; + double num_detectors = dem.count_detectors(); + return static_cast(std::round((std::pow(4.5, k - 2.0) / 3.0) * num_detectors)); +} + std::string Node::str() { std::stringstream ss; auto& self = *this; @@ -217,6 +234,18 @@ void TesseractDecoder::initialize_structures(size_t num_detectors) { } if (config.sparsify_errors) { + if (config.sparsify_base_degree < 0) { + throw std::invalid_argument( + "sparsify_base_degree must be >= 0 when sparsify_errors is enabled."); + } + if (config.sparsify_max_degree >= 0 && + config.sparsify_max_degree < config.sparsify_base_degree) { + throw std::invalid_argument( + "sparsify_max_degree must be >= sparsify_base_degree."); + } + + config.sparsify_reactivate_limit = config.get_sparsify_reactivate_limit(); + sparsify_mandatory_errors.clear(); sparsify_optional_errors.clear(); for (size_t ei = 0; ei < num_errors; ++ei) { diff --git a/src/tesseract.h b/src/tesseract.h index 37b55319..57d5a4fd 100644 --- a/src/tesseract.h +++ b/src/tesseract.h @@ -50,6 +50,7 @@ struct TesseractConfig { int sparsify_max_degree = -1; int sparsify_reactivate_limit = -1; + int get_sparsify_reactivate_limit() const; std::string str(); }; diff --git a/src/tesseract.pybind.h b/src/tesseract.pybind.h index 3bdf477d..495a020a 100644 --- a/src/tesseract.pybind.h +++ b/src/tesseract.pybind.h @@ -38,13 +38,17 @@ TesseractConfig tesseract_config_maker_no_dem( bool verbose = false, bool merge_errors = true, size_t pqlimit = std::numeric_limits::max(), std::vector> det_orders = std::vector>(), - double det_penalty = 0.0, bool create_visualization = false) { + double det_penalty = 0.0, bool create_visualization = false, + bool sparsify_errors = false, int sparsify_base_degree = -1, + int sparsify_max_degree = -1, int sparsify_reactivate_limit = -1) { 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, verbose, - merge_errors, pqlimit, det_orders, det_penalty, create_visualization}); + merge_errors, pqlimit, det_orders, det_penalty, create_visualization, + sparsify_errors, sparsify_base_degree, sparsify_max_degree, + sparsify_reactivate_limit}); } TesseractConfig tesseract_config_maker( @@ -52,13 +56,17 @@ TesseractConfig tesseract_config_maker( bool no_revisit_dets = false, bool verbose = false, bool merge_errors = true, size_t pqlimit = std::numeric_limits::max(), std::vector> det_orders = std::vector>(), - double det_penalty = 0.0, bool create_visualization = false) { + double det_penalty = 0.0, bool create_visualization = false, + bool sparsify_errors = false, int sparsify_base_degree = -1, + int sparsify_max_degree = -1, int sparsify_reactivate_limit = -1) { stim::DetectorErrorModel input_dem = parse_py_object(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, verbose, - merge_errors, pqlimit, det_orders, det_penalty, create_visualization}); + merge_errors, pqlimit, det_orders, det_penalty, create_visualization, + sparsify_errors, sparsify_base_degree, sparsify_max_degree, + sparsify_reactivate_limit}); } }; // namespace @@ -83,6 +91,8 @@ void add_tesseract_module(py::module& root) { py::arg("verbose") = false, py::arg("merge_errors") = true, py::arg("pqlimit") = 200000, py::arg("det_orders") = std::vector>(), py::arg("det_penalty") = 0.0, py::arg("create_visualization") = false, + py::arg("sparsify_errors") = false, py::arg("sparsify_base_degree") = -1, + py::arg("sparsify_max_degree") = -1, py::arg("sparsify_reactivate_limit") = -1, R"pbdoc( The constructor for the `TesseractConfig` class without a `dem` argument. This creates an empty `DetectorErrorModel` by default. @@ -109,12 +119,22 @@ void add_tesseract_module(py::module& root) { A penalty value added to the cost of each detector visited. create_visualization: bool, defualt=False Whether to record the information needed to create a visualization or not. + sparsify_errors: bool, default=False + If True, enables per-shot sparse error activation. + sparsify_base_degree: int, default=-1 + Maximum detector degree for mandatory errors. + sparsify_max_degree: int, default=-1 + Maximum detector degree for optional errors. + sparsify_reactivate_limit: int, default=-1 + Maximum number of optional errors to reactivate per shot. Use -1 for heuristic default. )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("verbose") = false, py::arg("merge_errors") = true, py::arg("pqlimit") = 200000, py::arg("det_orders") = std::vector>(), py::arg("det_penalty") = 0.0, py::arg("create_visualization") = false, + py::arg("sparsify_errors") = false, py::arg("sparsify_base_degree") = -1, + py::arg("sparsify_max_degree") = -1, py::arg("sparsify_reactivate_limit") = -1, R"pbdoc( The constructor for the `TesseractConfig` class. @@ -142,6 +162,14 @@ void add_tesseract_module(py::module& root) { A penalty value added to the cost of each detector visited. create_visualization: bool, defualt=False Whether to record the information needed to create a visualization or not. + sparsify_errors: bool, default=False + If True, enables per-shot sparse error activation. + sparsify_base_degree: int, default=-1 + Maximum detector degree for mandatory errors. + sparsify_max_degree: int, default=-1 + Maximum detector degree for optional errors. + sparsify_reactivate_limit: int, default=-1 + Maximum number of optional errors to reactivate per shot. Use -1 for heuristic default. )pbdoc") .def_property("dem", &dem_getter, &dem_setter, "The `stim.DetectorErrorModel` that defines the error channels and detectors.") @@ -164,6 +192,17 @@ void add_tesseract_module(py::module& root) { "The penalty cost added for each detector.") .def_readwrite("create_visualization", &TesseractConfig::create_visualization, "If True, records necessary information to create visualization.") + .def_readwrite("sparsify_errors", &TesseractConfig::sparsify_errors, + "If True, enables per-shot sparse error activation.") + .def_readwrite("sparsify_base_degree", &TesseractConfig::sparsify_base_degree, + "Maximum detector degree for mandatory errors.") + .def_readwrite("sparsify_max_degree", &TesseractConfig::sparsify_max_degree, + "Maximum detector degree for optional errors.") + .def_readwrite("sparsify_reactivate_limit", &TesseractConfig::sparsify_reactivate_limit, + "Maximum number of optional errors to reactivate per shot. Use -1 for " + "heuristic default.") + .def("get_sparsify_reactivate_limit", &TesseractConfig::get_sparsify_reactivate_limit, + "Returns the resolved reactivate limit, applying the heuristic if it is set to -1.") .def("__str__", &TesseractConfig::str) .def("compile_decoder", &_compile_tesseract_decoder_helper, py::return_value_policy::take_ownership, diff --git a/src/tesseract_main.cc b/src/tesseract_main.cc index bcdf74e3..fa7890cc 100644 --- a/src/tesseract_main.cc +++ b/src/tesseract_main.cc @@ -334,16 +334,10 @@ struct Args { config.sparsify_errors = sparsify_errors; config.sparsify_base_degree = sparsify_base_degree; config.sparsify_max_degree = sparsify_max_degree; - - // Apply heuristic estimate for number of errors if sparsify_errors is enabled but no limit was - // provided - if (sparsify_errors && sparsify_reactivate_limit < 0) { - double k = sparsify_base_degree; - double num_detectors = config.dem.count_detectors(); - sparsify_reactivate_limit = - static_cast(std::round((std::pow(4.5, k - 2.0) / 3.0) * num_detectors)); - } config.sparsify_reactivate_limit = sparsify_reactivate_limit; + + config.sparsify_reactivate_limit = config.get_sparsify_reactivate_limit(); + sparsify_reactivate_limit = config.sparsify_reactivate_limit; } }; diff --git a/src/tesseract_sinter_compat.pybind.h b/src/tesseract_sinter_compat.pybind.h index c889423c..b09632b1 100644 --- a/src/tesseract_sinter_compat.pybind.h +++ b/src/tesseract_sinter_compat.pybind.h @@ -109,6 +109,10 @@ struct TesseractSinterDecoder { size_t pqlimit; double det_penalty; bool create_visualization; + bool sparsify_errors; + int sparsify_base_degree; + int sparsify_max_degree; + int sparsify_reactivate_limit; // Parameters for build_det_orders size_t num_det_orders; @@ -125,6 +129,10 @@ struct TesseractSinterDecoder { pqlimit(DEFAULT_PQLIMIT), det_penalty(0.0), create_visualization(false), + sparsify_errors(false), + sparsify_base_degree(-1), + sparsify_max_degree(-1), + sparsify_reactivate_limit(-1), num_det_orders(0), det_order_method(DetOrder::DetBFS), seed(2384753) {} @@ -132,7 +140,10 @@ struct TesseractSinterDecoder { // Constructor with parameters TesseractSinterDecoder(int det_beam, bool beam_climbing, bool no_revisit_dets, bool verbose, bool merge_errors, size_t pqlimit, double det_penalty, - bool create_visualization, size_t num_det_orders, + bool create_visualization, + bool sparsify_errors, int sparsify_base_degree, + int sparsify_max_degree, int sparsify_reactivate_limit, + size_t num_det_orders, DetOrder det_order_method, uint64_t seed) : det_beam(det_beam), beam_climbing(beam_climbing), @@ -142,6 +153,10 @@ struct TesseractSinterDecoder { pqlimit(pqlimit), det_penalty(det_penalty), create_visualization(create_visualization), + sparsify_errors(sparsify_errors), + sparsify_base_degree(sparsify_base_degree), + sparsify_max_degree(sparsify_max_degree), + sparsify_reactivate_limit(sparsify_reactivate_limit), num_det_orders(num_det_orders), det_order_method(det_order_method), seed(seed) {} @@ -151,6 +166,10 @@ struct TesseractSinterDecoder { no_revisit_dets == other.no_revisit_dets && verbose == other.verbose && merge_errors == other.merge_errors && pqlimit == other.pqlimit && det_penalty == other.det_penalty && create_visualization == other.create_visualization && + sparsify_errors == other.sparsify_errors && + sparsify_base_degree == other.sparsify_base_degree && + sparsify_max_degree == other.sparsify_max_degree && + sparsify_reactivate_limit == other.sparsify_reactivate_limit && num_det_orders == other.num_det_orders && det_order_method == other.det_order_method && seed == other.seed; } @@ -167,8 +186,13 @@ struct TesseractSinterDecoder { build_det_orders(stim_dem, num_det_orders, det_order_method, seed); TesseractConfig local_config = { - stim_dem, det_beam, beam_climbing, no_revisit_dets, verbose, - merge_errors, pqlimit, det_orders, det_penalty, create_visualization}; + stim_dem, det_beam, + beam_climbing, no_revisit_dets, + verbose, merge_errors, + pqlimit, det_orders, + det_penalty, create_visualization, + sparsify_errors, sparsify_base_degree, + sparsify_max_degree, sparsify_reactivate_limit}; auto decoder = std::make_unique(local_config); return TesseractSinterCompiledDecoder{ @@ -202,8 +226,13 @@ struct TesseractSinterDecoder { build_det_orders(stim_dem, num_det_orders, det_order_method, seed); TesseractConfig local_config = { - stim_dem, det_beam, beam_climbing, no_revisit_dets, verbose, - merge_errors, pqlimit, det_orders, det_penalty, create_visualization}; + stim_dem, det_beam, + beam_climbing, no_revisit_dets, + verbose, merge_errors, + pqlimit, det_orders, + det_penalty, create_visualization, + sparsify_errors, sparsify_base_degree, + sparsify_max_degree, sparsify_reactivate_limit}; TesseractDecoder decoder(local_config); // Calculate expected number of bytes per shot for detectors and observables. @@ -305,11 +334,15 @@ void pybind_sinter_compat(py::module& root) { Initializes a new TesseractSinterDecoder instance with a default TesseractConfig. )pbdoc") .def( - py::init(), + py::init(), py::arg("det_beam") = DEFAULT_DET_BEAM, py::arg("beam_climbing") = false, py::arg("no_revisit_dets") = true, py::arg("verbose") = false, py::arg("merge_errors") = true, py::arg("pqlimit") = DEFAULT_PQLIMIT, py::arg("det_penalty") = 0.0, py::arg("create_visualization") = false, + py::arg("sparsify_errors") = false, py::arg("sparsify_base_degree") = -1, + py::arg("sparsify_max_degree") = -1, py::arg("sparsify_reactivate_limit") = -1, py::arg("num_det_orders") = 0, py::arg("det_order_method") = DetOrder::DetBFS, py::arg("seed") = 2384753, R"pbdoc( @@ -347,6 +380,10 @@ void pybind_sinter_compat(py::module& root) { .def_readwrite("pqlimit", &TesseractSinterDecoder::pqlimit) .def_readwrite("det_penalty", &TesseractSinterDecoder::det_penalty) .def_readwrite("create_visualization", &TesseractSinterDecoder::create_visualization) + .def_readwrite("sparsify_errors", &TesseractSinterDecoder::sparsify_errors) + .def_readwrite("sparsify_base_degree", &TesseractSinterDecoder::sparsify_base_degree) + .def_readwrite("sparsify_max_degree", &TesseractSinterDecoder::sparsify_max_degree) + .def_readwrite("sparsify_reactivate_limit", &TesseractSinterDecoder::sparsify_reactivate_limit) .def_readwrite("num_det_orders", &TesseractSinterDecoder::num_det_orders) .def_readwrite("det_order_method", &TesseractSinterDecoder::det_order_method) .def_readwrite("seed", &TesseractSinterDecoder::seed) @@ -358,17 +395,20 @@ void pybind_sinter_compat(py::module& root) { [](const TesseractSinterDecoder& self) -> py::tuple { // __getstate__ return py::make_tuple(self.det_beam, self.beam_climbing, self.no_revisit_dets, self.verbose, self.merge_errors, self.pqlimit, self.det_penalty, - self.create_visualization, self.num_det_orders, - self.det_order_method, self.seed); + self.create_visualization, + self.sparsify_errors, self.sparsify_base_degree, + self.sparsify_max_degree, self.sparsify_reactivate_limit, + self.num_det_orders, self.det_order_method, self.seed); }, [](py::tuple t) { // __setstate__ - if (t.size() != 11) { + if (t.size() != 15) { throw std::runtime_error("Invalid state for TesseractSinterDecoder!"); } return TesseractSinterDecoder( t[0].cast(), t[1].cast(), t[2].cast(), t[3].cast(), t[4].cast(), t[5].cast(), t[6].cast(), t[7].cast(), - t[8].cast(), t[9].cast(), t[10].cast()); + t[8].cast(), t[9].cast(), t[10].cast(), t[11].cast(), + t[12].cast(), t[13].cast(), t[14].cast()); })); // Add a function to create a dictionary of custom decoders @@ -380,12 +420,44 @@ void pybind_sinter_compat(py::module& root) { /*det_beam=*/20, /*beam_climbing=*/true, /*no_revisit_dets=*/true, /*verbose=*/false, /*merge_errors=*/true, /*pqlimit=*/1000000, /*det_penalty=*/0.0, /*create_visualization=*/false, + /*sparsify_errors=*/false, /*sparsify_base_degree=*/-1, + /*sparsify_max_degree=*/-1, /*sparsify_reactivate_limit=*/-1, /*num_det_orders=*/21, /*det_order_method=*/DetOrder::DetIndex, /*seed=*/2384753); result["tesseract"] = result["tesseract-long-beam"]; + result["tesseract-long-beam-sparsify3"] = TesseractSinterDecoder( + /*det_beam=*/20, /*beam_climbing=*/true, /*no_revisit_dets=*/true, + /*verbose=*/false, /*merge_errors=*/true, /*pqlimit=*/1000000, + /*det_penalty=*/0.0, /*create_visualization=*/false, + /*sparsify_errors=*/true, /*sparsify_base_degree=*/3, + /*sparsify_max_degree=*/-1, /*sparsify_reactivate_limit=*/-1, + /*num_det_orders=*/21, /*det_order_method=*/DetOrder::DetIndex, /*seed=*/2384753); + result["tesseract-long-beam-sparsify2"] = TesseractSinterDecoder( + /*det_beam=*/20, /*beam_climbing=*/true, /*no_revisit_dets=*/true, + /*verbose=*/false, /*merge_errors=*/true, /*pqlimit=*/1000000, + /*det_penalty=*/0.0, /*create_visualization=*/false, + /*sparsify_errors=*/true, /*sparsify_base_degree=*/2, + /*sparsify_max_degree=*/-1, /*sparsify_reactivate_limit=*/-1, + /*num_det_orders=*/21, /*det_order_method=*/DetOrder::DetIndex, /*seed=*/2384753); result["tesseract-short-beam"] = TesseractSinterDecoder( /*det_beam=*/15, /*beam_climbing=*/true, /*no_revisit_dets=*/true, /*verbose=*/false, /*merge_errors=*/true, /*pqlimit=*/200000, /*det_penalty=*/0.0, /*create_visualization=*/false, + /*sparsify_errors=*/false, /*sparsify_base_degree=*/-1, + /*sparsify_max_degree=*/-1, /*sparsify_reactivate_limit=*/-1, + /*num_det_orders=*/16, /*det_order_method=*/DetOrder::DetIndex, /*seed=*/2384753); + result["tesseract-short-beam-sparsify3"] = TesseractSinterDecoder( + /*det_beam=*/15, /*beam_climbing=*/true, /*no_revisit_dets=*/true, + /*verbose=*/false, /*merge_errors=*/true, /*pqlimit=*/200000, + /*det_penalty=*/0.0, /*create_visualization=*/false, + /*sparsify_errors=*/true, /*sparsify_base_degree=*/3, + /*sparsify_max_degree=*/-1, /*sparsify_reactivate_limit=*/-1, + /*num_det_orders=*/16, /*det_order_method=*/DetOrder::DetIndex, /*seed=*/2384753); + result["tesseract-short-beam-sparsify2"] = TesseractSinterDecoder( + /*det_beam=*/15, /*beam_climbing=*/true, /*no_revisit_dets=*/true, + /*verbose=*/false, /*merge_errors=*/true, /*pqlimit=*/200000, + /*det_penalty=*/0.0, /*create_visualization=*/false, + /*sparsify_errors=*/true, /*sparsify_base_degree=*/2, + /*sparsify_max_degree=*/-1, /*sparsify_reactivate_limit=*/-1, /*num_det_orders=*/16, /*det_order_method=*/DetOrder::DetIndex, /*seed=*/2384753); return result; }, From 1831f235ce4a6d463ef39930c2562a31562b1a01 Mon Sep 17 00:00:00 2001 From: noajshu Date: Wed, 27 May 2026 02:08:15 +0000 Subject: [PATCH 2/2] format --- src/tesseract.cc | 3 +- src/tesseract.pybind.h | 24 ++++---- src/tesseract_sinter_compat.pybind.h | 85 +++++++++++++++------------- 3 files changed, 60 insertions(+), 52 deletions(-) diff --git a/src/tesseract.cc b/src/tesseract.cc index ea78b651..07cd133d 100644 --- a/src/tesseract.cc +++ b/src/tesseract.cc @@ -240,8 +240,7 @@ void TesseractDecoder::initialize_structures(size_t num_detectors) { } if (config.sparsify_max_degree >= 0 && config.sparsify_max_degree < config.sparsify_base_degree) { - throw std::invalid_argument( - "sparsify_max_degree must be >= sparsify_base_degree."); + throw std::invalid_argument("sparsify_max_degree must be >= sparsify_base_degree."); } config.sparsify_reactivate_limit = config.get_sparsify_reactivate_limit(); diff --git a/src/tesseract.pybind.h b/src/tesseract.pybind.h index 495a020a..555d7c91 100644 --- a/src/tesseract.pybind.h +++ b/src/tesseract.pybind.h @@ -38,9 +38,9 @@ TesseractConfig tesseract_config_maker_no_dem( bool verbose = false, bool merge_errors = true, size_t pqlimit = std::numeric_limits::max(), std::vector> det_orders = std::vector>(), - double det_penalty = 0.0, bool create_visualization = false, - bool sparsify_errors = false, int sparsify_base_degree = -1, - int sparsify_max_degree = -1, int sparsify_reactivate_limit = -1) { + double det_penalty = 0.0, bool create_visualization = false, bool sparsify_errors = false, + int sparsify_base_degree = -1, int sparsify_max_degree = -1, + int sparsify_reactivate_limit = -1) { stim::DetectorErrorModel empty_dem; if (det_orders.empty()) { det_orders = build_det_orders(empty_dem, 20, DetOrder::DetBFS, 2384753); @@ -56,9 +56,9 @@ TesseractConfig tesseract_config_maker( bool no_revisit_dets = false, bool verbose = false, bool merge_errors = true, size_t pqlimit = std::numeric_limits::max(), std::vector> det_orders = std::vector>(), - double det_penalty = 0.0, bool create_visualization = false, - bool sparsify_errors = false, int sparsify_base_degree = -1, - int sparsify_max_degree = -1, int sparsify_reactivate_limit = -1) { + double det_penalty = 0.0, bool create_visualization = false, bool sparsify_errors = false, + int sparsify_base_degree = -1, int sparsify_max_degree = -1, + int sparsify_reactivate_limit = -1) { stim::DetectorErrorModel input_dem = parse_py_object(dem); if (det_orders.empty()) { det_orders = build_det_orders(input_dem, 20, DetOrder::DetBFS, 2384753); @@ -90,9 +90,9 @@ void add_tesseract_module(py::module& root) { py::arg("beam_climbing") = false, py::arg("no_revisit_dets") = true, py::arg("verbose") = false, py::arg("merge_errors") = true, py::arg("pqlimit") = 200000, py::arg("det_orders") = std::vector>(), py::arg("det_penalty") = 0.0, - py::arg("create_visualization") = false, - py::arg("sparsify_errors") = false, py::arg("sparsify_base_degree") = -1, - py::arg("sparsify_max_degree") = -1, py::arg("sparsify_reactivate_limit") = -1, + py::arg("create_visualization") = false, py::arg("sparsify_errors") = false, + py::arg("sparsify_base_degree") = -1, py::arg("sparsify_max_degree") = -1, + py::arg("sparsify_reactivate_limit") = -1, R"pbdoc( The constructor for the `TesseractConfig` class without a `dem` argument. This creates an empty `DetectorErrorModel` by default. @@ -132,9 +132,9 @@ void add_tesseract_module(py::module& root) { py::arg("beam_climbing") = false, py::arg("no_revisit_dets") = true, py::arg("verbose") = false, py::arg("merge_errors") = true, py::arg("pqlimit") = 200000, py::arg("det_orders") = std::vector>(), py::arg("det_penalty") = 0.0, - py::arg("create_visualization") = false, - py::arg("sparsify_errors") = false, py::arg("sparsify_base_degree") = -1, - py::arg("sparsify_max_degree") = -1, py::arg("sparsify_reactivate_limit") = -1, + py::arg("create_visualization") = false, py::arg("sparsify_errors") = false, + py::arg("sparsify_base_degree") = -1, py::arg("sparsify_max_degree") = -1, + py::arg("sparsify_reactivate_limit") = -1, R"pbdoc( The constructor for the `TesseractConfig` class. diff --git a/src/tesseract_sinter_compat.pybind.h b/src/tesseract_sinter_compat.pybind.h index b09632b1..e1969b90 100644 --- a/src/tesseract_sinter_compat.pybind.h +++ b/src/tesseract_sinter_compat.pybind.h @@ -140,11 +140,9 @@ struct TesseractSinterDecoder { // Constructor with parameters TesseractSinterDecoder(int det_beam, bool beam_climbing, bool no_revisit_dets, bool verbose, bool merge_errors, size_t pqlimit, double det_penalty, - bool create_visualization, - bool sparsify_errors, int sparsify_base_degree, + bool create_visualization, bool sparsify_errors, int sparsify_base_degree, int sparsify_max_degree, int sparsify_reactivate_limit, - size_t num_det_orders, - DetOrder det_order_method, uint64_t seed) + size_t num_det_orders, DetOrder det_order_method, uint64_t seed) : det_beam(det_beam), beam_climbing(beam_climbing), no_revisit_dets(no_revisit_dets), @@ -185,14 +183,20 @@ struct TesseractSinterDecoder { std::vector> det_orders = build_det_orders(stim_dem, num_det_orders, det_order_method, seed); - TesseractConfig local_config = { - stim_dem, det_beam, - beam_climbing, no_revisit_dets, - verbose, merge_errors, - pqlimit, det_orders, - det_penalty, create_visualization, - sparsify_errors, sparsify_base_degree, - sparsify_max_degree, sparsify_reactivate_limit}; + TesseractConfig local_config = {stim_dem, + det_beam, + beam_climbing, + no_revisit_dets, + verbose, + merge_errors, + pqlimit, + det_orders, + det_penalty, + create_visualization, + sparsify_errors, + sparsify_base_degree, + sparsify_max_degree, + sparsify_reactivate_limit}; auto decoder = std::make_unique(local_config); return TesseractSinterCompiledDecoder{ @@ -225,14 +229,20 @@ struct TesseractSinterDecoder { std::vector> det_orders = build_det_orders(stim_dem, num_det_orders, det_order_method, seed); - TesseractConfig local_config = { - stim_dem, det_beam, - beam_climbing, no_revisit_dets, - verbose, merge_errors, - pqlimit, det_orders, - det_penalty, create_visualization, - sparsify_errors, sparsify_base_degree, - sparsify_max_degree, sparsify_reactivate_limit}; + TesseractConfig local_config = {stim_dem, + det_beam, + beam_climbing, + no_revisit_dets, + verbose, + merge_errors, + pqlimit, + det_orders, + det_penalty, + create_visualization, + sparsify_errors, + sparsify_base_degree, + sparsify_max_degree, + sparsify_reactivate_limit}; TesseractDecoder decoder(local_config); // Calculate expected number of bytes per shot for detectors and observables. @@ -333,19 +343,17 @@ void pybind_sinter_compat(py::module& root) { .def(py::init<>(), R"pbdoc( Initializes a new TesseractSinterDecoder instance with a default TesseractConfig. )pbdoc") - .def( - py::init(), - py::arg("det_beam") = DEFAULT_DET_BEAM, py::arg("beam_climbing") = false, - py::arg("no_revisit_dets") = true, py::arg("verbose") = false, - py::arg("merge_errors") = true, py::arg("pqlimit") = DEFAULT_PQLIMIT, - py::arg("det_penalty") = 0.0, py::arg("create_visualization") = false, - py::arg("sparsify_errors") = false, py::arg("sparsify_base_degree") = -1, - py::arg("sparsify_max_degree") = -1, py::arg("sparsify_reactivate_limit") = -1, - py::arg("num_det_orders") = 0, py::arg("det_order_method") = DetOrder::DetBFS, - py::arg("seed") = 2384753, - R"pbdoc( + .def(py::init(), + py::arg("det_beam") = DEFAULT_DET_BEAM, py::arg("beam_climbing") = false, + py::arg("no_revisit_dets") = true, py::arg("verbose") = false, + py::arg("merge_errors") = true, py::arg("pqlimit") = DEFAULT_PQLIMIT, + py::arg("det_penalty") = 0.0, py::arg("create_visualization") = false, + py::arg("sparsify_errors") = false, py::arg("sparsify_base_degree") = -1, + py::arg("sparsify_max_degree") = -1, py::arg("sparsify_reactivate_limit") = -1, + py::arg("num_det_orders") = 0, py::arg("det_order_method") = DetOrder::DetBFS, + py::arg("seed") = 2384753, + R"pbdoc( Initializes a new TesseractSinterDecoder instance with custom TesseractConfig parameters. )pbdoc") .def("compile_decoder_for_dem", &TesseractSinterDecoder::compile_decoder_for_dem, @@ -383,7 +391,8 @@ void pybind_sinter_compat(py::module& root) { .def_readwrite("sparsify_errors", &TesseractSinterDecoder::sparsify_errors) .def_readwrite("sparsify_base_degree", &TesseractSinterDecoder::sparsify_base_degree) .def_readwrite("sparsify_max_degree", &TesseractSinterDecoder::sparsify_max_degree) - .def_readwrite("sparsify_reactivate_limit", &TesseractSinterDecoder::sparsify_reactivate_limit) + .def_readwrite("sparsify_reactivate_limit", + &TesseractSinterDecoder::sparsify_reactivate_limit) .def_readwrite("num_det_orders", &TesseractSinterDecoder::num_det_orders) .def_readwrite("det_order_method", &TesseractSinterDecoder::det_order_method) .def_readwrite("seed", &TesseractSinterDecoder::seed) @@ -395,10 +404,10 @@ void pybind_sinter_compat(py::module& root) { [](const TesseractSinterDecoder& self) -> py::tuple { // __getstate__ return py::make_tuple(self.det_beam, self.beam_climbing, self.no_revisit_dets, self.verbose, self.merge_errors, self.pqlimit, self.det_penalty, - self.create_visualization, - self.sparsify_errors, self.sparsify_base_degree, - self.sparsify_max_degree, self.sparsify_reactivate_limit, - self.num_det_orders, self.det_order_method, self.seed); + self.create_visualization, self.sparsify_errors, + self.sparsify_base_degree, self.sparsify_max_degree, + self.sparsify_reactivate_limit, self.num_det_orders, + self.det_order_method, self.seed); }, [](py::tuple t) { // __setstate__ if (t.size() != 15) {