sinter 1.15.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sinter might be problematic. Click here for more details.
- sinter/__init__.py +47 -0
- sinter/_collection/__init__.py +10 -0
- sinter/_collection/_collection.py +480 -0
- sinter/_collection/_collection_manager.py +581 -0
- sinter/_collection/_collection_manager_test.py +287 -0
- sinter/_collection/_collection_test.py +317 -0
- sinter/_collection/_collection_worker_loop.py +35 -0
- sinter/_collection/_collection_worker_state.py +259 -0
- sinter/_collection/_collection_worker_test.py +222 -0
- sinter/_collection/_mux_sampler.py +56 -0
- sinter/_collection/_printer.py +65 -0
- sinter/_collection/_sampler_ramp_throttled.py +66 -0
- sinter/_collection/_sampler_ramp_throttled_test.py +144 -0
- sinter/_command/__init__.py +0 -0
- sinter/_command/_main.py +39 -0
- sinter/_command/_main_collect.py +350 -0
- sinter/_command/_main_collect_test.py +482 -0
- sinter/_command/_main_combine.py +84 -0
- sinter/_command/_main_combine_test.py +153 -0
- sinter/_command/_main_plot.py +817 -0
- sinter/_command/_main_plot_test.py +445 -0
- sinter/_command/_main_predict.py +75 -0
- sinter/_command/_main_predict_test.py +36 -0
- sinter/_data/__init__.py +20 -0
- sinter/_data/_anon_task_stats.py +89 -0
- sinter/_data/_anon_task_stats_test.py +35 -0
- sinter/_data/_collection_options.py +106 -0
- sinter/_data/_collection_options_test.py +24 -0
- sinter/_data/_csv_out.py +74 -0
- sinter/_data/_existing_data.py +173 -0
- sinter/_data/_existing_data_test.py +41 -0
- sinter/_data/_task.py +311 -0
- sinter/_data/_task_stats.py +244 -0
- sinter/_data/_task_stats_test.py +140 -0
- sinter/_data/_task_test.py +38 -0
- sinter/_decoding/__init__.py +16 -0
- sinter/_decoding/_decoding.py +419 -0
- sinter/_decoding/_decoding_all_built_in_decoders.py +25 -0
- sinter/_decoding/_decoding_decoder_class.py +161 -0
- sinter/_decoding/_decoding_fusion_blossom.py +193 -0
- sinter/_decoding/_decoding_mwpf.py +302 -0
- sinter/_decoding/_decoding_pymatching.py +81 -0
- sinter/_decoding/_decoding_test.py +480 -0
- sinter/_decoding/_decoding_vacuous.py +38 -0
- sinter/_decoding/_perfectionist_sampler.py +38 -0
- sinter/_decoding/_sampler.py +72 -0
- sinter/_decoding/_stim_then_decode_sampler.py +222 -0
- sinter/_decoding/_stim_then_decode_sampler_test.py +192 -0
- sinter/_plotting.py +619 -0
- sinter/_plotting_test.py +108 -0
- sinter/_predict.py +381 -0
- sinter/_predict_test.py +227 -0
- sinter/_probability_util.py +519 -0
- sinter/_probability_util_test.py +281 -0
- sinter-1.15.0.data/data/README.md +332 -0
- sinter-1.15.0.data/data/readme_example_plot.png +0 -0
- sinter-1.15.0.data/data/requirements.txt +4 -0
- sinter-1.15.0.dist-info/METADATA +354 -0
- sinter-1.15.0.dist-info/RECORD +62 -0
- sinter-1.15.0.dist-info/WHEEL +5 -0
- sinter-1.15.0.dist-info/entry_points.txt +2 -0
- sinter-1.15.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from sinter._decoding._decoding_decoder_class import Decoder, CompiledDecoder
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class PyMatchingCompiledDecoder(CompiledDecoder):
|
|
5
|
+
def __init__(self, matcher: 'pymatching.Matching'):
|
|
6
|
+
self.matcher = matcher
|
|
7
|
+
|
|
8
|
+
def decode_shots_bit_packed(
|
|
9
|
+
self,
|
|
10
|
+
*,
|
|
11
|
+
bit_packed_detection_event_data: 'np.ndarray',
|
|
12
|
+
) -> 'np.ndarray':
|
|
13
|
+
return self.matcher.decode_batch(
|
|
14
|
+
shots=bit_packed_detection_event_data,
|
|
15
|
+
bit_packed_shots=True,
|
|
16
|
+
bit_packed_predictions=True,
|
|
17
|
+
return_weights=False,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PyMatchingDecoder(Decoder):
|
|
22
|
+
"""Use pymatching to predict observables from detection events."""
|
|
23
|
+
|
|
24
|
+
def compile_decoder_for_dem(self, *, dem: 'stim.DetectorErrorModel') -> CompiledDecoder:
|
|
25
|
+
try:
|
|
26
|
+
import pymatching
|
|
27
|
+
except ImportError as ex:
|
|
28
|
+
raise ImportError(
|
|
29
|
+
"The decoder 'pymatching' isn't installed\n"
|
|
30
|
+
"To fix this, install the python package 'pymatching' into your environment.\n"
|
|
31
|
+
"For example, if you are using pip, run `pip install pymatching`.\n"
|
|
32
|
+
) from ex
|
|
33
|
+
|
|
34
|
+
return PyMatchingCompiledDecoder(pymatching.Matching.from_detector_error_model(dem))
|
|
35
|
+
|
|
36
|
+
def decode_via_files(self,
|
|
37
|
+
*,
|
|
38
|
+
num_shots: int,
|
|
39
|
+
num_dets: int,
|
|
40
|
+
num_obs: int,
|
|
41
|
+
dem_path: 'pathlib.Path',
|
|
42
|
+
dets_b8_in_path: 'pathlib.Path',
|
|
43
|
+
obs_predictions_b8_out_path: 'pathlib.Path',
|
|
44
|
+
tmp_dir: 'pathlib.Path',
|
|
45
|
+
) -> None:
|
|
46
|
+
try:
|
|
47
|
+
import pymatching
|
|
48
|
+
except ImportError as ex:
|
|
49
|
+
raise ImportError(
|
|
50
|
+
"The decoder 'pymatching' isn't installed\n"
|
|
51
|
+
"To fix this, install the python package 'pymatching' into your environment.\n"
|
|
52
|
+
"For example, if you are using pip, run `pip install pymatching`.\n"
|
|
53
|
+
) from ex
|
|
54
|
+
|
|
55
|
+
if num_dets == 0:
|
|
56
|
+
with open(obs_predictions_b8_out_path, 'wb') as f:
|
|
57
|
+
f.write(b'\0' * (num_obs * num_shots))
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
if not hasattr(pymatching, 'cli'):
|
|
61
|
+
raise ValueError("""
|
|
62
|
+
The installed version of pymatching has no `pymatching.cli` method.
|
|
63
|
+
sinter requires pymatching 2.1.0 or later.
|
|
64
|
+
If you're using pip to install packages, this can be fixed by running
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
pip install "pymatching~=2.1" --upgrade
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
""")
|
|
71
|
+
|
|
72
|
+
result = pymatching.cli(command_line_args=[
|
|
73
|
+
"predict",
|
|
74
|
+
"--dem", str(dem_path),
|
|
75
|
+
"--in", str(dets_b8_in_path),
|
|
76
|
+
"--in_format", "b8",
|
|
77
|
+
"--out", str(obs_predictions_b8_out_path),
|
|
78
|
+
"--out_format", "b8",
|
|
79
|
+
])
|
|
80
|
+
if result:
|
|
81
|
+
raise ValueError("pymatching.cli returned a non-zero exit code")
|
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import pathlib
|
|
4
|
+
import tempfile
|
|
5
|
+
from typing import Dict, List, Optional, Tuple
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
import sinter
|
|
11
|
+
import stim
|
|
12
|
+
|
|
13
|
+
from sinter import CompiledDecoder
|
|
14
|
+
from sinter._collection import post_selection_mask_from_4th_coord
|
|
15
|
+
from sinter._decoding._decoding_all_built_in_decoders import BUILT_IN_DECODERS
|
|
16
|
+
from sinter._decoding._decoding import sample_decode
|
|
17
|
+
from sinter._decoding._decoding_vacuous import VacuousDecoder
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_test_decoders() -> Tuple[List[str], Dict[str, sinter.Decoder]]:
|
|
21
|
+
available_decoders = list(BUILT_IN_DECODERS.keys())
|
|
22
|
+
custom_decoders = {}
|
|
23
|
+
try:
|
|
24
|
+
import pymatching
|
|
25
|
+
except ImportError:
|
|
26
|
+
available_decoders.remove('pymatching')
|
|
27
|
+
try:
|
|
28
|
+
import fusion_blossom
|
|
29
|
+
except ImportError:
|
|
30
|
+
available_decoders.remove('fusion_blossom')
|
|
31
|
+
try:
|
|
32
|
+
import mwpf
|
|
33
|
+
except ImportError:
|
|
34
|
+
available_decoders.remove('hypergraph_union_find')
|
|
35
|
+
available_decoders.remove('mw_parity_factor')
|
|
36
|
+
|
|
37
|
+
e = os.environ.get('SINTER_PYTEST_CUSTOM_DECODERS')
|
|
38
|
+
if e is not None:
|
|
39
|
+
for term in e.split(';'):
|
|
40
|
+
module, method = term.split(':')
|
|
41
|
+
for name, obj in getattr(__import__(module), method)().items():
|
|
42
|
+
custom_decoders[name] = obj
|
|
43
|
+
available_decoders.append(name)
|
|
44
|
+
|
|
45
|
+
available_decoders.append("also_vacuous")
|
|
46
|
+
custom_decoders["also_vacuous"] = VacuousDecoder()
|
|
47
|
+
return available_decoders, custom_decoders
|
|
48
|
+
|
|
49
|
+
TEST_DECODER_NAMES, TEST_CUSTOM_DECODERS = get_test_decoders()
|
|
50
|
+
|
|
51
|
+
DECODER_CASES = [
|
|
52
|
+
(decoder, force_streaming)
|
|
53
|
+
for decoder in TEST_DECODER_NAMES
|
|
54
|
+
for force_streaming in [None, True]
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@pytest.mark.parametrize('decoder,force_streaming', DECODER_CASES)
|
|
59
|
+
def test_decode_repetition_code(decoder: str, force_streaming: Optional[bool]):
|
|
60
|
+
circuit = stim.Circuit.generated('repetition_code:memory',
|
|
61
|
+
rounds=3,
|
|
62
|
+
distance=3,
|
|
63
|
+
after_clifford_depolarization=0.05)
|
|
64
|
+
result = sample_decode(
|
|
65
|
+
circuit_obj=circuit,
|
|
66
|
+
circuit_path=None,
|
|
67
|
+
dem_obj=circuit.detector_error_model(decompose_errors=True),
|
|
68
|
+
dem_path=None,
|
|
69
|
+
num_shots=1000,
|
|
70
|
+
decoder=decoder,
|
|
71
|
+
__private__unstable__force_decode_on_disk=force_streaming,
|
|
72
|
+
custom_decoders=TEST_CUSTOM_DECODERS,
|
|
73
|
+
)
|
|
74
|
+
assert result.discards == 0
|
|
75
|
+
if 'vacuous' not in decoder:
|
|
76
|
+
assert 1 <= result.errors <= 100
|
|
77
|
+
assert result.shots == 1000
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@pytest.mark.parametrize('decoder,force_streaming', DECODER_CASES)
|
|
81
|
+
def test_decode_surface_code(decoder: str, force_streaming: Optional[bool]):
|
|
82
|
+
circuit = stim.Circuit.generated(
|
|
83
|
+
"surface_code:rotated_memory_x",
|
|
84
|
+
distance=3,
|
|
85
|
+
rounds=15,
|
|
86
|
+
after_clifford_depolarization=0.001,
|
|
87
|
+
)
|
|
88
|
+
stats = sample_decode(
|
|
89
|
+
num_shots=1000,
|
|
90
|
+
circuit_obj=circuit,
|
|
91
|
+
circuit_path=None,
|
|
92
|
+
dem_obj=circuit.detector_error_model(decompose_errors=True),
|
|
93
|
+
dem_path=None,
|
|
94
|
+
decoder=decoder,
|
|
95
|
+
__private__unstable__force_decode_on_disk=force_streaming,
|
|
96
|
+
custom_decoders=TEST_CUSTOM_DECODERS,
|
|
97
|
+
)
|
|
98
|
+
if 'vacuous' not in decoder:
|
|
99
|
+
assert 0 <= stats.errors <= 50
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@pytest.mark.parametrize('decoder,force_streaming', DECODER_CASES)
|
|
103
|
+
def test_empty(decoder: str, force_streaming: Optional[bool]):
|
|
104
|
+
circuit = stim.Circuit()
|
|
105
|
+
result = sample_decode(
|
|
106
|
+
circuit_obj=circuit,
|
|
107
|
+
circuit_path=None,
|
|
108
|
+
dem_obj=circuit.detector_error_model(decompose_errors=True),
|
|
109
|
+
dem_path=None,
|
|
110
|
+
num_shots=1000,
|
|
111
|
+
decoder=decoder,
|
|
112
|
+
__private__unstable__force_decode_on_disk=force_streaming,
|
|
113
|
+
custom_decoders=TEST_CUSTOM_DECODERS,
|
|
114
|
+
)
|
|
115
|
+
assert result.discards == 0
|
|
116
|
+
assert result.shots == 1000
|
|
117
|
+
assert result.errors == 0
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@pytest.mark.parametrize('decoder,force_streaming', DECODER_CASES)
|
|
121
|
+
def test_no_observables(decoder: str, force_streaming: Optional[bool]):
|
|
122
|
+
circuit = stim.Circuit("""
|
|
123
|
+
X_ERROR(0.1) 0
|
|
124
|
+
M 0
|
|
125
|
+
DETECTOR rec[-1]
|
|
126
|
+
""")
|
|
127
|
+
result = sample_decode(
|
|
128
|
+
circuit_obj=circuit,
|
|
129
|
+
circuit_path=None,
|
|
130
|
+
dem_obj=circuit.detector_error_model(decompose_errors=True),
|
|
131
|
+
dem_path=None,
|
|
132
|
+
num_shots=1000,
|
|
133
|
+
decoder=decoder,
|
|
134
|
+
__private__unstable__force_decode_on_disk=force_streaming,
|
|
135
|
+
custom_decoders=TEST_CUSTOM_DECODERS,
|
|
136
|
+
)
|
|
137
|
+
assert result.discards == 0
|
|
138
|
+
assert result.shots == 1000
|
|
139
|
+
assert result.errors == 0
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@pytest.mark.parametrize('decoder,force_streaming', DECODER_CASES)
|
|
143
|
+
def test_invincible_observables(decoder: str, force_streaming: Optional[bool]):
|
|
144
|
+
circuit = stim.Circuit("""
|
|
145
|
+
X_ERROR(0.1) 0
|
|
146
|
+
M 0 1
|
|
147
|
+
DETECTOR rec[-2]
|
|
148
|
+
OBSERVABLE_INCLUDE(1) rec[-1]
|
|
149
|
+
""")
|
|
150
|
+
result = sample_decode(
|
|
151
|
+
circuit_obj=circuit,
|
|
152
|
+
circuit_path=None,
|
|
153
|
+
dem_obj=circuit.detector_error_model(decompose_errors=True),
|
|
154
|
+
dem_path=None,
|
|
155
|
+
num_shots=1000,
|
|
156
|
+
decoder=decoder,
|
|
157
|
+
__private__unstable__force_decode_on_disk=force_streaming,
|
|
158
|
+
custom_decoders=TEST_CUSTOM_DECODERS,
|
|
159
|
+
)
|
|
160
|
+
assert result.discards == 0
|
|
161
|
+
assert result.shots == 1000
|
|
162
|
+
assert result.errors == 0
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@pytest.mark.parametrize('decoder,force_streaming,offset', [(a, b, c) for a, b in DECODER_CASES for c in range(8)])
|
|
166
|
+
def test_observable_offsets_mod8(decoder: str, force_streaming: bool, offset: int):
|
|
167
|
+
circuit = stim.Circuit("""
|
|
168
|
+
X_ERROR(0.1) 0
|
|
169
|
+
MR 0
|
|
170
|
+
DETECTOR rec[-1]
|
|
171
|
+
""") * (8 + offset) + stim.Circuit("""
|
|
172
|
+
X_ERROR(0.1) 0
|
|
173
|
+
MR 0
|
|
174
|
+
OBSERVABLE_INCLUDE(0) rec[-1]
|
|
175
|
+
""")
|
|
176
|
+
result = sample_decode(
|
|
177
|
+
circuit_obj=circuit,
|
|
178
|
+
circuit_path=None,
|
|
179
|
+
dem_obj=circuit.detector_error_model(decompose_errors=True),
|
|
180
|
+
dem_path=None,
|
|
181
|
+
num_shots=1000,
|
|
182
|
+
decoder=decoder,
|
|
183
|
+
__private__unstable__force_decode_on_disk=force_streaming,
|
|
184
|
+
custom_decoders=TEST_CUSTOM_DECODERS,
|
|
185
|
+
)
|
|
186
|
+
assert result.discards == 0
|
|
187
|
+
assert result.shots == 1000
|
|
188
|
+
assert 50 <= result.errors <= 150
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@pytest.mark.parametrize('decoder,force_streaming', DECODER_CASES)
|
|
192
|
+
def test_no_detectors(decoder: str, force_streaming: Optional[bool]):
|
|
193
|
+
circuit = stim.Circuit("""
|
|
194
|
+
X_ERROR(0.1) 0
|
|
195
|
+
M 0
|
|
196
|
+
OBSERVABLE_INCLUDE(0) rec[-1]
|
|
197
|
+
""")
|
|
198
|
+
result = sample_decode(
|
|
199
|
+
circuit_obj=circuit,
|
|
200
|
+
circuit_path=None,
|
|
201
|
+
dem_obj=circuit.detector_error_model(decompose_errors=True),
|
|
202
|
+
dem_path=None,
|
|
203
|
+
num_shots=1000,
|
|
204
|
+
decoder=decoder,
|
|
205
|
+
__private__unstable__force_decode_on_disk=force_streaming,
|
|
206
|
+
custom_decoders=TEST_CUSTOM_DECODERS,
|
|
207
|
+
)
|
|
208
|
+
assert result.discards == 0
|
|
209
|
+
assert 50 <= result.errors <= 150
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
@pytest.mark.parametrize('decoder,force_streaming', DECODER_CASES)
|
|
213
|
+
def test_no_detectors_with_post_mask(decoder: str, force_streaming: Optional[bool]):
|
|
214
|
+
circuit = stim.Circuit("""
|
|
215
|
+
X_ERROR(0.1) 0
|
|
216
|
+
M 0
|
|
217
|
+
OBSERVABLE_INCLUDE(0) rec[-1]
|
|
218
|
+
""")
|
|
219
|
+
result = sample_decode(
|
|
220
|
+
circuit_obj=circuit,
|
|
221
|
+
circuit_path=None,
|
|
222
|
+
dem_obj=circuit.detector_error_model(decompose_errors=True),
|
|
223
|
+
dem_path=None,
|
|
224
|
+
post_mask=np.array([], dtype=np.uint8),
|
|
225
|
+
num_shots=1000,
|
|
226
|
+
decoder=decoder,
|
|
227
|
+
__private__unstable__force_decode_on_disk=force_streaming,
|
|
228
|
+
custom_decoders=TEST_CUSTOM_DECODERS,
|
|
229
|
+
)
|
|
230
|
+
assert result.discards == 0
|
|
231
|
+
assert 50 <= result.errors <= 150
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@pytest.mark.parametrize('decoder,force_streaming', DECODER_CASES)
|
|
235
|
+
def test_post_selection(decoder: str, force_streaming: Optional[bool]):
|
|
236
|
+
circuit = stim.Circuit("""
|
|
237
|
+
X_ERROR(0.6) 0
|
|
238
|
+
M 0
|
|
239
|
+
DETECTOR(2, 0, 0, 1) rec[-1]
|
|
240
|
+
OBSERVABLE_INCLUDE(0) rec[-1]
|
|
241
|
+
|
|
242
|
+
X_ERROR(0.5) 1
|
|
243
|
+
M 1
|
|
244
|
+
DETECTOR(1, 0, 0) rec[-1]
|
|
245
|
+
OBSERVABLE_INCLUDE(0) rec[-1]
|
|
246
|
+
|
|
247
|
+
X_ERROR(0.1) 2
|
|
248
|
+
M 2
|
|
249
|
+
OBSERVABLE_INCLUDE(0) rec[-1]
|
|
250
|
+
""")
|
|
251
|
+
result = sample_decode(
|
|
252
|
+
circuit_obj=circuit,
|
|
253
|
+
circuit_path=None,
|
|
254
|
+
dem_obj=circuit.detector_error_model(decompose_errors=True),
|
|
255
|
+
dem_path=None,
|
|
256
|
+
post_mask=post_selection_mask_from_4th_coord(circuit),
|
|
257
|
+
num_shots=2000,
|
|
258
|
+
decoder=decoder,
|
|
259
|
+
__private__unstable__force_decode_on_disk=force_streaming,
|
|
260
|
+
custom_decoders=TEST_CUSTOM_DECODERS,
|
|
261
|
+
)
|
|
262
|
+
assert 1050 <= result.discards <= 1350
|
|
263
|
+
if 'vacuous' not in decoder:
|
|
264
|
+
assert 40 <= result.errors <= 160
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@pytest.mark.parametrize('decoder,force_streaming', DECODER_CASES)
|
|
268
|
+
def test_observable_post_selection(decoder: str, force_streaming: Optional[bool]):
|
|
269
|
+
circuit = stim.Circuit("""
|
|
270
|
+
X_ERROR(0.1) 0
|
|
271
|
+
X_ERROR(0.2) 1
|
|
272
|
+
M 0 1
|
|
273
|
+
OBSERVABLE_INCLUDE(0) rec[-1]
|
|
274
|
+
OBSERVABLE_INCLUDE(1) rec[-1] rec[-2]
|
|
275
|
+
""")
|
|
276
|
+
result = sample_decode(
|
|
277
|
+
circuit_obj=circuit,
|
|
278
|
+
circuit_path=None,
|
|
279
|
+
dem_obj=circuit.detector_error_model(decompose_errors=True),
|
|
280
|
+
dem_path=None,
|
|
281
|
+
post_mask=None,
|
|
282
|
+
postselected_observable_mask=np.array([1], dtype=np.uint8),
|
|
283
|
+
num_shots=10000,
|
|
284
|
+
decoder=decoder,
|
|
285
|
+
__private__unstable__force_decode_on_disk=force_streaming,
|
|
286
|
+
custom_decoders=TEST_CUSTOM_DECODERS,
|
|
287
|
+
)
|
|
288
|
+
np.testing.assert_allclose(result.discards / result.shots, 0.2, atol=0.1)
|
|
289
|
+
if 'vacuous' not in decoder:
|
|
290
|
+
np.testing.assert_allclose(result.errors / (result.shots - result.discards), 0.1, atol=0.05)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
@pytest.mark.parametrize('decoder,force_streaming', DECODER_CASES)
|
|
294
|
+
def test_error_splitting(decoder: str, force_streaming: Optional[bool]):
|
|
295
|
+
circuit = stim.Circuit("""
|
|
296
|
+
X_ERROR(0.1) 0
|
|
297
|
+
X_ERROR(0.2) 1
|
|
298
|
+
M 0 1
|
|
299
|
+
OBSERVABLE_INCLUDE(0) rec[-1]
|
|
300
|
+
OBSERVABLE_INCLUDE(1) rec[-1] rec[-2]
|
|
301
|
+
""")
|
|
302
|
+
result = sample_decode(
|
|
303
|
+
circuit_obj=circuit,
|
|
304
|
+
circuit_path=None,
|
|
305
|
+
dem_obj=circuit.detector_error_model(decompose_errors=True),
|
|
306
|
+
dem_path=None,
|
|
307
|
+
post_mask=None,
|
|
308
|
+
num_shots=10000,
|
|
309
|
+
decoder=decoder,
|
|
310
|
+
count_observable_error_combos=True,
|
|
311
|
+
__private__unstable__force_decode_on_disk=force_streaming,
|
|
312
|
+
custom_decoders=TEST_CUSTOM_DECODERS,
|
|
313
|
+
)
|
|
314
|
+
assert result.discards == 0
|
|
315
|
+
assert set(result.custom_counts.keys()) == {'obs_mistake_mask=E_', 'obs_mistake_mask=_E', 'obs_mistake_mask=EE'}
|
|
316
|
+
if 'vacuous' not in decoder:
|
|
317
|
+
np.testing.assert_allclose(result.errors / result.shots, 1 - 0.8 * 0.9, atol=0.05)
|
|
318
|
+
np.testing.assert_allclose(result.custom_counts['obs_mistake_mask=E_'] / result.shots, 0.1 * 0.2, atol=0.05)
|
|
319
|
+
np.testing.assert_allclose(result.custom_counts['obs_mistake_mask=_E'] / result.shots, 0.1 * 0.8, atol=0.05)
|
|
320
|
+
np.testing.assert_allclose(result.custom_counts['obs_mistake_mask=EE'] / result.shots, 0.9 * 0.2, atol=0.05)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
@pytest.mark.parametrize('decoder,force_streaming', DECODER_CASES)
|
|
324
|
+
def test_detector_counting(decoder: str, force_streaming: Optional[bool]):
|
|
325
|
+
circuit = stim.Circuit("""
|
|
326
|
+
X_ERROR(0.1) 0
|
|
327
|
+
X_ERROR(0.2) 1
|
|
328
|
+
M 0 1
|
|
329
|
+
DETECTOR rec[-1]
|
|
330
|
+
DETECTOR rec[-2]
|
|
331
|
+
OBSERVABLE_INCLUDE(0) rec[-1]
|
|
332
|
+
OBSERVABLE_INCLUDE(1) rec[-1] rec[-2]
|
|
333
|
+
""")
|
|
334
|
+
result = sample_decode(
|
|
335
|
+
circuit_obj=circuit,
|
|
336
|
+
circuit_path=None,
|
|
337
|
+
dem_obj=circuit.detector_error_model(decompose_errors=True),
|
|
338
|
+
dem_path=None,
|
|
339
|
+
post_mask=None,
|
|
340
|
+
num_shots=10000,
|
|
341
|
+
decoder=decoder,
|
|
342
|
+
count_detection_events=True,
|
|
343
|
+
__private__unstable__force_decode_on_disk=force_streaming,
|
|
344
|
+
custom_decoders=TEST_CUSTOM_DECODERS,
|
|
345
|
+
)
|
|
346
|
+
assert result.discards == 0
|
|
347
|
+
assert result.custom_counts['detectors_checked'] == 20000
|
|
348
|
+
assert 0.3 * 10000 * 0.5 <= result.custom_counts['detection_events'] <= 0.3 * 10000 * 2.0
|
|
349
|
+
assert set(result.custom_counts.keys()) == {'detectors_checked', 'detection_events'}
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
@pytest.mark.parametrize('decoder,force_streaming', DECODER_CASES)
|
|
353
|
+
def test_decode_fails_correctly(decoder: str, force_streaming: Optional[bool]):
|
|
354
|
+
decoder_obj = BUILT_IN_DECODERS.get(decoder)
|
|
355
|
+
with tempfile.TemporaryDirectory() as d:
|
|
356
|
+
d = pathlib.Path(d)
|
|
357
|
+
circuit = stim.Circuit("""
|
|
358
|
+
REPEAT 9 {
|
|
359
|
+
MR(0.001) 0
|
|
360
|
+
DETECTOR rec[-1]
|
|
361
|
+
OBSERVABLE_INCLUDE(0) rec[-1]
|
|
362
|
+
}
|
|
363
|
+
""")
|
|
364
|
+
dem = circuit.detector_error_model()
|
|
365
|
+
circuit.to_file(d / 'circuit.stim')
|
|
366
|
+
dem.to_file(d / 'dem.dem')
|
|
367
|
+
with open(d / 'bad_dets.b8', 'wb') as f:
|
|
368
|
+
f.write(b'!')
|
|
369
|
+
|
|
370
|
+
if 'vacuous' not in decoder:
|
|
371
|
+
with pytest.raises(Exception):
|
|
372
|
+
decoder_obj.decode_via_files(
|
|
373
|
+
num_shots=1,
|
|
374
|
+
num_dets=dem.num_detectors,
|
|
375
|
+
num_obs=dem.num_observables,
|
|
376
|
+
dem_path=d / 'dem.dem',
|
|
377
|
+
dets_b8_in_path=d / 'bad_dets.b8',
|
|
378
|
+
obs_predictions_b8_out_path=d / 'predict.b8',
|
|
379
|
+
tmp_dir=d,
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
@pytest.mark.parametrize('decoder', TEST_DECODER_NAMES)
|
|
384
|
+
def test_full_scale(decoder: str):
|
|
385
|
+
result, = sinter.collect(
|
|
386
|
+
num_workers=2,
|
|
387
|
+
tasks=[sinter.Task(circuit=stim.Circuit())],
|
|
388
|
+
decoders=[decoder],
|
|
389
|
+
max_shots=1000,
|
|
390
|
+
custom_decoders=TEST_CUSTOM_DECODERS,
|
|
391
|
+
)
|
|
392
|
+
assert result.discards == 0
|
|
393
|
+
assert result.shots == 1000
|
|
394
|
+
assert result.errors == 0
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def test_infer_decode_via_files_from_decode_from_compile_decoder_for_dem():
|
|
398
|
+
class IncompleteDecoder(sinter.Decoder):
|
|
399
|
+
pass
|
|
400
|
+
|
|
401
|
+
class WrongDecoder(sinter.Decoder, sinter.CompiledDecoder):
|
|
402
|
+
def compile_decoder_for_dem(
|
|
403
|
+
self,
|
|
404
|
+
*,
|
|
405
|
+
dem: stim.DetectorErrorModel,
|
|
406
|
+
) -> CompiledDecoder:
|
|
407
|
+
return self
|
|
408
|
+
def decode_shots_bit_packed(
|
|
409
|
+
self,
|
|
410
|
+
*,
|
|
411
|
+
bit_packed_detection_event_data: np.ndarray,
|
|
412
|
+
) -> np.ndarray:
|
|
413
|
+
return np.zeros(shape=5, dtype=np.bool_)
|
|
414
|
+
|
|
415
|
+
class TrivialCompiledDecoder(sinter.CompiledDecoder):
|
|
416
|
+
def __init__(self, num_obs: int):
|
|
417
|
+
self.num_obs = -(-num_obs // 8)
|
|
418
|
+
|
|
419
|
+
def decode_shots_bit_packed(
|
|
420
|
+
self,
|
|
421
|
+
*,
|
|
422
|
+
bit_packed_detection_event_data: np.ndarray,
|
|
423
|
+
) -> np.ndarray:
|
|
424
|
+
return np.zeros(dtype=np.uint8, shape=(bit_packed_detection_event_data.shape[0], self.num_obs))
|
|
425
|
+
|
|
426
|
+
class TrivialDecoder(sinter.Decoder):
|
|
427
|
+
def compile_decoder_for_dem(
|
|
428
|
+
self,
|
|
429
|
+
*,
|
|
430
|
+
dem: stim.DetectorErrorModel,
|
|
431
|
+
) -> CompiledDecoder:
|
|
432
|
+
return TrivialCompiledDecoder(num_obs=dem.num_observables)
|
|
433
|
+
|
|
434
|
+
circuit = stim.Circuit.generated("repetition_code:memory", distance=3, rounds=3)
|
|
435
|
+
dem = circuit.detector_error_model()
|
|
436
|
+
|
|
437
|
+
with tempfile.TemporaryDirectory() as d:
|
|
438
|
+
d = pathlib.Path(d)
|
|
439
|
+
|
|
440
|
+
circuit.compile_detector_sampler().sample_write(
|
|
441
|
+
shots=10,
|
|
442
|
+
filepath=d / 'dets.b8',
|
|
443
|
+
format='b8',
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
dem.to_file(d / 'dem.dem')
|
|
447
|
+
|
|
448
|
+
with pytest.raises(NotImplementedError, match='compile_decoder_for_dem'):
|
|
449
|
+
IncompleteDecoder().decode_via_files(
|
|
450
|
+
num_shots=10,
|
|
451
|
+
num_dets=dem.num_detectors,
|
|
452
|
+
num_obs=dem.num_observables,
|
|
453
|
+
dem_path=d / 'dem.dem',
|
|
454
|
+
dets_b8_in_path=d / 'dets.b8',
|
|
455
|
+
obs_predictions_b8_out_path=d / 'obs.b8',
|
|
456
|
+
tmp_dir=d,
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
with pytest.raises(ValueError, match='shape='):
|
|
460
|
+
WrongDecoder().decode_via_files(
|
|
461
|
+
num_shots=10,
|
|
462
|
+
num_dets=dem.num_detectors,
|
|
463
|
+
num_obs=dem.num_observables,
|
|
464
|
+
dem_path=d / 'dem.dem',
|
|
465
|
+
dets_b8_in_path=d / 'dets.b8',
|
|
466
|
+
obs_predictions_b8_out_path=d / 'obs.b8',
|
|
467
|
+
tmp_dir=d,
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
TrivialDecoder().decode_via_files(
|
|
471
|
+
num_shots=10,
|
|
472
|
+
num_dets=dem.num_detectors,
|
|
473
|
+
num_obs=dem.num_observables,
|
|
474
|
+
dem_path=d / 'dem.dem',
|
|
475
|
+
dets_b8_in_path=d / 'dets.b8',
|
|
476
|
+
obs_predictions_b8_out_path=d / 'obs.b8',
|
|
477
|
+
tmp_dir=d,
|
|
478
|
+
)
|
|
479
|
+
obs = np.fromfile(d / 'obs.b8', dtype=np.uint8, count=10)
|
|
480
|
+
np.testing.assert_array_equal(obs, [0] * 10)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from sinter._decoding._decoding_decoder_class import Decoder, CompiledDecoder
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class VacuousDecoder(Decoder):
|
|
7
|
+
"""An example decoder that always predicts the observables aren't flipped.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
def compile_decoder_for_dem(self, *, dem: 'stim.DetectorErrorModel') -> CompiledDecoder:
|
|
11
|
+
return VacuousCompiledDecoder(shape=(dem.num_observables + 7) // 8)
|
|
12
|
+
|
|
13
|
+
def decode_via_files(self,
|
|
14
|
+
*,
|
|
15
|
+
num_shots: int,
|
|
16
|
+
num_dets: int,
|
|
17
|
+
num_obs: int,
|
|
18
|
+
dem_path: 'pathlib.Path',
|
|
19
|
+
dets_b8_in_path: 'pathlib.Path',
|
|
20
|
+
obs_predictions_b8_out_path: 'pathlib.Path',
|
|
21
|
+
tmp_dir: 'pathlib.Path',
|
|
22
|
+
) -> None:
|
|
23
|
+
with open(obs_predictions_b8_out_path, 'wb') as f:
|
|
24
|
+
f.write(b'\0' * (num_obs * num_shots))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class VacuousCompiledDecoder(CompiledDecoder):
|
|
28
|
+
"""An example decoder that always predicts the observables aren't flipped.
|
|
29
|
+
"""
|
|
30
|
+
def __init__(self, shape: int):
|
|
31
|
+
self.shape = shape
|
|
32
|
+
|
|
33
|
+
def decode_shots_bit_packed(
|
|
34
|
+
self,
|
|
35
|
+
*,
|
|
36
|
+
bit_packed_detection_event_data: 'np.ndarray',
|
|
37
|
+
) -> 'np.ndarray':
|
|
38
|
+
return np.zeros(shape=(bit_packed_detection_event_data.shape[0], self.shape), dtype=np.uint8)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from sinter._data import Task, AnonTaskStats
|
|
6
|
+
from sinter._decoding._sampler import Sampler, CompiledSampler
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PerfectionistSampler(Sampler):
|
|
10
|
+
"""Predicts obs aren't flipped. Discards shots with any detection events."""
|
|
11
|
+
def compiled_sampler_for_task(self, task: Task) -> CompiledSampler:
|
|
12
|
+
return CompiledPerfectionistSampler(task)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CompiledPerfectionistSampler(CompiledSampler):
|
|
16
|
+
def __init__(self, task: Task):
|
|
17
|
+
self.stim_sampler = task.circuit.compile_detector_sampler()
|
|
18
|
+
|
|
19
|
+
def sample(self, max_shots: int) -> AnonTaskStats:
|
|
20
|
+
t0 = time.monotonic()
|
|
21
|
+
dets, obs = self.stim_sampler.sample(
|
|
22
|
+
shots=max_shots,
|
|
23
|
+
bit_packed=True,
|
|
24
|
+
separate_observables=True,
|
|
25
|
+
)
|
|
26
|
+
num_shots = dets.shape[0]
|
|
27
|
+
discards = np.any(dets, axis=1)
|
|
28
|
+
errors = np.any(obs, axis=1)
|
|
29
|
+
num_discards = np.count_nonzero(discards)
|
|
30
|
+
num_errors = np.count_nonzero(errors & ~discards)
|
|
31
|
+
t1 = time.monotonic()
|
|
32
|
+
|
|
33
|
+
return AnonTaskStats(
|
|
34
|
+
shots=num_shots,
|
|
35
|
+
errors=num_errors,
|
|
36
|
+
discards=num_discards,
|
|
37
|
+
seconds=t1 - t0,
|
|
38
|
+
)
|