diff --git a/README.md b/README.md index 35df092..bf428d2 100644 --- a/README.md +++ b/README.md @@ -212,6 +212,103 @@ print(f"Predicted errors indices: {predicted_errors}") for i in predicted_errors: print(f" {i}: {decoder.errors[i]}") ``` +## Using Tesseract with Sinter + +Tesseract can be easily integrated into [Sinter](https://github.com/quantumlib/Stim/tree/main/glue/sample) workflows. Sinter is a tool for running and organizing quantum error correction simulations. + +Here's an example of how to use Tesseract as a decoder for multiple Sinter tasks: + +```python +import stim +import sinter +from tesseract_decoder import make_tesseract_sinter_decoders_dict, TesseractSinterDecoder +import tesseract_decoder + +if __name__ == "__main__": + # Define a list of Sinter task(s) with different circuits/decoders. + tasks = [] + # Depolarizing noise probability. + p = 0.005 + # These are the sensible defaults given by make_tesseract_sinter_decoders_dict(). + # Note that `tesseract-short-beam` and `tesseract-long-beam` are the two sets of parameters used in the [Tesseract paper](https://arxiv.org/pdf/2503.10988). + decoders = ['tesseract', 'tesseract-long-beam', 'tesseract-short-beam'] + decoder_dict = make_tesseract_sinter_decoders_dict() + # You can also make your own custom Tesseract Decoder to-be-used with Sinter. + decoders.append('custom-tesseract-decoder') + decoder_dict['custom-tesseract-decoder'] = TesseractSinterDecoder( + det_beam=10, + beam_climbing=True, + no_revisit_dets=True, + merge_errors=True, + pqlimit=1_000, + num_det_orders=5, + det_order_method=tesseract_decoder.utils.DetOrder.DetIndex, + seed=2384753, + ) + + for distance in [3, 5, 7]: + for decoder in decoders: + circuit = stim.Circuit.generated( + "surface_code:rotated_memory_x", + distance=distance, + rounds=3, + after_clifford_depolarization=p + ) + tasks.append(sinter.Task( + circuit=circuit, + decoder=decoder, + json_metadata={"d": distance, "decoder": decoder}, + )) + + # Collect decoding outcomes per task from Sinter. + results = sinter.collect( + num_workers=8, + tasks=tasks, + max_shots=10_000, + decoders=decoders, + custom_decoders=decoder_dict, + print_progress=True, + ) + + # Print samples as CSV data. + print(sinter.CSV_HEADER) + for sample in results: + print(sample.to_csv_line()) +``` +should get something like: +``` + shots, errors, discards, seconds,decoder,strong_id,json_metadata,custom_counts + 10000, 42, 0, 0.071,tesseract,1b3fce6286e438f38c00c8f6a5005947373515ab08e6446a7dd9ecdbef12d4cc,"{""d"":3,""decoder"":""tesseract""}", + 10000, 49, 0, 0.546,custom-tesseract-decoder,7b082bec7541be858e239d7828a432e329cd448356bbdf051b8b8aa76c86625a,"{""d"":3,""decoder"":""custom-tesseract-decoder""}", + 10000, 13, 0, 7.64,tesseract-long-beam,217a3542f56319924576658a6da7081ea2833f5167cf6d77fbc7071548e386a9,"{""d"":5,""decoder"":""tesseract-long-beam""}", + 10000, 42, 0, 0.743,tesseract-short-beam,cf4a4b0ce0e4c7beec1171f58eddffe403ed7359db5016fca2e16174ea577057,"{""d"":3,""decoder"":""tesseract-short-beam""}", + 10000, 34, 0, 0.924,tesseract-long-beam,8cfa0f2e4061629e13bc98fe213285dc00eb90f21bba36e08c76bcdf213a1c09,"{""d"":3,""decoder"":""tesseract-long-beam""}", + 10000, 10, 0, 0.439,tesseract,8274ea5ffec15d6e71faed5ee1057cdd7e497cbaee4c6109784f8a74669d7f96,"{""d"":5,""decoder"":""tesseract""}", + 10000, 8, 0, 3.93,custom-tesseract-decoder,8e4f5ab5dde00fec74127eea39ea52d5a98ae6ccfc277b5d9be450f78acc1c45,"{""d"":5,""decoder"":""custom-tesseract-decoder""}", + 10000, 10, 0, 5.74,tesseract-short-beam,bf696535d62a25720c3a0c624ec5624002efe3f6cb0468963eee702efb48abc1,"{""d"":5,""decoder"":""tesseract-short-beam""}", + 10000, 5, 0, 1.27,tesseract,3f94c61f1503844df6cf0d200b74ac01bfbc5e29e70cedbfc2faad67047e7887,"{""d"":7,""decoder"":""tesseract""}", + 10000, 4, 0, 25.0,tesseract-long-beam,4d510f0acf511e24a833a93c956b683346696d8086866fadc73063fb09014c23,"{""d"":7,""decoder"":""tesseract-long-beam""}", + 10000, 1, 0, 18.6,tesseract-short-beam,75782ce4593022fcedad4c73104711f05c9c635db92869531f78da336945b121,"{""d"":7,""decoder"":""tesseract-short-beam""}", + 10000, 4, 0, 11.6,custom-tesseract-decoder,48f256a28fff47c58af7bffdf98fdee1d41a721751ee965c5d3c5712ac795dc8,"{""d"":7,""decoder"":""custom-tesseract-decoder""}", +``` + +This example runs simulations for a repetition code with different distances [3, 5, 7] with different Tesseract default decoders. + +Sinter can also be used at the command line. Here is an example of this using Tesseract: + +```bash +sinter collect \ + --circuits "example_circuit.stim" \ + --decoders tesseract \ + --custom_decoders_module_function "tesseract_decoder:make_tesseract_sinter_decoders_dict" \ + --max_shots 100_000 \ + --max_errors 100 + --processes auto \ + --save_resume_filepath "stats.csv" \ +``` + +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`. + ## Good Starting Points for Tesseract Configurations: The [Tesseract paper](https://arxiv.org/pdf/2503.10988) recommends two setup for starting your exploration with tesseract: diff --git a/src/py/tesseract_sinter_compat_test.py b/src/py/tesseract_sinter_compat_test.py index 7c4b554..afa50be 100644 --- a/src/py/tesseract_sinter_compat_test.py +++ b/src/py/tesseract_sinter_compat_test.py @@ -19,7 +19,7 @@ import shutil from sinter._decoding._decoding import sample_decode -from src.tesseract_decoder import tesseract_sinter_compat as tesseract_module +from src.tesseract_decoder import TesseractSinterDecoder, make_tesseract_sinter_decoders_dict from src import tesseract_decoder import sinter @@ -29,7 +29,7 @@ def test_tesseract_sinter_obj_exists(): Sanity check to ensure the decoder object exists and has the required methods. """ - decoder = tesseract_module.TesseractSinterDecoder() + decoder = TesseractSinterDecoder() assert hasattr(decoder, 'compile_decoder_for_dem') assert hasattr(decoder, 'decode_via_files') @@ -51,11 +51,11 @@ def test_compile_decoder_for_dem(use_custom_config): """) if use_custom_config: - config = tesseract_decoder.tesseract.TesseractConfig() - config.verbose = True - decoder = tesseract_module.TesseractSinterDecoder(config=config) + decoder = TesseractSinterDecoder( + verbose=True, + ) else: - decoder = tesseract_module.TesseractSinterDecoder() + decoder = TesseractSinterDecoder() compiled_decoder = decoder.compile_decoder_for_dem(dem=dem) @@ -83,7 +83,7 @@ def test_decode_shots_bit_packed(): error(0.1) D1 D2 L1 """) - decoder = tesseract_module.TesseractSinterDecoder() + decoder = TesseractSinterDecoder() compiled_decoder = decoder.compile_decoder_for_dem(dem=dem) num_shots = 1 @@ -119,7 +119,7 @@ def test_decode_shots_bit_packed_multi_shot(): error(0.1) D1 D2 L1 """) - decoder = tesseract_module.TesseractSinterDecoder() + decoder = TesseractSinterDecoder() compiled_decoder = decoder.compile_decoder_for_dem(dem=dem) num_shots = 3 @@ -184,7 +184,7 @@ def test_decode_via_files_sanity_check(): with open(dets_in_path, 'wb') as f: f.write(detection_events.tobytes()) - tesseract_module.TesseractSinterDecoder().decode_via_files( + TesseractSinterDecoder().decode_via_files( num_shots=num_shots, num_dets=dem.num_detectors, num_obs=dem.num_observables, @@ -247,11 +247,11 @@ def test_decode_via_files(use_custom_config): f.write(detection_events_np.tobytes()) if use_custom_config: - config = tesseract_decoder.tesseract.TesseractConfig() - config.verbose = True - decoder = tesseract_module.TesseractSinterDecoder(config=config) + decoder = TesseractSinterDecoder( + verbose=True, + ) else: - decoder = tesseract_module.TesseractSinterDecoder() + decoder = TesseractSinterDecoder() decoder.decode_via_files( num_shots=num_shots, @@ -280,7 +280,7 @@ def test_decode_via_files(use_custom_config): if temp_dir.exists(): shutil.rmtree(temp_dir) - assert decoder.config.verbose == use_custom_config + assert decoder.verbose == use_custom_config def test_decode_via_files_multi_shot(): @@ -332,7 +332,7 @@ def test_decode_via_files_multi_shot(): with open(dets_in_path, 'wb') as f: f.write(detection_events_np.tobytes()) - tesseract_module.TesseractSinterDecoder().decode_via_files( + TesseractSinterDecoder().decode_via_files( num_shots=num_shots, num_dets=num_detectors, num_obs=dem.num_observables, @@ -378,7 +378,7 @@ def test_sinter_decode_repetition_code(): dem_path=None, num_shots=1000, decoder="tesseract", - custom_decoders=tesseract_module.make_tesseract_sinter_decoders_dict(), + custom_decoders=make_tesseract_sinter_decoders_dict(), ) assert result.discards == 0 assert 0 <= result.errors <= 100 @@ -402,7 +402,7 @@ def test_sinter_decode_surface_code(): dem_obj=circuit.detector_error_model(decompose_errors=True), dem_path=None, decoder="tesseract", - custom_decoders=tesseract_module.make_tesseract_sinter_decoders_dict(), + custom_decoders=make_tesseract_sinter_decoders_dict(), ) assert result.discards == 0 assert 0 <= result.errors <= 50 @@ -421,7 +421,7 @@ def test_sinter_empty(): dem_path=None, num_shots=1000, decoder="tesseract", - custom_decoders=tesseract_module.make_tesseract_sinter_decoders_dict(), + custom_decoders=make_tesseract_sinter_decoders_dict(), ) assert result.discards == 0 assert result.shots == 1000 @@ -444,7 +444,7 @@ def test_sinter_no_observables(): dem_path=None, num_shots=1000, decoder="tesseract", - custom_decoders=tesseract_module.make_tesseract_sinter_decoders_dict(), + custom_decoders=make_tesseract_sinter_decoders_dict(), ) assert result.discards == 0 assert result.shots == 1000 @@ -468,7 +468,7 @@ def test_sinter_invincible_observables(): dem_path=None, num_shots=1000, decoder="tesseract", - custom_decoders=tesseract_module.make_tesseract_sinter_decoders_dict(), + custom_decoders=make_tesseract_sinter_decoders_dict(), ) assert result.discards == 0 assert result.shots == 1000 @@ -497,7 +497,7 @@ def test_sinter_detector_counting(): num_shots=10000, decoder="tesseract", count_detection_events=True, - custom_decoders=tesseract_module.make_tesseract_sinter_decoders_dict(), + custom_decoders=make_tesseract_sinter_decoders_dict(), ) assert result.discards == 0 assert result.custom_counts['detectors_checked'] == 20000 @@ -513,7 +513,7 @@ def test_full_scale(): tasks=[sinter.Task(circuit=stim.Circuit())], decoders=["tesseract"], max_shots=1000, - custom_decoders=tesseract_module.make_tesseract_sinter_decoders_dict(), + custom_decoders=make_tesseract_sinter_decoders_dict(), ) assert result.discards == 0 assert result.shots == 1000 @@ -535,7 +535,7 @@ def test_full_scale_one_worker(): tasks=[sinter.Task(circuit=circuit)], decoders=["tesseract"], max_shots=1000, - custom_decoders=tesseract_module.make_tesseract_sinter_decoders_dict(), + custom_decoders=make_tesseract_sinter_decoders_dict(), ) assert result.discards == 0 @@ -596,20 +596,25 @@ def test_decode_shots_bit_packed_vs_decode_batch(det_beam, beam_climbing, no_rev circuit = relabel_logical_observables(circuit=circuit, relabel_dict={0: 3}) dem = circuit.detector_error_model() - # 2. Create the Tesseract configuration object with the parameterized values. - config = tesseract_decoder.tesseract.TesseractConfig( - dem=dem, + # 2. Compile the Sinter-compatible decoder with the parameterized values for the DEM. + sinter_decoder = TesseractSinterDecoder( + det_beam=det_beam, + beam_climbing=beam_climbing, + no_revisit_dets=no_revisit_dets, + merge_errors=merge_errors, ) - config.det_beam = det_beam - config.beam_climbing = beam_climbing - config.no_revisit_dets = no_revisit_dets - config.merge_errors = merge_errors # 3. Compile the Sinter-compatible decoder. - sinter_decoder = tesseract_module.TesseractSinterDecoder(config=config) compiled_sinter_decoder = sinter_decoder.compile_decoder_for_dem(dem=dem) - # 4. Compile the raw Tesseract decoder directly from the config. + # 4. Obtain the compiled decoder from the config. + config = tesseract_decoder.tesseract.TesseractConfig( + dem=dem, + det_beam=det_beam, + beam_climbing=beam_climbing, + no_revisit_dets=no_revisit_dets, + merge_errors=merge_errors, + ) decoder = config.compile_decoder() # 5. Generate a batch of shots and unpack them for comparison. @@ -630,5 +635,40 @@ def test_decode_shots_bit_packed_vs_decode_batch(det_beam, beam_climbing, no_rev assert np.array_equal(predictions_sinter, predictions_decode_batch) +def test_sinter_collect_different_dems(): + """ + Ensures that Sinter tasks compile with different DEMs before collection. + """ + # Create a repetition code circuit to test the decoder. + min_distance = 3 + max_distance = 7 + tasks = [ + sinter.Task( + circuit=stim.Circuit.generated( + "repetition_code:memory", + distance=d, + rounds=3, + after_clifford_depolarization=0.1, + ), + json_metadata={"d": d}, + ) + for d in range(min_distance, max_distance + 1, 2) + ] + + # Use sinter.collect to run the decoding task. + all_results = sinter.collect( + num_workers=1, + tasks=tasks, + decoders=["tesseract-long-beam"], + max_shots=100, # Reduced max_shots for testing + custom_decoders=make_tesseract_sinter_decoders_dict() + ) + + assert len(all_results) == len(tasks) + expected_distances = [3,5,7] + for i, results in enumerate(all_results): + assert results.json_metadata['d'] == expected_distances[i] + + if __name__ == "__main__": raise SystemExit(pytest.main([__file__])) diff --git a/src/tesseract_sinter_compat.pybind.h b/src/tesseract_sinter_compat.pybind.h index 6dcbcc7..0ad6c89 100644 --- a/src/tesseract_sinter_compat.pybind.h +++ b/src/tesseract_sinter_compat.pybind.h @@ -100,17 +100,59 @@ struct TesseractSinterCompiledDecoder { // a decoder for a specific Detector Error Model (DEM). //-------------------------------------------------------------------------------------------------- struct TesseractSinterDecoder { - // Use TesseractConfig as an integrated property. - TesseractConfig config; + // Parameters for TesseractConfig + int det_beam; + bool beam_climbing; + bool no_revisit_dets; + bool verbose; + bool merge_errors; + size_t pqlimit; + double det_penalty; + bool create_visualization; + + // Parameters for build_det_orders + size_t num_det_orders; + DetOrder det_order_method; + uint64_t seed; // Default constructor - TesseractSinterDecoder() : config(TesseractConfig()) {} - - // Constructor with TesseractConfig parameter - TesseractSinterDecoder(const TesseractConfig& config_in) : config(config_in) {} + TesseractSinterDecoder() + : det_beam(DEFAULT_DET_BEAM), + beam_climbing(false), + no_revisit_dets(true), + verbose(false), + merge_errors(true), + pqlimit(DEFAULT_PQLIMIT), + det_penalty(0.0), + create_visualization(false), + num_det_orders(0), + det_order_method(DetOrder::DetBFS), + seed(2384753) {} + + // 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, + DetOrder det_order_method, uint64_t seed) + : det_beam(det_beam), + beam_climbing(beam_climbing), + no_revisit_dets(no_revisit_dets), + verbose(verbose), + merge_errors(merge_errors), + pqlimit(pqlimit), + det_penalty(det_penalty), + create_visualization(create_visualization), + num_det_orders(num_det_orders), + det_order_method(det_order_method), + seed(seed) {} bool operator==(const TesseractSinterDecoder& other) const { - return true; + return det_beam == other.det_beam && beam_climbing == other.beam_climbing && + 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 && + num_det_orders == other.num_det_orders && det_order_method == other.det_order_method && + seed == other.seed; } bool operator!=(const TesseractSinterDecoder& other) const { @@ -121,8 +163,12 @@ struct TesseractSinterDecoder { TesseractSinterCompiledDecoder compile_decoder_for_dem(const py::object& dem) { const stim::DetectorErrorModel stim_dem(py::cast(py::str(dem)).c_str()); - TesseractConfig local_config = config; - local_config.dem = stim_dem; + 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}; auto decoder = std::make_unique(local_config); return TesseractSinterCompiledDecoder{ @@ -151,9 +197,13 @@ struct TesseractSinterDecoder { dem_file.close(); // Construct TesseractDecoder. - TesseractConfig local_config = config; const stim::DetectorErrorModel stim_dem(dem_content_str.c_str()); - local_config.dem = stim_dem; + 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}; TesseractDecoder decoder(local_config); // Calculate expected number of bytes per shot for detectors and observables. @@ -254,14 +304,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::kw_only(), py::arg("config"), - R"pbdoc( - Initializes a new TesseractSinterDecoder instance with a custom TesseractConfig object. - - :param config: A `TesseractConfig` object to configure the decoder. - )pbdoc") - .def_readwrite("config", &TesseractSinterDecoder::config, - R"pbdoc(The TesseractConfig object for the decoder.)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("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, py::kw_only(), py::arg("dem"), R"pbdoc( @@ -286,34 +339,36 @@ void pybind_sinter_compat(py::module& root) { bit-packed observable predictions will be written. :param tmp_dir: A temporary directory path. (Currently unused, but required by API) )pbdoc") + .def_readwrite("det_beam", &TesseractSinterDecoder::det_beam) + .def_readwrite("beam_climbing", &TesseractSinterDecoder::beam_climbing) + .def_readwrite("no_revisit_dets", &TesseractSinterDecoder::no_revisit_dets) + .def_readwrite("verbose", &TesseractSinterDecoder::verbose) + .def_readwrite("merge_errors", &TesseractSinterDecoder::merge_errors) + .def_readwrite("pqlimit", &TesseractSinterDecoder::pqlimit) + .def_readwrite("det_penalty", &TesseractSinterDecoder::det_penalty) + .def_readwrite("create_visualization", &TesseractSinterDecoder::create_visualization) + .def_readwrite("num_det_orders", &TesseractSinterDecoder::num_det_orders) + .def_readwrite("det_order_method", &TesseractSinterDecoder::det_order_method) + .def_readwrite("seed", &TesseractSinterDecoder::seed) .def(py::self == py::self, R"pbdoc(Checks if two TesseractSinterDecoder instances are equal.)pbdoc") .def(py::self != py::self, 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.verbose, self.config.merge_errors, - self.config.pqlimit, self.config.det_orders, - self.config.det_penalty, self.config.create_visualization); + 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); }, [](py::tuple t) { // __setstate__ - if (t.size() != 10) { + if (t.size() != 11) { throw std::runtime_error("Invalid state for TesseractSinterDecoder!"); } - TesseractConfig config; - config.dem = stim::DetectorErrorModel(t[0].cast()); - config.det_beam = t[1].cast(); - config.beam_climbing = t[2].cast(); - config.no_revisit_dets = t[3].cast(); - config.verbose = t[4].cast(); - config.merge_errors = t[5].cast(); - config.pqlimit = t[6].cast(); - config.det_orders = t[7].cast>>(); - config.det_penalty = t[8].cast(); - config.create_visualization = t[9].cast(); - return TesseractSinterDecoder(config); + 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()); })); // Add a function to create a dictionary of custom decoders @@ -322,10 +377,24 @@ void pybind_sinter_compat(py::module& root) { []() -> py::object { auto result = py::dict(); result["tesseract"] = TesseractSinterDecoder{}; + result["tesseract-long-beam"] = 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, + /*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, + /*num_det_orders=*/16, /*det_order_method=*/DetOrder::DetIndex, /*seed=*/2384753); return result; }, R"pbdoc( Returns a dictionary mapping decoder names to sinter.Decoder-style objects. This allows Sinter to easily discover and use Tesseract as a custom decoder. )pbdoc"); + + // Aliases that are visible from the root module. + root.attr("TesseractSinterDecoder") = m.attr("TesseractSinterDecoder"); + root.attr("make_tesseract_sinter_decoders_dict") = m.attr("make_tesseract_sinter_decoders_dict"); }