Skip to content
Merged
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ A Search-Based Decoder for Quantum Error Correction.
Tesseract is a Most Likely Error decoder designed for Low Density Parity Check (LDPC) quantum
error-correcting codes. It applies pruning heuristics and manifold orientation techniques during a
search over the error subsets to identify the most likely error configuration consistent with the
observed syndrome. Tesseract archives significant speed improvements over traditional integer
observed syndrome. Tesseract achieves significant speed improvements over traditional integer
programming-based decoders while maintaining comparable accuracy at moderate physical error rates.

We tested the Tesseract decoder for:
Expand All @@ -37,7 +37,8 @@ We tested the Tesseract decoder for:
* **Stim and DEM Support:** processes [Stim](https://github.com/quantumlib/stim) circuit files and
[Detector Error Model
(DEM)](https://github.com/quantumlib/Stim/blob/main/doc/file_format_dem_detector_error_model.md)
files with arbitrary error models.
files with arbitrary error models. Zero-probability error instructions are
automatically removed when a DEM is loaded.
* **Parallel Decoding:** uses multithreading to accelerate the decoding process, making it
suitable for large-scale simulations.
* **Efficient Beam Search:** implements a [beam search](https://en.wikipedia.org/wiki/Beam_search)
Expand Down Expand Up @@ -68,6 +69,19 @@ Tesseract uses [Bazel](https://bazel.build/) as its build system. To build the d
bazel build src:all
```

## Running Tests

Unit tests are executed with Bazel. Run the quick test suite using:
```bash
bazel test //src:all
```
By default the tests use reduced parameters and finish in under 30 seconds.
To run a more exhaustive suite with additional shots and larger distances, set:
```bash
TESSERACT_LONG_TESTS=1 bazel test //src:all
```


## Usage

The file `tesseract_main.cc` provides the main entry point for Tesseract Decoder. It can decode
Expand Down
5 changes: 3 additions & 2 deletions src/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ OPT_COPTS = select({
"//conditions:default": [
"-Ofast",
"-fno-fast-math",
"-flto",
"-march=native",
],
":debug": ["-Og"],
Expand All @@ -42,7 +41,6 @@ OPT_LINKOPTS = select({
"//conditions:default": [
"-Ofast",
"-fno-fast-math",
"-flto",
"-march=native",
],
":debug": [],
Expand Down Expand Up @@ -141,6 +139,9 @@ cc_test(
cc_test(
name = "common_tests",
srcs = ["common.test.cc"],
copts = OPT_COPTS,
linkopts = OPT_LINKOPTS,
linkstatic = True,
deps = [
":libcommon",
"@gtest",
Expand Down
47 changes: 39 additions & 8 deletions src/common.cc
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,29 @@ stim::DetectorErrorModel common::merge_identical_errors(
return out_dem;
}

stim::DetectorErrorModel common::remove_zero_probability_errors(
const stim::DetectorErrorModel& dem) {
stim::DetectorErrorModel out_dem;
for (const stim::DemInstruction& instruction : dem.flattened().instructions) {
switch (instruction.type) {
case stim::DemInstructionType::DEM_SHIFT_DETECTORS:
assert(false && "unreachable");
break;
case stim::DemInstructionType::DEM_ERROR:
if (instruction.arg_data[0] > 0) {
out_dem.append_dem_instruction(instruction);
}
break;
case stim::DemInstructionType::DEM_DETECTOR:
out_dem.append_dem_instruction(instruction);
break;
default:
assert(false && "unreachable");
}
}
return out_dem;
}

stim::DetectorErrorModel common::dem_from_counts(
stim::DetectorErrorModel& orig_dem, const std::vector<size_t>& error_counts,
size_t num_shots) {
Expand All @@ -121,6 +144,17 @@ stim::DetectorErrorModel common::dem_from_counts(
"Error hits array must be the same size as the number of errors in the "
"original DEM.");
}

for (const stim::DemInstruction& instruction :
orig_dem.flattened().instructions) {
if (instruction.type == stim::DemInstructionType::DEM_ERROR &&
instruction.arg_data[0] == 0) {
throw std::invalid_argument(
"dem_from_counts requires DEMs without zero-probability errors. Use"
" remove_zero_probability_errors first.");
}
}

stim::DetectorErrorModel out_dem;
size_t ei = 0;
for (const stim::DemInstruction& instruction :
Expand All @@ -130,14 +164,11 @@ stim::DetectorErrorModel common::dem_from_counts(
assert(false && "unreachable");
break;
case stim::DemInstructionType::DEM_ERROR: {
// Ignore zero-probability errors
if (instruction.arg_data[0] > 0) {
double est_probability =
double(error_counts.at(ei)) / double(num_shots);
out_dem.append_error_instruction(est_probability,
instruction.target_data, /*tag=*/"");
++ei;
}
double est_probability =
double(error_counts.at(ei)) / double(num_shots);
out_dem.append_error_instruction(est_probability,
instruction.target_data, /*tag=*/"");
++ei;
break;
}
case stim::DemInstructionType::DEM_DETECTOR: {
Expand Down
7 changes: 7 additions & 0 deletions src/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,15 @@ struct Error {
stim::DetectorErrorModel merge_identical_errors(
const stim::DetectorErrorModel& dem);

// Returns a copy of the given error model with any zero-probability DEM_ERROR
// instructions removed.
stim::DetectorErrorModel remove_zero_probability_errors(
const stim::DetectorErrorModel& dem);

// Makes a new dem where the probabilities of errors are estimated from the
// fraction of shots they were used in.
// Throws std::invalid_argument if `orig_dem` contains zero-probability errors;
// call remove_zero_probability_errors first.
stim::DetectorErrorModel dem_from_counts(
stim::DetectorErrorModel& orig_dem, const std::vector<size_t>& error_counts,
size_t num_shots);
Expand Down
80 changes: 80 additions & 0 deletions src/common.test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,83 @@ TEST(common, ErrorsStructFromDemInstruction) {
EXPECT_EQ(ES.symptom.detectors, std::vector<int>{1});
EXPECT_EQ(ES.symptom.observables, 0b01);
}

TEST(common, DemFromCountsRejectsZeroProbabilityErrors) {
stim::DetectorErrorModel dem(R"DEM(
error(0.1) D0
error(0) D1
error(0.2) D2
detector(0, 0, 0) D0
detector(0, 0, 0) D1
detector(0, 0, 0) D2
)DEM");

std::vector<size_t> counts{1, 7, 4};
size_t num_shots = 10;
EXPECT_THROW({
common::dem_from_counts(dem, counts, num_shots);
}, std::invalid_argument);

stim::DetectorErrorModel cleaned = common::remove_zero_probability_errors(dem);
stim::DetectorErrorModel out_dem =
common::dem_from_counts(cleaned, std::vector<size_t>{1, 4}, num_shots);

auto flat = out_dem.flattened();
ASSERT_EQ(out_dem.count_errors(), 2);
ASSERT_GE(flat.instructions.size(), 2);

EXPECT_EQ(flat.instructions[0].type,
stim::DemInstructionType::DEM_ERROR);
EXPECT_NEAR(flat.instructions[0].arg_data[0], 0.1, 1e-9);
ASSERT_EQ(flat.instructions[1].type,
stim::DemInstructionType::DEM_ERROR);
EXPECT_NEAR(flat.instructions[1].arg_data[0], 0.4, 1e-9);
}

TEST(common, DemFromCountsSimpleTwoErrors) {
stim::DetectorErrorModel dem(R"DEM(
error(0.25) D0
error(0.35) D1
detector(0, 0, 0) D0
detector(0, 0, 0) D1
)DEM");

std::vector<size_t> counts{5, 7};
size_t num_shots = 20;
stim::DetectorErrorModel out_dem =
common::dem_from_counts(dem, counts, num_shots);

auto flat = out_dem.flattened();
ASSERT_EQ(out_dem.count_errors(), 2);

ASSERT_GE(flat.instructions.size(), 2);
EXPECT_EQ(flat.instructions[0].type,
stim::DemInstructionType::DEM_ERROR);
EXPECT_NEAR(flat.instructions[0].arg_data[0], 0.25, 1e-9);
EXPECT_EQ(flat.instructions[1].type,
stim::DemInstructionType::DEM_ERROR);
EXPECT_NEAR(flat.instructions[1].arg_data[0], 0.35, 1e-9);
}

TEST(common, RemoveZeroProbabilityErrors) {
stim::DetectorErrorModel dem(R"DEM(
error(0.1) D0
error(0) D1
error(0.2) D2
detector(0, 0, 0) D0
detector(0, 0, 0) D1
detector(0, 0, 0) D2
)DEM");

stim::DetectorErrorModel cleaned =
common::remove_zero_probability_errors(dem);

EXPECT_EQ(cleaned.count_errors(), 2);
auto flat = cleaned.flattened();
ASSERT_EQ(flat.instructions[0].type,
stim::DemInstructionType::DEM_ERROR);
EXPECT_NEAR(flat.instructions[0].arg_data[0], 0.1, 1e-9);
ASSERT_EQ(flat.instructions[1].type,
stim::DemInstructionType::DEM_ERROR);
EXPECT_NEAR(flat.instructions[1].arg_data[0], 0.2, 1e-9);
}
7 changes: 3 additions & 4 deletions src/simplex.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
constexpr size_t T_COORD = 2;

SimplexDecoder::SimplexDecoder(SimplexConfig _config) : config(_config) {
config.dem = common::remove_zero_probability_errors(config.dem);
std::vector<double> detector_t_coords(config.dem.count_detectors());
for (const stim::DemInstruction& instruction :
config.dem.flattened().instructions) {
Expand All @@ -30,10 +31,8 @@ SimplexDecoder::SimplexDecoder(SimplexConfig _config) : config(_config) {
assert(false && "unreachable");
break;
case stim::DemInstructionType::DEM_ERROR: {
// Ignore zero-probability errors
if (instruction.arg_data[0] > 0) {
errors.emplace_back(instruction);
}
assert(instruction.arg_data[0] > 0);
errors.emplace_back(instruction);
break;
}
case stim::DemInstructionType::DEM_DETECTOR:
Expand Down
1 change: 1 addition & 0 deletions src/simplex_main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ struct Args {
if (!no_merge_errors) {
config.dem = common::merge_identical_errors(config.dem);
}
config.dem = common::remove_zero_probability_errors(config.dem);

if (sample_num_shots > 0) {
assert(!circuit_path.empty());
Expand Down
1 change: 1 addition & 0 deletions src/tesseract.cc
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ double TesseractDecoder::get_detcost(size_t d,
}

TesseractDecoder::TesseractDecoder(TesseractConfig config_) : config(config_) {
config.dem = common::remove_zero_probability_errors(config.dem);
if (config.det_orders.empty()) {
config.det_orders.emplace_back(config.dem.count_detectors());
std::iota(config.det_orders[0].begin(), config.det_orders[0].end(), 0);
Expand Down
2 changes: 2 additions & 0 deletions src/tesseract.perf.cc
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ void benchmark_tesseract(std::string circuit_path, size_t num_shots) {
/*approximate_disjoint_errors_threshold=*/1,
/*ignore_decomposition_failures=*/false,
/*block_decomposition_from_introducing_remnant_edges=*/false);
dem = common::remove_zero_probability_errors(dem);
TesseractConfig config{dem};
config.det_beam = 20;
config.pqlimit = 10'000'000;
Expand All @@ -99,6 +100,7 @@ void benchmark_simplex(std::string circuit_path, size_t num_shots) {
/*approximate_disjoint_errors_threshold=*/1,
/*ignore_decomposition_failures=*/false,
/*block_decomposition_from_introducing_remnant_edges=*/false);
dem = common::remove_zero_probability_errors(dem);
SimplexConfig config{dem};
config.parallelize = true;
SimplexDecoder decoder(config);
Expand Down
39 changes: 35 additions & 4 deletions src/tesseract.test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "tesseract.h"

#include <vector>
#include <cstdlib>

#include "gtest/gtest.h"
#include "simplex.h"
Expand Down Expand Up @@ -79,10 +80,19 @@ bool simplex_test_compare(stim::DetectorErrorModel& dem,
}

TEST(tesseract, Tesseract_simplex_test) {
for (float p_err : {0.001, 0.003, 0.005}) {
for (size_t distance : {3, 5}) {
for (const size_t num_rounds : {2, 5, 10}) {
const size_t num_shots = 1000 / num_rounds / distance;
bool long_tests = std::getenv("TESSERACT_LONG_TESTS") != nullptr;
auto p_errs = long_tests ? std::vector<float>{0.001f, 0.003f, 0.005f}
: std::vector<float>{0.003f};
auto distances = long_tests ? std::vector<size_t>{3, 5, 7}
: std::vector<size_t>{3};
auto rounds = long_tests ? std::vector<size_t>{2, 5, 10}
: std::vector<size_t>{2};
size_t base_shots = long_tests ? 1000 : 100;

for (float p_err : p_errs) {
for (size_t distance : distances) {
for (const size_t num_rounds : rounds) {
const size_t num_shots = base_shots / num_rounds / distance;
std::cout << "p_err = " << p_err << " distance = " << distance
<< " num_rounds = " << num_rounds
<< " num_shots = " << num_shots << std::endl;
Expand Down Expand Up @@ -195,3 +205,24 @@ TEST(tesseract, Tesseract_simplex_DEM_exhaustive_test) {
ASSERT_TRUE(return_val);
}
}

TEST(tesseract, DecodersStripZeroProbabilityErrors) {
stim::DetectorErrorModel dem(R"DEM(
error(0.1) D0
error(0) D1
error(0.2) D2
detector(0,0,0) D0
detector(0,0,0) D1
detector(0,0,0) D2
)DEM");

TesseractConfig t_config{dem};
TesseractDecoder t_dec(t_config);
EXPECT_EQ(t_dec.config.dem.count_errors(), 2);
EXPECT_EQ(t_dec.errors.size(), 2);

SimplexConfig s_config{dem};
SimplexDecoder s_dec(s_config);
EXPECT_EQ(s_dec.config.dem.count_errors(), 2);
EXPECT_EQ(s_dec.errors.size(), 2);
}
1 change: 1 addition & 0 deletions src/tesseract_main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ struct Args {
if (!no_merge_errors) {
config.dem = common::merge_identical_errors(config.dem);
}
config.dem = common::remove_zero_probability_errors(config.dem);

// Sample orientations of the error model to use for the det priority
{
Expand Down