Skip to content

Commit 5ec6406

Browse files
authored
Added demutils (#198)
### Add `demutils` Python utilities for DEM manipulation This PR introduces a new set of Python utilities under `tesseract_decoder.demutil` to support flexible manipulation of Stim **`DetectorErrorModel` (DEM)** objects used in decoding workflows. This PR exposes decomposition and generalization of detector error models. The changes include: - Added a new `demutil.py` module providing access to functions such as: - `decompose_errors(...)` for breaking complex error mechanisms into simpler component-level errors - `regeneralize_spatial_dem(...)` for recombining error probabilities across multiple templates into a scaffold DEM - Update Python build definitions and test targets to include the new module - Extend `src/py/README.md` with documentation describing the new API and usage patterns Exposing these utilities enable more expressive and reusable DEM preprocessing within the Tesseract Python interface, making it easier to experiment with decoder configurations and integrate Stim-based error models into downstream decoding pipelines.
1 parent 9ca0bbb commit 5ec6406

10 files changed

Lines changed: 390 additions & 2 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
/.idea/
1515
/.ijwb/
1616
/.project
17+
__pycache__/
1718
/.settings
1819
/.vscode/
1920
.eclipse/
@@ -22,6 +23,7 @@
2223
.project
2324
eclipse-*bin/
2425
/bazel.iml
26+
*.pyc
2527
# Ignore all bazel-* symlinks. There is no full list since this can change
2628
# based on the name of the directory bazel is cloned into.
2729
/bazel-*

MODULE.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pip.parse(
2525
hub_name = "pypi",
2626
python_version = DEFAULT_PYTHON_VERSION,
2727
requirements_lock = "//src/py:requirements_lock.txt",
28+
extra_pip_args = ["--index-url=https://pypi.org/simple"],
2829
)
2930

3031
use_repo(pip, "pypi")

src/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ py_library(
9595
imports=["src"],
9696
deps=[
9797
":tesseract_decoder",
98+
"//src/py:demutil",
9899
],
99100
)
100101

src/py/BUILD

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,18 @@ py_library(
2727
"//src:lib_tesseract_decoder",
2828
],
2929
)
30+
31+
py_library(
32+
name = "demutil",
33+
srcs = ["demutil.py", "generalize_dem.py"],
34+
visibility = ["//:__subpackages__"],
35+
deps = [
36+
":decompose_errors",
37+
"@pypi//stim",
38+
"@pypi//numpy",
39+
],
40+
)
41+
3042
py_test(
3143
name = "common_test",
3244
srcs = ["common_test.py"],
@@ -84,6 +96,39 @@ py_test(
8496
],
8597
)
8698

99+
100+
py_library(
101+
name = "decompose_errors",
102+
srcs = ["decompose_errors.py"],
103+
visibility = ["//:__subpackages__"],
104+
deps = [
105+
"@pypi//stim",
106+
],
107+
)
108+
109+
py_test(
110+
name = "decompose_errors_test",
111+
srcs = ["decompose_errors_test.py"],
112+
visibility = ["//:__subpackages__"],
113+
deps = [
114+
":decompose_errors",
115+
"@pypi//pytest",
116+
"@pypi//stim",
117+
],
118+
)
119+
120+
121+
py_test(
122+
name = "demutil_test",
123+
srcs = ["demutil_test.py"],
124+
visibility = ["//:__subpackages__"],
125+
deps = [
126+
"@pypi//pytest",
127+
"@pypi//stim",
128+
"//src:lib_tesseract_decoder",
129+
],
130+
)
131+
87132
compile_pip_requirements(
88133
name = "requirements",
89134
src = "requirements.in",

src/py/README.md

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -569,4 +569,68 @@ print("Basic sample_decode Results:")
569569
print(f"Shots run: {result.shots}")
570570
print(f"Observed errors: {result.errors}")
571571
print(f"Logical error rate: {result.errors / result.shots}")
572-
```
572+
```
573+
### `tesseract_decoder.demutil` Module
574+
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.
575+
576+
#### Functions
577+
* `demutil.decompose_errors(dem: stim.DetectorErrorModel, method: str) -> stim.DetectorErrorModel`
578+
* Decomposes error mechanisms in a DEM into simpler components based on the specified method.
579+
* Supported methods:
580+
* `"stim-surfacecode-coords"`: Decomposes errors based on the spatial coordinates of detectors, assuming a surface code layout where coordinates indicate X or Z basis.
581+
* `"last-coordinate-index"`: Decomposes errors using the last coordinate of the detector as the component identifier.
582+
* **Note:** For decomposition to work, the DEM must contain "atomic" errors (errors involving only one component) that explain the components of the complex errors.
583+
584+
**Example Usage**:
585+
586+
```python
587+
import tesseract_decoder.demutil as demutil
588+
import stim
589+
590+
dem = stim.DetectorErrorModel("""
591+
detector(0, 0, 0) D0
592+
detector(0, 0, 1) D1
593+
# Atomic errors for decomposition reference
594+
error(0.01) D0
595+
error(0.01) D1
596+
# Complex error to decompose
597+
error(0.1) D0 D1
598+
""")
599+
600+
# Re-decompose the errors assuming Stim surface code coordinate convention
601+
nice_matchable_dem = demutil.decompose_errors(dem, method='stim-surfacecode-coords')
602+
603+
# Re-decompose the errors assuming the last-coordinate index indicates the component:
604+
nice_matchable_dem2 = demutil.decompose_errors(dem, method='last-coordinate-index')
605+
```
606+
607+
* `demutil.regeneralize_spatial_dem(templates: list[stim.DetectorErrorModel], scaffold: stim.DetectorErrorModel, verbose: bool = False) -> stim.DetectorErrorModel`
608+
* 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).
609+
* **Important:** The scaffold errors must have the same structure and **same absolute coordinates** (for the first detector) as the template errors to be matched.
610+
611+
**Example Usage**:
612+
613+
```python
614+
import tesseract_decoder.demutil as demutil
615+
import stim
616+
617+
# Take one or more DEMs **with detector coordinates**, aggregate the error probabilities
618+
template1 = stim.DetectorErrorModel("""
619+
detector(0, 0) D0
620+
error(0.1) D0
621+
""")
622+
template2 = stim.DetectorErrorModel("""
623+
detector(0, 0) D0
624+
error(0.2) D0
625+
""")
626+
scaffold = stim.DetectorErrorModel("""
627+
detector(0, 0) D1
628+
error(0.99) D1
629+
""")
630+
631+
nice_calibrated_dem = demutil.regeneralize_spatial_dem(
632+
templates=[template1, template2],
633+
scaffold=scaffold
634+
)
635+
# Result will have error probability (0.1 + 0.2) / 2 = 0.15
636+
```

