Skip to content

Commit 012770f

Browse files
authored
Add strip_undecomposable_errors=False to decomposition (#218)
The decomposer code written by Oscar is very nice because it is quite careful not to make any mistakes. For instance in a situation like this: ``` error D0 D1 error D0 ``` if we label D0 as X and D1 as Z type, it will raise a ValueError due to D1 not appearing on its own in the error model. This prevents at least one possible issue, which is putting the logical flip (if present) onto the wrong component. Anyways, sometimes we want to produce an error model anyways, and we don't care about these errors. In these situations we may as well strip such undecomposable errors entirely. Alternative considered: we could also try harder to decompose them and workaround the lack of a single error with that component's worth of symptoms in the DEM. I tried this first but I feel it is somewhat higher risk since it could be silently failing and putting badly-decomposed errors into the DEM. So I think the best practice should be to strip them out entirely.
1 parent c1780b5 commit 012770f

File tree

5 files changed

+103
-13
lines changed

5 files changed

+103
-13
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,5 @@ user.bazelrc
3737

3838
# Ignore python extension module produced by CMake.
3939
src/tesseract_decoder*.so
40+
41+
MODULE.bazel.lock

src/py/_tesseract_py_util/decompose_errors.py

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@ def get_component_obs_matching_undecomposed_obs(
8989

9090

9191
def decompose_errors_using_detector_assignment(
92-
dem: stim.DetectorErrorModel, detector_component_func: Callable[[int], int]
92+
dem: stim.DetectorErrorModel,
93+
detector_component_func: Callable[[int], int],
94+
strip_undecomposable_errors: bool = False,
9395
) -> stim.DetectorErrorModel:
9496
"""Decomposes errors in the detector error model `dem` based on an assignment of
9597
detectors to components by the function `detector_component_func`.
@@ -112,6 +114,9 @@ def decompose_errors_using_detector_assignment(
112114
detector_component_func : Callable[[int], int]
113115
A function that maps a detector id to its component. i.e. This could map
114116
a detector index to 0 if it is X-type or to 1 if it is Z-type.
117+
strip_undecomposable_errors : bool
118+
If True, errors that cannot be decomposed due to a missing component error
119+
will be stripped from the output DEM instead of raising a ValueError.
115120
116121
Returns
117122
-------
@@ -150,21 +155,29 @@ def decompose_errors_using_detector_assignment(
150155
dets_by_component = []
151156
obs_options_by_component = []
152157

158+
is_undecomposable = False
153159
for c in unique_components:
154160
component_dets = tuple(
155161
sorted(d for d in detectors if det_components[d] == c)
156162
)
157163
if component_dets not in single_component_dets_to_obs:
158-
raise ValueError(
159-
f"The dem error `{instruction}` needs to be decomposed into components, however "
160-
f"the component with detectors {component_dets} is not present as its own error "
161-
"in the dem."
162-
)
164+
if strip_undecomposable_errors:
165+
is_undecomposable = True
166+
break
167+
else:
168+
raise ValueError(
169+
f"The dem error `{instruction}` needs to be decomposed into components, however "
170+
f"the component with detectors {component_dets} is not present as its own error "
171+
"in the dem."
172+
)
163173
dets_by_component.append(component_dets)
164174
obs_options_by_component.append(
165175
single_component_dets_to_obs[component_dets]
166176
)
167177

178+
if is_undecomposable:
179+
continue
180+
168181
# Assign observables to each component, such that they are consistent with the
169182
# observables of the undecomposed error
170183
consistent_obs_by_component = get_component_obs_matching_undecomposed_obs(
@@ -202,7 +215,9 @@ def decompose_errors_using_detector_assignment(
202215

203216

204217
def decompose_errors_using_detector_coordinate_assignment(
205-
dem: stim.DetectorErrorModel, coord_to_component_func: Callable[[list[float]], int]
218+
dem: stim.DetectorErrorModel,
219+
coord_to_component_func: Callable[[list[float]], int],
220+
strip_undecomposable_errors: bool = False,
206221
) -> stim.DetectorErrorModel:
207222
"""Decomposes errors in the detector error model `dem` based on an assignment of
208223
detectors to components using a function of the detector coordinates.
@@ -225,6 +240,9 @@ def decompose_errors_using_detector_coordinate_assignment(
225240
A function that coordinates of a detector to an integer corresponding to
226241
the index of a component, to be used for the decomposition. The coordinates
227242
are provided as a list of floats.
243+
strip_undecomposable_errors : bool
244+
If True, errors that cannot be decomposed due to a missing component error
245+
will be stripped from the output DEM instead of raising a ValueError.
228246
229247
Returns
230248
-------
@@ -237,7 +255,9 @@ def component_using_coords(detector_id: int) -> int:
237255
return coord_to_component_func(detector_coords[detector_id])
238256

239257
return decompose_errors_using_detector_assignment(
240-
dem=dem, detector_component_func=component_using_coords
258+
dem=dem,
259+
detector_component_func=component_using_coords,
260+
strip_undecomposable_errors=strip_undecomposable_errors,
241261
)
242262

243263

@@ -252,6 +272,7 @@ def detector_coord_to_basis_for_stim_surface_code_convention(coord: tuple[int])
252272

253273
def decompose_errors_using_last_coordinate_index(
254274
dem: stim.DetectorErrorModel,
275+
strip_undecomposable_errors: bool = False,
255276
) -> stim.DetectorErrorModel:
256277
"""Decomposes errors in the detector error model `dem` based on an assignment of
257278
detectors to components by the last element of each detector coordinate.
@@ -269,6 +290,9 @@ def decompose_errors_using_last_coordinate_index(
269290
----------
270291
dem : stim.DetectorErrorModel
271292
The detector error model to decompose.
293+
strip_undecomposable_errors : bool
294+
If True, errors that cannot be decomposed due to a missing component error
295+
will be stripped from the output DEM instead of raising a ValueError.
272296
273297
Returns
274298
-------
@@ -281,12 +305,15 @@ def last_coordinate_component(detector_id: int) -> int:
281305
return detector_coords[detector_id][-1]
282306

283307
return decompose_errors_using_detector_assignment(
284-
dem=dem, detector_component_func=last_coordinate_component
308+
dem=dem,
309+
detector_component_func=last_coordinate_component,
310+
strip_undecomposable_errors=strip_undecomposable_errors,
285311
)
286312

287313

288314
def decompose_errors_for_stim_surface_code_coords(
289315
dem: stim.DetectorErrorModel,
316+
strip_undecomposable_errors: bool = False,
290317
) -> stim.DetectorErrorModel:
291318
"""Decomposes the errors in the dem, such that each component
292319
of a decomposed error only triggers detectors of one basis (X or Z)
@@ -302,6 +329,9 @@ def decompose_errors_for_stim_surface_code_coords(
302329
----------
303330
dem : stim.DetectorErrorModel
304331
The detector error model to decompose
332+
strip_undecomposable_errors : bool
333+
If True, errors that cannot be decomposed due to a missing component error
334+
will be stripped from the output DEM instead of raising a ValueError.
305335
306336
Returns
307337
-------
@@ -316,7 +346,9 @@ def stim_surface_code_det_component(detector_id: int) -> int:
316346
)
317347

318348
return decompose_errors_using_detector_assignment(
319-
dem=dem, detector_component_func=stim_surface_code_det_component
349+
dem=dem,
350+
detector_component_func=stim_surface_code_det_component,
351+
strip_undecomposable_errors=strip_undecomposable_errors,
320352
)
321353

322354

src/py/_tesseract_py_util/decompose_errors_test.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,32 @@ def test_undecompose_errors_surface_code():
185185
assert dem_decomposed_using_coords_func == dem_decomposed_using_coords
186186

187187

188+
def test_decompose_errors_strip_undecomposable_errors():
189+
dem = stim.DetectorErrorModel("""
190+
detector(0) D0
191+
detector(1) D1
192+
# Error with multiple components (D0 and D1)
193+
error(0.1) D0 D1
194+
# D0 exists as a standalone error
195+
error(0.1) D0
196+
# D1 DOES NOT exist as a standalone error
197+
""")
198+
199+
# Should fail by default
200+
with pytest.raises(ValueError, match="needs to be decomposed into components"):
201+
decompose_errors_using_last_coordinate_index(dem)
202+
203+
# Should pass with strip_undecomposable_errors=True, but D0 D1 error is removed
204+
decomposed_dem = decompose_errors_using_last_coordinate_index(dem, strip_undecomposable_errors=True)
205+
206+
expected_dem = stim.DetectorErrorModel("""
207+
detector(0) D0
208+
detector(1) D1
209+
error(0.1) D0
210+
""")
211+
assert str(decomposed_dem) == str(expected_dem)
212+
213+
188214
def test_undecompose_errors_with_repeat_block():
189215
dem = stim.DetectorErrorModel("""error(0.1) D2 D5 ^ D10 L1
190216
repeat 10 {

src/py/_tesseract_py_util/demutil.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,19 @@
2323

2424

2525
def decompose_errors(
26-
dem: stim.DetectorErrorModel, method: str = "stim-surfacecode-coords"
26+
dem: stim.DetectorErrorModel,
27+
method: str = "stim-surfacecode-coords",
28+
strip_undecomposable_errors: bool = False,
2729
) -> stim.DetectorErrorModel:
2830
"""Dispatch decomposition strategy by method name."""
2931
if method == "stim-surfacecode-coords":
30-
return decompose_errors_for_stim_surface_code_coords(dem)
32+
return decompose_errors_for_stim_surface_code_coords(
33+
dem, strip_undecomposable_errors=strip_undecomposable_errors
34+
)
3135
if method == "last-coordinate-index":
32-
return decompose_errors_using_last_coordinate_index(dem)
36+
return decompose_errors_using_last_coordinate_index(
37+
dem, strip_undecomposable_errors=strip_undecomposable_errors
38+
)
3339
raise ValueError(
3440
"Unknown decomposition method "
3541
f"{method!r}. Expected 'stim-surfacecode-coords' or 'last-coordinate-index'."

src/py/_tesseract_py_util/demutil_test.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,5 +67,29 @@ def test_regeneralize_spatial_dem_averages_template_probabilities():
6767
assert probs == pytest.approx([0.2, 0.3])
6868

6969

70+
def test_decompose_errors_top_level_strip_undecomposable_errors():
71+
dem = stim.DetectorErrorModel("""
72+
detector(0) D0
73+
detector(1) D1
74+
# Error with multiple components (D0 and D1)
75+
error(0.1) D0 D1
76+
# D0 exists as a standalone error
77+
error(0.1) D0
78+
# D1 DOES NOT exist as a standalone error
79+
""")
80+
81+
# Should pass with strip_undecomposable_errors=True
82+
decomposed_dem = demutil.decompose_errors(
83+
dem, method="last-coordinate-index", strip_undecomposable_errors=True
84+
)
85+
86+
expected_dem = stim.DetectorErrorModel("""
87+
detector(0) D0
88+
detector(1) D1
89+
error(0.1) D0
90+
""")
91+
assert str(decomposed_dem) == str(expected_dem)
92+
93+
7094
if __name__ == "__main__":
7195
raise SystemExit(pytest.main([__file__]))

0 commit comments

Comments
 (0)