Skip to content

Commit 2168aa5

Browse files
noajshudandragona-dev
authored andcommitted
Update BUILD to remove "-march=native", for macOS builds
1 parent da5cc0f commit 2168aa5

File tree

3 files changed

+218
-53
lines changed

3 files changed

+218
-53
lines changed

README.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,66 @@ print(f"Predicted errors indices: {predicted_errors}")
212212
for i in predicted_errors:
213213
print(f" {i}: {decoder.errors[i]}")
214214
```
215+
## Using Tesseract with Sinter
216+
217+
Tesseract can be easily integrated into [Sinter](https://github.com/quantumlib/Sinter) workflows. Sinter is a tool for running and organizing quantum error correction simulations. The `tesseract_sinter_compat` module provides the necessary interface.
218+
219+
Here's an example of how to use Tesseract as a decoder for multiple Sinter tasks:
220+
221+
```python
222+
import stim
223+
import sinter
224+
from tesseract_decoder import tesseract_sinter_compat
225+
226+
# Define a list of Sinter task(s) with different circuits/decoders.
227+
tasks = []
228+
# These are the sensible defaults given by tesseract_module.make_tesseract_sinter_decoders_dict().
229+
decoders = ['tesseract', 'tesseract-long-beam', 'tesseract-short-beam']
230+
for i, distance in enumerate([3, 5, 7]):
231+
circuit = stim.Circuit.generated(
232+
"repetition_code:memory",
233+
distance=distance,
234+
rounds=3,
235+
after_clifford_depolarization=0.1
236+
)
237+
tasks.append(sinter.Task(
238+
circuit=circuit,
239+
decoder=decoders[i],
240+
json_metadata={"d": distance, "decoder": decoders[i]},
241+
))
242+
243+
# Collect decoding outcomes per task from Sinter.
244+
results = sinter.collect(
245+
num_workers=2,
246+
tasks=tasks,
247+
max_shots=10000,
248+
decoders=decoders,
249+
custom_decoders=tesseract_module.make_tesseract_sinter_decoders_dict(),
250+
)
251+
252+
for result in results:
253+
print(f"task metadata = {result.json_metadata}")
254+
print(f" Shots run: {result.shots}")
255+
print(f" Observed errors: {result.errors}")
256+
print(f" Logical error rate: {result.errors / result.shots}")
257+
258+
# Should get something like:
259+
# task metadata = {'d': 5, 'decoder': 'tesseract-long-beam'}
260+
# Shots run: 10000
261+
# Observed errors: 315
262+
# Logical error rate: 0.0315
263+
# task metadata = {'d': 3, 'decoder': 'tesseract'}
264+
# Shots run: 10000
265+
# Observed errors: 654
266+
# Logical error rate: 0.0654
267+
# task metadata = {'d': 7, 'decoder': 'tesseract-short-beam'}
268+
# Shots run: 10000
269+
# Observed errors: 153
270+
# Logical error rate: 0.0153
271+
```
272+
273+
This example runs simulations for a repetition code with different distances [3, 5, 7] with different Tesseract default decoders. 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`.
274+
215275
## Good Starting Points for Tesseract Configurations:
216276
The [Tesseract paper](https://arxiv.org/pdf/2503.10988) recommends two setup for starting your exploration with tesseract:
217277

src/py/tesseract_sinter_compat_test.py

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ def test_compile_decoder_for_dem(use_custom_config):
5151
""")
5252

5353
if use_custom_config:
54-
config = tesseract_decoder.tesseract.TesseractConfig()
55-
config.verbose = True
56-
decoder = tesseract_module.TesseractSinterDecoder(config=config)
54+
decoder = tesseract_module.TesseractSinterDecoder(
55+
verbose=True,
56+
)
5757
else:
5858
decoder = tesseract_module.TesseractSinterDecoder()
5959

@@ -247,9 +247,9 @@ def test_decode_via_files(use_custom_config):
247247
f.write(detection_events_np.tobytes())
248248

249249
if use_custom_config:
250-
config = tesseract_decoder.tesseract.TesseractConfig()
251-
config.verbose = True
252-
decoder = tesseract_module.TesseractSinterDecoder(config=config)
250+
decoder = tesseract_module.TesseractSinterDecoder(
251+
verbose=True,
252+
)
253253
else:
254254
decoder = tesseract_module.TesseractSinterDecoder()
255255

@@ -280,7 +280,7 @@ def test_decode_via_files(use_custom_config):
280280
if temp_dir.exists():
281281
shutil.rmtree(temp_dir)
282282

283-
assert decoder.config.verbose == use_custom_config
283+
assert decoder.verbose == use_custom_config
284284

285285

286286
def test_decode_via_files_multi_shot():
@@ -596,20 +596,25 @@ def test_decode_shots_bit_packed_vs_decode_batch(det_beam, beam_climbing, no_rev
596596
circuit = relabel_logical_observables(circuit=circuit, relabel_dict={0: 3})
597597
dem = circuit.detector_error_model()
598598

599-
# 2. Create the Tesseract configuration object with the parameterized values.
600-
config = tesseract_decoder.tesseract.TesseractConfig(
601-
dem=dem,
599+
# 2. Compile the Sinter-compatible decoder with the parameterized values for the DEM.
600+
sinter_decoder = tesseract_module.TesseractSinterDecoder(
601+
det_beam=det_beam,
602+
beam_climbing=beam_climbing,
603+
no_revisit_dets=no_revisit_dets,
604+
merge_errors=merge_errors,
602605
)
603-
config.det_beam = det_beam
604-
config.beam_climbing = beam_climbing
605-
config.no_revisit_dets = no_revisit_dets
606-
config.merge_errors = merge_errors
607606

608607
# 3. Compile the Sinter-compatible decoder.
609-
sinter_decoder = tesseract_module.TesseractSinterDecoder(config=config)
610608
compiled_sinter_decoder = sinter_decoder.compile_decoder_for_dem(dem=dem)
611609

612-
# 4. Compile the raw Tesseract decoder directly from the config.
610+
# 4. Obtain the compiled decoder from the config.
611+
config = tesseract_decoder.tesseract.TesseractConfig(
612+
dem=dem,
613+
det_beam=det_beam,
614+
beam_climbing=beam_climbing,
615+
no_revisit_dets=no_revisit_dets,
616+
merge_errors=merge_errors,
617+
)
613618
decoder = config.compile_decoder()
614619

615620
# 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
630635
assert np.array_equal(predictions_sinter, predictions_decode_batch)
631636

632637

638+
def test_sinter_collect_different_dems():
639+
"""
640+
Ensures that Sinter tasks compile with different DEMs before collection.
641+
"""
642+
# Create a repetition code circuit to test the decoder.
643+
min_distance = 3
644+
max_distance = 7
645+
tasks = [
646+
sinter.Task(
647+
circuit=stim.Circuit.generated(
648+
"repetition_code:memory",
649+
distance=d,
650+
rounds=3,
651+
after_clifford_depolarization=0.1,
652+
),
653+
json_metadata={"d": d},
654+
)
655+
for d in range(min_distance, max_distance + 1, 2)
656+
]
657+
658+
# Use sinter.collect to run the decoding task.
659+
all_results = sinter.collect(
660+
num_workers=1,
661+
tasks=tasks,
662+
decoders=["tesseract-long-beam"],
663+
max_shots=100, # Reduced max_shots for testing
664+
custom_decoders=tesseract_module.make_tesseract_sinter_decoders_dict()
665+
)
666+
667+
assert len(all_results) == len(tasks)
668+
expected_distances = [3,5,7]
669+
for i, results in enumerate(all_results):
670+
assert results.json_metadata['d'] == expected_distances[i]
671+
672+
633673
if __name__ == "__main__":
634674
raise SystemExit(pytest.main([__file__]))

src/tesseract_sinter_compat.pybind.h

Lines changed: 102 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -100,17 +100,59 @@ struct TesseractSinterCompiledDecoder {
100100
// a decoder for a specific Detector Error Model (DEM).
101101
//--------------------------------------------------------------------------------------------------
102102
struct TesseractSinterDecoder {
103-
// Use TesseractConfig as an integrated property.
104-
TesseractConfig config;
103+
// Parameters for TesseractConfig
104+
int det_beam;
105+
bool beam_climbing;
106+
bool no_revisit_dets;
107+
bool verbose;
108+
bool merge_errors;
109+
size_t pqlimit;
110+
double det_penalty;
111+
bool create_visualization;
112+
113+
// Parameters for build_det_orders
114+
size_t num_det_orders;
115+
DetOrder det_order_method;
116+
uint64_t seed;
105117

106118
// Default constructor
107-
TesseractSinterDecoder() : config(TesseractConfig()) {}
108-
109-
// Constructor with TesseractConfig parameter
110-
TesseractSinterDecoder(const TesseractConfig& config_in) : config(config_in) {}
119+
TesseractSinterDecoder()
120+
: det_beam(DEFAULT_DET_BEAM),
121+
beam_climbing(false),
122+
no_revisit_dets(true),
123+
verbose(false),
124+
merge_errors(true),
125+
pqlimit(DEFAULT_PQLIMIT),
126+
det_penalty(0.0),
127+
create_visualization(false),
128+
num_det_orders(0),
129+
det_order_method(DetOrder::DetBFS),
130+
seed(2384753) {}
131+
132+
// Constructor with parameters
133+
TesseractSinterDecoder(int det_beam, bool beam_climbing, bool no_revisit_dets, bool verbose,
134+
bool merge_errors, size_t pqlimit, double det_penalty,
135+
bool create_visualization, size_t num_det_orders,
136+
DetOrder det_order_method, uint64_t seed)
137+
: det_beam(det_beam),
138+
beam_climbing(beam_climbing),
139+
no_revisit_dets(no_revisit_dets),
140+
verbose(verbose),
141+
merge_errors(merge_errors),
142+
pqlimit(pqlimit),
143+
det_penalty(det_penalty),
144+
create_visualization(create_visualization),
145+
num_det_orders(num_det_orders),
146+
det_order_method(det_order_method),
147+
seed(seed) {}
111148

112149
bool operator==(const TesseractSinterDecoder& other) const {
113-
return true;
150+
return det_beam == other.det_beam && beam_climbing == other.beam_climbing &&
151+
no_revisit_dets == other.no_revisit_dets && verbose == other.verbose &&
152+
merge_errors == other.merge_errors && pqlimit == other.pqlimit &&
153+
det_penalty == other.det_penalty && create_visualization == other.create_visualization &&
154+
num_det_orders == other.num_det_orders && det_order_method == other.det_order_method &&
155+
seed == other.seed;
114156
}
115157

116158
bool operator!=(const TesseractSinterDecoder& other) const {
@@ -121,8 +163,12 @@ struct TesseractSinterDecoder {
121163
TesseractSinterCompiledDecoder compile_decoder_for_dem(const py::object& dem) {
122164
const stim::DetectorErrorModel stim_dem(py::cast<std::string>(py::str(dem)).c_str());
123165

124-
TesseractConfig local_config = config;
125-
local_config.dem = stim_dem;
166+
std::vector<std::vector<size_t>> det_orders =
167+
build_det_orders(stim_dem, num_det_orders, det_order_method, seed);
168+
169+
TesseractConfig local_config = {
170+
stim_dem, det_beam, beam_climbing, no_revisit_dets, verbose,
171+
merge_errors, pqlimit, det_orders, det_penalty, create_visualization};
126172
auto decoder = std::make_unique<TesseractDecoder>(local_config);
127173

128174
return TesseractSinterCompiledDecoder{
@@ -151,9 +197,13 @@ struct TesseractSinterDecoder {
151197
dem_file.close();
152198

153199
// Construct TesseractDecoder.
154-
TesseractConfig local_config = config;
155200
const stim::DetectorErrorModel stim_dem(dem_content_str.c_str());
156-
local_config.dem = stim_dem;
201+
std::vector<std::vector<size_t>> det_orders =
202+
build_det_orders(stim_dem, num_det_orders, det_order_method, seed);
203+
204+
TesseractConfig local_config = {
205+
stim_dem, det_beam, beam_climbing, no_revisit_dets, verbose,
206+
merge_errors, pqlimit, det_orders, det_penalty, create_visualization};
157207
TesseractDecoder decoder(local_config);
158208

159209
// Calculate expected number of bytes per shot for detectors and observables.
@@ -254,14 +304,17 @@ void pybind_sinter_compat(py::module& root) {
254304
.def(py::init<>(), R"pbdoc(
255305
Initializes a new TesseractSinterDecoder instance with a default TesseractConfig.
256306
)pbdoc")
257-
.def(py::init<const TesseractConfig&>(), py::kw_only(), py::arg("config"),
258-
R"pbdoc(
259-
Initializes a new TesseractSinterDecoder instance with a custom TesseractConfig object.
260-
261-
:param config: A `TesseractConfig` object to configure the decoder.
262-
)pbdoc")
263-
.def_readwrite("config", &TesseractSinterDecoder::config,
264-
R"pbdoc(The TesseractConfig object for the decoder.)pbdoc")
307+
.def(
308+
py::init<int, bool, bool, bool, bool, size_t, double, bool, size_t, DetOrder, uint64_t>(),
309+
py::arg("det_beam") = DEFAULT_DET_BEAM, py::arg("beam_climbing") = false,
310+
py::arg("no_revisit_dets") = true, py::arg("verbose") = false,
311+
py::arg("merge_errors") = true, py::arg("pqlimit") = DEFAULT_PQLIMIT,
312+
py::arg("det_penalty") = 0.0, py::arg("create_visualization") = false,
313+
py::arg("num_det_orders") = 0, py::arg("det_order_method") = DetOrder::DetBFS,
314+
py::arg("seed") = 2384753,
315+
R"pbdoc(
316+
Initializes a new TesseractSinterDecoder instance with custom TesseractConfig parameters.
317+
)pbdoc")
265318
.def("compile_decoder_for_dem", &TesseractSinterDecoder::compile_decoder_for_dem,
266319
py::kw_only(), py::arg("dem"),
267320
R"pbdoc(
@@ -286,34 +339,36 @@ void pybind_sinter_compat(py::module& root) {
286339
bit-packed observable predictions will be written.
287340
:param tmp_dir: A temporary directory path. (Currently unused, but required by API)
288341
)pbdoc")
342+
.def_readwrite("det_beam", &TesseractSinterDecoder::det_beam)
343+
.def_readwrite("beam_climbing", &TesseractSinterDecoder::beam_climbing)
344+
.def_readwrite("no_revisit_dets", &TesseractSinterDecoder::no_revisit_dets)
345+
.def_readwrite("verbose", &TesseractSinterDecoder::verbose)
346+
.def_readwrite("merge_errors", &TesseractSinterDecoder::merge_errors)
347+
.def_readwrite("pqlimit", &TesseractSinterDecoder::pqlimit)
348+
.def_readwrite("det_penalty", &TesseractSinterDecoder::det_penalty)
349+
.def_readwrite("create_visualization", &TesseractSinterDecoder::create_visualization)
350+
.def_readwrite("num_det_orders", &TesseractSinterDecoder::num_det_orders)
351+
.def_readwrite("det_order_method", &TesseractSinterDecoder::det_order_method)
352+
.def_readwrite("seed", &TesseractSinterDecoder::seed)
289353
.def(py::self == py::self,
290354
R"pbdoc(Checks if two TesseractSinterDecoder instances are equal.)pbdoc")
291355
.def(py::self != py::self,
292356
R"pbdoc(Checks if two TesseractSinterDecoder instances are not equal.)pbdoc")
293357
.def(py::pickle(
294358
[](const TesseractSinterDecoder& self) -> py::tuple { // __getstate__
295-
return py::make_tuple(std::string(self.config.dem.str()), self.config.det_beam,
296-
self.config.beam_climbing, self.config.no_revisit_dets,
297-
self.config.verbose, self.config.merge_errors,
298-
self.config.pqlimit, self.config.det_orders,
299-
self.config.det_penalty, self.config.create_visualization);
359+
return py::make_tuple(self.det_beam, self.beam_climbing, self.no_revisit_dets,
360+
self.verbose, self.merge_errors, self.pqlimit, self.det_penalty,
361+
self.create_visualization, self.num_det_orders,
362+
self.det_order_method, self.seed);
300363
},
301364
[](py::tuple t) { // __setstate__
302-
if (t.size() != 10) {
365+
if (t.size() != 11) {
303366
throw std::runtime_error("Invalid state for TesseractSinterDecoder!");
304367
}
305-
TesseractConfig config;
306-
config.dem = stim::DetectorErrorModel(t[0].cast<std::string>());
307-
config.det_beam = t[1].cast<int>();
308-
config.beam_climbing = t[2].cast<bool>();
309-
config.no_revisit_dets = t[3].cast<bool>();
310-
config.verbose = t[4].cast<bool>();
311-
config.merge_errors = t[5].cast<bool>();
312-
config.pqlimit = t[6].cast<size_t>();
313-
config.det_orders = t[7].cast<std::vector<std::vector<size_t>>>();
314-
config.det_penalty = t[8].cast<double>();
315-
config.create_visualization = t[9].cast<bool>();
316-
return TesseractSinterDecoder(config);
368+
return TesseractSinterDecoder(
369+
t[0].cast<int>(), t[1].cast<bool>(), t[2].cast<bool>(), t[3].cast<bool>(),
370+
t[4].cast<bool>(), t[5].cast<size_t>(), t[6].cast<double>(), t[7].cast<bool>(),
371+
t[8].cast<size_t>(), t[9].cast<DetOrder>(), t[10].cast<uint64_t>());
317372
}));
318373

319374
// Add a function to create a dictionary of custom decoders
@@ -322,6 +377,16 @@ void pybind_sinter_compat(py::module& root) {
322377
[]() -> py::object {
323378
auto result = py::dict();
324379
result["tesseract"] = TesseractSinterDecoder{};
380+
result["tesseract-short-beam"] = TesseractSinterDecoder(
381+
/*det_beam=*/10, /*beam_climbing=*/false, /*no_revisit_dets=*/true,
382+
/*verbose=*/false, /*merge_errors=*/true, /*pqlimit=*/DEFAULT_PQLIMIT,
383+
/*det_penalty=*/0.0, /*create_visualization=*/false,
384+
/*num_det_orders=*/0, /*det_order_method=*/DetOrder::DetBFS, /*seed=*/2384753);
385+
result["tesseract-long-beam"] = TesseractSinterDecoder(
386+
/*det_beam=*/1000, /*beam_climbing=*/false, /*no_revisit_dets=*/true,
387+
/*verbose=*/false, /*merge_errors=*/true, /*pqlimit=*/DEFAULT_PQLIMIT,
388+
/*det_penalty=*/0.0, /*create_visualization=*/false,
389+
/*num_det_orders=*/0, /*det_order_method=*/DetOrder::DetBFS, /*seed=*/2384753);
325390
return result;
326391
},
327392
R"pbdoc(

0 commit comments

Comments
 (0)