Skip to content

Commit af02125

Browse files
authored
Fix for #20 with --det-order-bfs, and update visualizer (#30)
Fix #20 by adding `--det-order-bfs` argument. Apparently it can improve performance for the same LER as well: ``` Before change: bazel build src:all && time ./bazel-bin/src/tesseract --pqlimit 200000 --beam 15 --beam-climbing --num-det-orders 16 --sample-num-shots 10000 --det-order-seed 13267562 --circuit testdata/colorcodes/r\=9\,d\=9\,p\=0.002\,noise\=si1000\,c\=superdense_color_code_X\,q\=121\,gates\=cz.stim --sample-seed 717347 --threads 60 --print-stats num_shots = 10000 num_low_confidence = 0 num_errors = 52 total_time_seconds = 64829.16896500005 After change: bazel build src:all && time ./bazel-bin/src/tesseract --pqlimit 200000 --beam 5 --num-det-orders 20 --sample-num-shots 10000 --det-order-seed 13267562 --circuit testdata/colorcodes/r\=9\,d\=9\,p\=0.002\,noise\=si1000\,c\=superdense_color_code_X\,q\=121\,gates\=cz.stim --sample-seed 717347 --threads 60 --det-order-bfs num_shots = 10000 num_low_confidence = 2 num_errors = 26 total_time_seconds = 46809.56727799983 ``` Plus a few updates for the visualizer, now it should load a default logfile automatically!
1 parent fddbb3f commit af02125

File tree

7 files changed

+357682
-34
lines changed

7 files changed

+357682
-34
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<div align="center">
22

3-
# Tesseract Decoder
3+
# [Tesseract Decoder](https://quantumlib.github.io/tesseract-decoder)
44

55
A Search-Based Decoder for Quantum Error Correction.
66

@@ -48,9 +48,10 @@ We tested the Tesseract decoder for:
4848
* **Detailed Statistics:** provides comprehensive statistics output, including shot counts, error
4949
counts, and processing times.
5050
* **Heuristics**: includes flexible heuristic options: `--beam`, `--det-penalty`,
51-
`--beam-climbing`, `--no-revisit-dets`, `--at-most-two-errors-per-detector` and `--pqlimit` to
51+
`--beam-climbing`, `--no-revisit-dets`, `--at-most-two-errors-per-detector`, `--det-order-bfs` and `--pqlimit` to
5252
improve performance while maintaining a low logical error rate. To learn more about these
5353
options, use `./bazel-bin/src/tesseract --help`
54+
* **Visualization tool:** open the [viz directory](viz/) in your browser to view decoding results. See [viz/README.md](viz/README.md) for instructions on generating the visualization JSON.
5455

5556
## Installation
5657

src/tesseract_main.cc

Lines changed: 82 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
#include <argparse/argparse.hpp>
1616
#include <atomic>
1717
#include <fstream>
18+
#include <queue>
19+
#include <algorithm>
20+
#include <numeric>
1821
#include <nlohmann/json.hpp>
1922
#include <thread>
2023

@@ -30,6 +33,7 @@ struct Args {
3033
// Manifold orientation options
3134
uint64_t det_order_seed;
3235
size_t num_det_orders = 10;
36+
bool det_order_bfs = false;
3337

3438
// Sampling options
3539
size_t sample_num_shots = 0;
@@ -192,46 +196,88 @@ struct Args {
192196
}
193197
}
194198

