Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,4 @@ jobs:
brew install clang-format
- name: Bazel tests
run: bazel test src/... //docs:tutorial_jupytext_sync_test
run: bazel test --define=tesseract_portable=true src/... //docs:tutorial_jupytext_sync_test
9 changes: 9 additions & 0 deletions src/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ config_setting(
values = {"compilation_mode": "dbg"},
)

# CI restores its Linux disk cache across runners with different CPUs. Avoid
# caching binaries containing instructions specific to whichever runner built first.
config_setting(
name = "portable_linux",
constraint_values = ["@platforms//os:linux"],
define_values = {"tesseract_portable": "true"},
)

OPT_COPTS = select({
"//conditions:default": [
"-Ofast",
Expand All @@ -33,6 +41,7 @@ OPT_COPTS = select({
"//conditions:default": ["-std=c++20"],
}) + select({
"@platforms//os:macos": ["-mmacosx-version-min=10.15",],
":portable_linux": [],
"//conditions:default": ["-march=native",],
})

Expand Down
31 changes: 28 additions & 3 deletions src/py/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ print(f"Logical error rate: {result.errors / result.shots}")
The `tesseract_decoder.demutil` module provides utilities for manipulating `stim.DetectorErrorModel` objects, specifically for decomposing complex error mechanisms into simpler components and regeneralizing spatial error models.

#### Functions
* `demutil.decompose_errors(dem: stim.DetectorErrorModel, method: str, strip_undecomposable_errors: bool = False) -> stim.DetectorErrorModel`
* `demutil.decompose_errors(dem: stim.DetectorErrorModel, method: str = "stim-surfacecode-coords", strip_undecomposable_errors: bool = False) -> stim.DetectorErrorModel`
* Decomposes error mechanisms in a DEM into simpler components based on the specified method.
* Supported methods:
* `"stim-surfacecode-coords"`: Decomposes errors based on the spatial coordinates of detectors, assuming a surface code layout where coordinates indicate X or Z basis.
Expand All @@ -621,8 +621,8 @@ The `tesseract_decoder.demutil` module provides utilities for manipulating `stim
**Example Usage**:

```python
import tesseract_decoder.demutil as demutil
import stim
from tesseract_decoder import demutil

dem = stim.DetectorErrorModel("""
detector(0, 0, 0) D0
Expand All @@ -648,15 +648,40 @@ nice_matchable_dem3 = demutil.decompose_errors(
)
```

#### Command-line decomposition

Like the other DEM utility modules, `decompose_errors.py` can also be run
directly:

```bash
python src/py/_tesseract_py_util/decompose_errors.py \
--method=last-coordinate-index \
--out output.dem \
input.dem
```

The input defaults to standard input, `--out` defaults to standard output, and
`--method` defaults to `stim-surfacecode-coords`, so the command can also be
used in a pipeline:

```bash
python src/py/_tesseract_py_util/decompose_errors.py \
--method=stim-surfacecode-coords \
< input.dem > output.dem
```

Pass `--strip-undecomposable-errors` to drop errors that cannot be decomposed
instead of returning an error.

* `demutil.regeneralize_spatial_dem(templates: list[stim.DetectorErrorModel], scaffold: stim.DetectorErrorModel, verbose: bool = False) -> stim.DetectorErrorModel`
* Updates the error probabilities in a `scaffold` DEM by averaging probabilities from matching errors in a list of `template` DEMs. Errors are matched based on their spatial geometry (relative coordinates of detectors).
* **Important:** The scaffold errors must have the same structure and **same absolute coordinates** (for the first detector) as the template errors to be matched.

**Example Usage**:

```python
import tesseract_decoder.demutil as demutil
import stim
from tesseract_decoder import demutil

# Take one or more DEMs **with detector coordinates**, aggregate the error probabilities
template1 = stim.DetectorErrorModel("""
Expand Down
18 changes: 2 additions & 16 deletions src/py/_tesseract_py_util/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,15 @@ py_test(
],
)


py_test(
name = "demutil_test",
srcs = ["demutil_test.py"],
visibility = ["//:__subpackages__"],
deps = [
"@pypi//pytest",
"@pypi//stim",
"//src:lib_tesseract_decoder",
":_tesseract_py_util",
],
imports = ["..", ".", "../.."],
)


py_test(
name = "decompose_errors_test",
srcs = ["decompose_errors_test.py"],
imports = ["..", ".", "../.."],
visibility = ["//:__subpackages__"],
deps = [
":_tesseract_py_util",
"@pypi//pytest",
"@pypi//stim",
"//src:lib_tesseract_decoder",
],
imports = ["..", "."],
)
12 changes: 5 additions & 7 deletions src/py/_tesseract_py_util/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.

"""
This module is a dispatcher for DEMfunctionality such as decomposition and re-generalization,
and related utilities, in `decompose_errors.py` and `generalize_dem.py`.
"""
"""Utilities exported through the public ``tesseract_decoder.demutil`` facade."""

from _tesseract_py_util import gari as gari
from _tesseract_py_util.demutil import decompose_errors
from _tesseract_py_util.generalize_dem import \
generalize as regeneralize_spatial_dem
from _tesseract_py_util.decompose_errors import decompose_errors as decompose_errors
from _tesseract_py_util.generalize_dem import generalize as regeneralize_spatial_dem

__all__ = ["decompose_errors", "gari", "regeneralize_spatial_dem"]
86 changes: 86 additions & 0 deletions src/py/_tesseract_py_util/decompose_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.

import itertools
import sys
from collections import defaultdict
from collections.abc import Callable, Iterable
from functools import reduce
Expand Down Expand Up @@ -354,6 +355,26 @@ def stim_surface_code_det_component(detector_id: int) -> int:
)


def decompose_errors(
dem: stim.DetectorErrorModel,
method: str = "stim-surfacecode-coords",
strip_undecomposable_errors: bool = False,
) -> stim.DetectorErrorModel:
"""Dispatches to a decomposition strategy selected by name."""
if method == "stim-surfacecode-coords":
return decompose_errors_for_stim_surface_code_coords(
dem, strip_undecomposable_errors=strip_undecomposable_errors
)
if method == "last-coordinate-index":
return decompose_errors_using_last_coordinate_index(
dem, strip_undecomposable_errors=strip_undecomposable_errors
)
raise ValueError(
"Unknown decomposition method "
f"{method!r}. Expected 'stim-surfacecode-coords' or 'last-coordinate-index'."
)


def undecompose_errors(dem: stim.DetectorErrorModel) -> stim.DetectorErrorModel:
"""Returns a detector error model with any error decompositions removed.

Expand Down Expand Up @@ -404,3 +425,68 @@ def undecompose_errors(dem: stim.DetectorErrorModel) -> stim.DetectorErrorModel:
)
)
return undecomposed_dem


def call_decompose_errors(
input_fname: str,
output_fname: str,
method: str,
strip_undecomposable_errors: bool,
) -> None:
"""Reads, decomposes, and writes one detector error model."""
if input_fname == "-":
dem = stim.DetectorErrorModel(sys.stdin.read())
else:
dem = stim.DetectorErrorModel.from_file(input_fname)

output_dem = decompose_errors(
dem,
method=method,
strip_undecomposable_errors=strip_undecomposable_errors,
)
if output_fname == "-":
print(output_dem)
else:
output_dem.to_file(output_fname)


def main() -> None:
import argparse

parser = argparse.ArgumentParser(
description="Decompose errors in a Stim detector error model."
)
parser.add_argument(
"input",
nargs="?",
default="-",
help="Input DEM file (default: standard input; use '-' for standard input).",
)
parser.add_argument(
"-o",
"--out",
default="-",
help="Output DEM file (default: standard output; use '-' for standard output).",
)
parser.add_argument(
"--method",
choices=("stim-surfacecode-coords", "last-coordinate-index"),
default="stim-surfacecode-coords",
help="Detector-component convention used for decomposition.",
)
parser.add_argument(
"--strip-undecomposable-errors",
action="store_true",
help="Drop errors that cannot be decomposed instead of failing.",
)
args = parser.parse_args()
call_decompose_errors(
args.input,
args.out,
method=args.method,
strip_undecomposable_errors=args.strip_undecomposable_errors,
)


if __name__ == "__main__":
main()
90 changes: 88 additions & 2 deletions src/py/_tesseract_py_util/decompose_errors_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,100 @@

import pytest
import stim
import tesseract_decoder
from _tesseract_py_util.decompose_errors import (
decompose_errors,
decompose_errors_for_stim_surface_code_coords,
decompose_errors_using_detector_coordinate_assignment,
decompose_errors_using_last_coordinate_index,
detector_coord_to_basis_for_stim_surface_code_convention,
get_component_obs_matching_undecomposed_obs,
reduce_set_symmetric_difference, reduce_symmetric_difference,
undecompose_errors)
reduce_set_symmetric_difference,
reduce_symmetric_difference,
undecompose_errors,
)
from tesseract_decoder import demutil


def _demo_dem() -> stim.DetectorErrorModel:
return stim.DetectorErrorModel("""
detector(0, 0, 0) D0
detector(2, 0, 1) D1
error(0.1) D0
error(0.2) D1
error(0.3) D0 D1
""")


def test_import_exposes_demutil_facade():
assert tesseract_decoder.demutil is demutil
assert hasattr(demutil, "regeneralize_spatial_dem")
assert demutil.decompose_errors is decompose_errors
assert hasattr(demutil.gari, "circuit_to_gari")


def test_decompose_errors_rejects_unknown_method():
with pytest.raises(ValueError, match="Unknown decomposition method"):
demutil.decompose_errors(_demo_dem(), method="bad-method")


def test_decompose_errors_public_default_method():
actual = demutil.decompose_errors(_demo_dem())
expected = stim.DetectorErrorModel("""
detector(0, 0, 0) D0
detector(2, 0, 1) D1
error(0.1) D0
error(0.2) D1
error(0.3) D1 ^ D0
""")
assert actual == expected


def test_regeneralize_spatial_dem_averages_template_probabilities():
template_1 = stim.DetectorErrorModel("""
detector(0, 0, 0) D0
detector(2, 0, 0) D1
error(0.1) D0
error(0.2) D1
""")
template_2 = stim.DetectorErrorModel("""
detector(0, 0, 0) D0
detector(2, 0, 0) D1
error(0.3) D0
error(0.4) D1
""")
scaffold = stim.DetectorErrorModel("""
detector(0, 0, 0) D0
detector(2, 0, 0) D1
error(0.9) D0
error(0.9) D1
""")

out = demutil.regeneralize_spatial_dem(
templates=[template_1, template_2], scaffold=scaffold
)

probs = [inst.args_copy()[0] for inst in out if inst.type == "error"]
assert probs == pytest.approx([0.2, 0.3])


def test_decompose_errors_public_strip_undecomposable_errors():
dem = stim.DetectorErrorModel("""
detector(0) D0
detector(1) D1
error(0.1) D0 D1
error(0.1) D0
""")

actual = demutil.decompose_errors(
dem, method="last-coordinate-index", strip_undecomposable_errors=True
)
expected = stim.DetectorErrorModel("""
detector(0) D0
detector(1) D1
error(0.1) D0
""")
assert actual == expected


@pytest.mark.parametrize(
Expand Down
42 changes: 0 additions & 42 deletions src/py/_tesseract_py_util/demutil.py

This file was deleted.

Loading
Loading