src/py/decompose_errors.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http:#www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
115
import stim
216
from functools import reduce
317
import itertools

src/py/decompose_errors_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from collections.abc import Iterable
33
import stim
44

5-
from decompose_errors import (
5+
from src.py.decompose_errors import (
66
reduce_symmetric_difference,
77
reduce_set_symmetric_difference,
88
get_component_obs_matching_undecomposed_obs,

src/py/demutil.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http:#www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""
16+
This module is a dispatcher for DEMfunctionality such as decomposition and re-generalization,
17+
and related utilities, in `decompose_errors.py` and `generalize_dem.py`.
18+
"""
19+
20+
21+
import stim
22+
23+
from .decompose_errors import (
24+
reduce_symmetric_difference as reduce_symmetric_difference,
25+
reduce_set_symmetric_difference as reduce_set_symmetric_difference,
26+
undecomposed_error_detectors_and_observables as undecomposed_error_detectors_and_observables,
27+
get_component_obs_matching_undecomposed_obs as get_component_obs_matching_undecomposed_obs,
28+
decompose_errors_using_detector_assignment as decompose_errors_using_detector_assignment,
29+
decompose_errors_using_detector_coordinate_assignment as decompose_errors_using_detector_coordinate_assignment,
30+
detector_coord_to_basis_for_stim_surface_code_convention as detector_coord_to_basis_for_stim_surface_code_convention,
31+
decompose_errors_using_last_coordinate_index as decompose_errors_using_last_coordinate_index,
32+
decompose_errors_for_stim_surface_code_coords as decompose_errors_for_stim_surface_code_coords,
33+
undecompose_errors as undecompose_errors,
34+
)
35+
from .generalize_dem import generalize as regeneralize_spatial_dem
36+
37+
38+
__all__ = [
39+
"decompose_errors",
40+
"regeneralize_spatial_dem",
41+
"reduce_symmetric_difference",
42+
"reduce_set_symmetric_difference",
43+
"undecomposed_error_detectors_and_observables",
44+
"get_component_obs_matching_undecomposed_obs",
45+
"decompose_errors_using_detector_assignment",
46+
"decompose_errors_using_detector_coordinate_assignment",
47+
"detector_coord_to_basis_for_stim_surface_code_convention",
48+
"decompose_errors_using_last_coordinate_index",
49+
"decompose_errors_for_stim_surface_code_coords",
50+
"undecompose_errors",
51+
]
52+
53+
54+
def decompose_errors(
55+
dem: stim.DetectorErrorModel, method: str = "stim-surfacecode-coords"
56+
) -> stim.DetectorErrorModel:
57+
"""Dispatch decomposition strategy by method name."""
58+
if method == "stim-surfacecode-coords":
59+
return decompose_errors_for_stim_surface_code_coords(dem)
60+
if method == "last-coordinate-index":
61+
return decompose_errors_using_last_coordinate_index(dem)
62+
raise ValueError(
63+
"Unknown decomposition method "
64+
f"{method!r}. Expected 'stim-surfacecode-coords' or 'last-coordinate-index'."
65+
)

0 commit comments

Comments
 (0)