195-
std::vector<double> inner_products(config.dem.count_detectors());
196-
197-
if (!detector_coords.size() or !detector_coords.at(0).size()) {
198-
// If there are no detector coordinates, just use the standard ordering
199-
// of the indices.
199+
if (det_order_bfs) {
200+
auto graph = build_detector_graph(config.dem);
201+
std::uniform_int_distribution<size_t> dist_det(0, graph.size() - 1);
200202
for (size_t det_order = 0; det_order < num_det_orders; ++det_order) {
201-
config.det_orders.emplace_back();
202-
std::iota(config.det_orders.back().begin(),
203-
config.det_orders.front().end(), 0);
204-
}
205-
} else {
206-
// Use the coordinates to order the detectors based on a random
207-
// orientation
208-
for (size_t det_order = 0; det_order < num_det_orders; ++det_order) {
209-
// Sample a direction
210-
std::vector<double> orientation_vector;
211-
for (size_t i = 0; i < detector_coords.at(0).size(); ++i) {
212-
orientation_vector.push_back(dist(rng));
213-
}
214-
215-
for (size_t i = 0; i < detector_coords.size(); ++i) {
216-
inner_products[i] = 0;
217-
for (size_t j = 0; j < orientation_vector.size(); ++j) {
218-
inner_products[i] +=
219-
detector_coords[i][j] * orientation_vector[j];
203+
std::vector<size_t> perm;
204+
perm.reserve(graph.size());
205+
std::vector<char> visited(graph.size(), false);
206+
std::queue<size_t> q;
207+
size_t start = dist_det(rng);
208+
while (perm.size() < graph.size()) {
209+
if (!visited[start]) {
210+
visited[start] = true;
211+
q.push(start);
212+
perm.push_back(start);
213+
}
214+
while (!q.empty()) {
215+
size_t cur = q.front();
216+
q.pop();
217+
auto neigh = graph[cur];
218+
std::shuffle(neigh.begin(), neigh.end(), rng);
219+
for (size_t n : neigh) {
220+
if (!visited[n]) {
221+
visited[n] = true;
222+
q.push(n);
223+
perm.push_back(n);
224+
}
225+
}
226+
}
227+
if (perm.size() < graph.size()) {
228+
do {
229+
start = dist_det(rng);
230+
} while (visited[start]);
220231
}
221232
}
222-
std::vector<size_t> perm(config.dem.count_detectors());
223-
std::iota(perm.begin(), perm.end(), 0);
224-
std::sort(perm.begin(), perm.end(),
225-
[&](const size_t& i, const size_t& j) {
226-
return inner_products[i] > inner_products[j];
227-
});
228-
// Invert the permutation
229-
std::vector<size_t> inv_perm(config.dem.count_detectors());
233+
std::vector<size_t> inv_perm(graph.size());
230234
for (size_t i = 0; i < perm.size(); ++i) {
231235
inv_perm[perm[i]] = i;
232236
}
233237
config.det_orders[det_order] = inv_perm;
234238
}
239+
} else {
240+
std::vector<double> inner_products(config.dem.count_detectors());
241+
242+
if (!detector_coords.size() || !detector_coords.at(0).size()) {
243+
// If there are no detector coordinates, just use the standard ordering
244+
// of the indices.
245+
for (size_t det_order = 0; det_order < num_det_orders; ++det_order) {
246+
config.det_orders.emplace_back();
247+
std::iota(config.det_orders.back().begin(),
248+
config.det_orders.front().end(), 0);
249+
}
250+
} else {
251+
// Use the coordinates to order the detectors based on a random
252+
// orientation
253+
for (size_t det_order = 0; det_order < num_det_orders; ++det_order) {
254+
// Sample a direction
255+
std::vector<double> orientation_vector;
256+
for (size_t i = 0; i < detector_coords.at(0).size(); ++i) {
257+
orientation_vector.push_back(dist(rng));
258+
}
259+
260+
for (size_t i = 0; i < detector_coords.size(); ++i) {
261+
inner_products[i] = 0;
262+
for (size_t j = 0; j < orientation_vector.size(); ++j) {
263+
inner_products[i] +=
264+
detector_coords[i][j] * orientation_vector[j];
265+
}
266+
}
267+
std::vector<size_t> perm(config.dem.count_detectors());
268+
std::iota(perm.begin(), perm.end(), 0);
269+
std::sort(perm.begin(), perm.end(),
270+
[&](const size_t& i, const size_t& j) {
271+
return inner_products[i] > inner_products[j];
272+
});
273+
// Invert the permutation
274+
std::vector<size_t> inv_perm(config.dem.count_detectors());
275+
for (size_t i = 0; i < perm.size(); ++i) {
276+
inv_perm[perm[i]] = i;
277+
}
278+
config.det_orders[det_order] = inv_perm;
279+
}
280+
}
235281
}
236282
}
237283

@@ -359,6 +405,10 @@ int main(int argc, char* argv[]) {
359405
.metavar("N")
360406
.default_value(size_t(1))
361407
.store_into(args.num_det_orders);
408+
program.add_argument("--det-order-bfs")
409+
.help("Use BFS-based detector ordering instead of geometric orientation")
410+
.flag()
411+
.store_into(args.det_order_bfs);
362412
program.add_argument("--det-order-seed")
363413
.help(
364414
"Seed used when initializing the random detector traversal "

src/utils.cc

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,36 @@ std::vector<std::vector<double>> get_detector_coords(
5151
return detector_coords;
5252
}
5353

54+
std::vector<std::vector<size_t>> build_detector_graph(
55+
const stim::DetectorErrorModel& dem) {
56+
size_t num_detectors = dem.count_detectors();
57+
std::vector<std::vector<size_t>> neighbors(num_detectors);
58+
for (const stim::DemInstruction& instruction : dem.flattened().instructions) {
59+
if (instruction.type != stim::DemInstructionType::DEM_ERROR) {
60+
continue;
61+
}
62+
std::vector<int> dets;
63+
for (const stim::DemTarget& target : instruction.target_data) {
64+
if (target.is_relative_detector_id()) {
65+
dets.push_back(target.val());
66+
}
67+
}
68+
for (size_t i = 0; i < dets.size(); ++i) {
69+
for (size_t j = i + 1; j < dets.size(); ++j) {
70+
size_t a = dets[i];
71+
size_t b = dets[j];
72+
neighbors[a].push_back(b);
73+
neighbors[b].push_back(a);
74+
}
75+
}
76+
}
77+
for (auto& neigh : neighbors) {
78+
std::sort(neigh.begin(), neigh.end());
79+
neigh.erase(std::unique(neigh.begin(), neigh.end()), neigh.end());
80+
}
81+
return neighbors;
82+
}
83+
5484
bool sampling_from_dem(uint64_t seed, size_t num_shots,
5585
stim::DetectorErrorModel dem,
5686
std::vector<stim::SparseShot>& shots) {

src/utils.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ constexpr const double EPSILON = 1e-7;
3131
std::vector<std::vector<double>> get_detector_coords(
3232
stim::DetectorErrorModel& dem);
3333

34+
// Builds an adjacency list graph where two detectors share an edge iff an error
35+
// in the model activates them both.
36+
std::vector<std::vector<size_t>> build_detector_graph(
37+
const stim::DetectorErrorModel& dem);
38+
3439
const double INF = std::numeric_limits<double>::infinity();
3540

3641
bool sampling_from_dem(uint64_t seed, size_t num_shots,

viz/README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Visualization
2+
3+
This tool displays the detectors and errors from a Tesseract decoding run in 3D.
4+
5+
## Generating the JSON
6+
7+
Use the `--verbose` flag when running the decoder and filter the output to
8+
include only the lines used by the converter script:
9+
10+
```bash
11+
bazel build src:all && \
12+
./bazel-bin/src/tesseract \
13+
--sample-num-shots 1 --det-order-seed 13267562 --pqlimit 10000 --beam 1 --num-det-orders 20 --det-order-bfs \
14+
--circuit testdata/colorcodes/r\=9\,d\=9\,p\=0.002\,noise\=si1000\,c\=superdense_color_code_X\,q\=121\,gates\=cz.stim \
15+
--sample-seed 717347 --threads 1 --verbose | \
16+
grep -E 'Error|Detector|activated_errors|activated_dets' > logfile.txt
17+
18+
python viz/to_json.py logfile.txt -o logfile.json
19+
```
20+
21+
22+
The `--det-order-bfs` flag is compatible with visualization logs. Just make
23+
sure `--verbose` is enabled so the detector coordinates are printed for
24+
`to_json.py` to parse.
25+
26+
The `to_json.py` script produces `logfile.json`, which contains the detector
27+
coordinates and animation frames for the viewer.
28+
29+
## Viewing
30+
31+
Open `viz/index.html` in a modern browser. It will automatically try to load
32+
`logfile.json` from the same directory. If the file picker is used, any JSON
33+
produced by `to_json.py` can be visualized.
34+

viz/index.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,18 @@
314314
if (playing && frames.length) restartAnimationTimer();
315315
else clearInterval(animationTimer);
316316
}
317+
318+
// Try to load a default logfile when the page is opened.
319+
window.addEventListener('load', () => {
320+
fetch('logfile.json')
321+
.then(r => r.ok ? r.json() : Promise.reject())
322+
.then(data => initializeScene(
323+
data.detectorCoords,
324+
data.errorCoords,
325+
data.frames,
326+
data.errorToDetectors))
327+
.catch(() => {/* ignore if not present */});
328+
});
317329
</script>
318330
</body>
319331
</html>

0 commit comments

Comments
 (0)