diff --git a/README.md b/README.md
index 9155ecb..bdc9e25 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-# Tesseract Decoder
+# [Tesseract Decoder](https://quantumlib.github.io/tesseract-decoder)
A Search-Based Decoder for Quantum Error Correction.
@@ -48,9 +48,10 @@ We tested the Tesseract decoder for:
* **Detailed Statistics:** provides comprehensive statistics output, including shot counts, error
counts, and processing times.
* **Heuristics**: includes flexible heuristic options: `--beam`, `--det-penalty`,
- `--beam-climbing`, `--no-revisit-dets`, `--at-most-two-errors-per-detector` and `--pqlimit` to
+ `--beam-climbing`, `--no-revisit-dets`, `--at-most-two-errors-per-detector`, `--det-order-bfs` and `--pqlimit` to
improve performance while maintaining a low logical error rate. To learn more about these
options, use `./bazel-bin/src/tesseract --help`
+* **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.
## Installation
diff --git a/src/tesseract_main.cc b/src/tesseract_main.cc
index d4ab226..496a7c0 100644
--- a/src/tesseract_main.cc
+++ b/src/tesseract_main.cc
@@ -15,6 +15,9 @@
#include
#include
#include
+#include
+#include
+#include
#include
#include
@@ -30,6 +33,7 @@ struct Args {
// Manifold orientation options
uint64_t det_order_seed;
size_t num_det_orders = 10;
+ bool det_order_bfs = false;
// Sampling options
size_t sample_num_shots = 0;
@@ -192,46 +196,88 @@ struct Args {
}
}
- std::vector inner_products(config.dem.count_detectors());
-
- if (!detector_coords.size() or !detector_coords.at(0).size()) {
- // If there are no detector coordinates, just use the standard ordering
- // of the indices.
+ if (det_order_bfs) {
+ auto graph = build_detector_graph(config.dem);
+ std::uniform_int_distribution dist_det(0, graph.size() - 1);
for (size_t det_order = 0; det_order < num_det_orders; ++det_order) {
- config.det_orders.emplace_back();
- std::iota(config.det_orders.back().begin(),
- config.det_orders.front().end(), 0);
- }
- } else {
- // Use the coordinates to order the detectors based on a random
- // orientation
- for (size_t det_order = 0; det_order < num_det_orders; ++det_order) {
- // Sample a direction
- std::vector orientation_vector;
- for (size_t i = 0; i < detector_coords.at(0).size(); ++i) {
- orientation_vector.push_back(dist(rng));
- }
-
- for (size_t i = 0; i < detector_coords.size(); ++i) {
- inner_products[i] = 0;
- for (size_t j = 0; j < orientation_vector.size(); ++j) {
- inner_products[i] +=
- detector_coords[i][j] * orientation_vector[j];
+ std::vector perm;
+ perm.reserve(graph.size());
+ std::vector visited(graph.size(), false);
+ std::queue q;
+ size_t start = dist_det(rng);
+ while (perm.size() < graph.size()) {
+ if (!visited[start]) {
+ visited[start] = true;
+ q.push(start);
+ perm.push_back(start);
+ }
+ while (!q.empty()) {
+ size_t cur = q.front();
+ q.pop();
+ auto neigh = graph[cur];
+ std::shuffle(neigh.begin(), neigh.end(), rng);
+ for (size_t n : neigh) {
+ if (!visited[n]) {
+ visited[n] = true;
+ q.push(n);
+ perm.push_back(n);
+ }
+ }
+ }
+ if (perm.size() < graph.size()) {
+ do {
+ start = dist_det(rng);
+ } while (visited[start]);
}
}
- std::vector perm(config.dem.count_detectors());
- std::iota(perm.begin(), perm.end(), 0);
- std::sort(perm.begin(), perm.end(),
- [&](const size_t& i, const size_t& j) {
- return inner_products[i] > inner_products[j];
- });
- // Invert the permutation
- std::vector inv_perm(config.dem.count_detectors());
+ std::vector inv_perm(graph.size());
for (size_t i = 0; i < perm.size(); ++i) {
inv_perm[perm[i]] = i;
}
config.det_orders[det_order] = inv_perm;
}
+ } else {
+ std::vector inner_products(config.dem.count_detectors());
+
+ if (!detector_coords.size() || !detector_coords.at(0).size()) {
+ // If there are no detector coordinates, just use the standard ordering
+ // of the indices.
+ for (size_t det_order = 0; det_order < num_det_orders; ++det_order) {
+ config.det_orders.emplace_back();
+ std::iota(config.det_orders.back().begin(),
+ config.det_orders.front().end(), 0);
+ }
+ } else {
+ // Use the coordinates to order the detectors based on a random
+ // orientation
+ for (size_t det_order = 0; det_order < num_det_orders; ++det_order) {
+ // Sample a direction
+ std::vector orientation_vector;
+ for (size_t i = 0; i < detector_coords.at(0).size(); ++i) {
+ orientation_vector.push_back(dist(rng));
+ }
+
+ for (size_t i = 0; i < detector_coords.size(); ++i) {
+ inner_products[i] = 0;
+ for (size_t j = 0; j < orientation_vector.size(); ++j) {
+ inner_products[i] +=
+ detector_coords[i][j] * orientation_vector[j];
+ }
+ }
+ std::vector perm(config.dem.count_detectors());
+ std::iota(perm.begin(), perm.end(), 0);
+ std::sort(perm.begin(), perm.end(),
+ [&](const size_t& i, const size_t& j) {
+ return inner_products[i] > inner_products[j];
+ });
+ // Invert the permutation
+ std::vector inv_perm(config.dem.count_detectors());
+ for (size_t i = 0; i < perm.size(); ++i) {
+ inv_perm[perm[i]] = i;
+ }
+ config.det_orders[det_order] = inv_perm;
+ }
+ }
}
}
@@ -359,6 +405,10 @@ int main(int argc, char* argv[]) {
.metavar("N")
.default_value(size_t(1))
.store_into(args.num_det_orders);
+ program.add_argument("--det-order-bfs")
+ .help("Use BFS-based detector ordering instead of geometric orientation")
+ .flag()
+ .store_into(args.det_order_bfs);
program.add_argument("--det-order-seed")
.help(
"Seed used when initializing the random detector traversal "
diff --git a/src/utils.cc b/src/utils.cc
index 10bca4d..f3081d3 100644
--- a/src/utils.cc
+++ b/src/utils.cc
@@ -51,6 +51,36 @@ std::vector> get_detector_coords(
return detector_coords;
}
+std::vector> build_detector_graph(
+ const stim::DetectorErrorModel& dem) {
+ size_t num_detectors = dem.count_detectors();
+ std::vector> neighbors(num_detectors);
+ for (const stim::DemInstruction& instruction : dem.flattened().instructions) {
+ if (instruction.type != stim::DemInstructionType::DEM_ERROR) {
+ continue;
+ }
+ std::vector dets;
+ for (const stim::DemTarget& target : instruction.target_data) {
+ if (target.is_relative_detector_id()) {
+ dets.push_back(target.val());
+ }
+ }
+ for (size_t i = 0; i < dets.size(); ++i) {
+ for (size_t j = i + 1; j < dets.size(); ++j) {
+ size_t a = dets[i];
+ size_t b = dets[j];
+ neighbors[a].push_back(b);
+ neighbors[b].push_back(a);
+ }
+ }
+ }
+ for (auto& neigh : neighbors) {
+ std::sort(neigh.begin(), neigh.end());
+ neigh.erase(std::unique(neigh.begin(), neigh.end()), neigh.end());
+ }
+ return neighbors;
+}
+
bool sampling_from_dem(uint64_t seed, size_t num_shots,
stim::DetectorErrorModel dem,
std::vector& shots) {
diff --git a/src/utils.h b/src/utils.h
index 8b7fb2f..52cd787 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -31,6 +31,11 @@ constexpr const double EPSILON = 1e-7;
std::vector> get_detector_coords(
stim::DetectorErrorModel& dem);
+// Builds an adjacency list graph where two detectors share an edge iff an error
+// in the model activates them both.
+std::vector> build_detector_graph(
+ const stim::DetectorErrorModel& dem);
+
const double INF = std::numeric_limits::infinity();
bool sampling_from_dem(uint64_t seed, size_t num_shots,
diff --git a/viz/README.md b/viz/README.md
new file mode 100644
index 0000000..9ab04d7
--- /dev/null
+++ b/viz/README.md
@@ -0,0 +1,34 @@
+# Visualization
+
+This tool displays the detectors and errors from a Tesseract decoding run in 3D.
+
+## Generating the JSON
+
+Use the `--verbose` flag when running the decoder and filter the output to
+include only the lines used by the converter script:
+
+```bash
+bazel build src:all && \
+./bazel-bin/src/tesseract \
+ --sample-num-shots 1 --det-order-seed 13267562 --pqlimit 10000 --beam 1 --num-det-orders 20 --det-order-bfs \
+ --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 1 --verbose | \
+ grep -E 'Error|Detector|activated_errors|activated_dets' > logfile.txt
+
+python viz/to_json.py logfile.txt -o logfile.json
+```
+
+
+The `--det-order-bfs` flag is compatible with visualization logs. Just make
+sure `--verbose` is enabled so the detector coordinates are printed for
+`to_json.py` to parse.
+
+The `to_json.py` script produces `logfile.json`, which contains the detector
+coordinates and animation frames for the viewer.
+
+## Viewing
+
+Open `viz/index.html` in a modern browser. It will automatically try to load
+`logfile.json` from the same directory. If the file picker is used, any JSON
+produced by `to_json.py` can be visualized.
+
diff --git a/viz/index.html b/viz/index.html
index 39521b0..698488f 100644
--- a/viz/index.html
+++ b/viz/index.html
@@ -314,6 +314,18 @@
if (playing && frames.length) restartAnimationTimer();
else clearInterval(animationTimer);
}
+
+// Try to load a default logfile when the page is opened.
+window.addEventListener('load', () => {
+ fetch('logfile.json')
+ .then(r => r.ok ? r.json() : Promise.reject())
+ .then(data => initializeScene(
+ data.detectorCoords,
+ data.errorCoords,
+ data.frames,
+ data.errorToDetectors))
+ .catch(() => {/* ignore if not present */});
+});