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.

Files changed (62) hide show
  1. sinter/__init__.py +47 -0
  2. sinter/_collection/__init__.py +10 -0
  3. sinter/_collection/_collection.py +480 -0
  4. sinter/_collection/_collection_manager.py +581 -0
  5. sinter/_collection/_collection_manager_test.py +287 -0
  6. sinter/_collection/_collection_test.py +317 -0
  7. sinter/_collection/_collection_worker_loop.py +35 -0
  8. sinter/_collection/_collection_worker_state.py +259 -0
  9. sinter/_collection/_collection_worker_test.py +222 -0
  10. sinter/_collection/_mux_sampler.py +56 -0
  11. sinter/_collection/_printer.py +65 -0
  12. sinter/_collection/_sampler_ramp_throttled.py +66 -0
  13. sinter/_collection/_sampler_ramp_throttled_test.py +144 -0
  14. sinter/_command/__init__.py +0 -0
  15. sinter/_command/_main.py +39 -0
  16. sinter/_command/_main_collect.py +350 -0
  17. sinter/_command/_main_collect_test.py +482 -0
  18. sinter/_command/_main_combine.py +84 -0
  19. sinter/_command/_main_combine_test.py +153 -0
  20. sinter/_command/_main_plot.py +817 -0
  21. sinter/_command/_main_plot_test.py +445 -0
  22. sinter/_command/_main_predict.py +75 -0
  23. sinter/_command/_main_predict_test.py +36 -0
  24. sinter/_data/__init__.py +20 -0
  25. sinter/_data/_anon_task_stats.py +89 -0
  26. sinter/_data/_anon_task_stats_test.py +35 -0
  27. sinter/_data/_collection_options.py +106 -0
  28. sinter/_data/_collection_options_test.py +24 -0
  29. sinter/_data/_csv_out.py +74 -0
  30. sinter/_data/_existing_data.py +173 -0
  31. sinter/_data/_existing_data_test.py +41 -0
  32. sinter/_data/_task.py +311 -0
  33. sinter/_data/_task_stats.py +244 -0
  34. sinter/_data/_task_stats_test.py +140 -0
  35. sinter/_data/_task_test.py +38 -0
  36. sinter/_decoding/__init__.py +16 -0
  37. sinter/_decoding/_decoding.py +419 -0
  38. sinter/_decoding/_decoding_all_built_in_decoders.py +25 -0
  39. sinter/_decoding/_decoding_decoder_class.py +161 -0
  40. sinter/_decoding/_decoding_fusion_blossom.py +193 -0
  41. sinter/_decoding/_decoding_mwpf.py +302 -0
  42. sinter/_decoding/_decoding_pymatching.py +81 -0
  43. sinter/_decoding/_decoding_test.py +480 -0
  44. sinter/_decoding/_decoding_vacuous.py +38 -0
  45. sinter/_decoding/_perfectionist_sampler.py +38 -0
  46. sinter/_decoding/_sampler.py +72 -0
  47. sinter/_decoding/_stim_then_decode_sampler.py +222 -0
  48. sinter/_decoding/_stim_then_decode_sampler_test.py +192 -0
  49. sinter/_plotting.py +619 -0
  50. sinter/_plotting_test.py +108 -0
  51. sinter/_predict.py +381 -0
  52. sinter/_predict_test.py +227 -0
  53. sinter/_probability_util.py +519 -0
  54. sinter/_probability_util_test.py +281 -0
  55. sinter-1.15.0.data/data/README.md +332 -0
  56. sinter-1.15.0.data/data/readme_example_plot.png +0 -0
  57. sinter-1.15.0.data/data/requirements.txt +4 -0
  58. sinter-1.15.0.dist-info/METADATA +354 -0
  59. sinter-1.15.0.dist-info/RECORD +62 -0
  60. sinter-1.15.0.dist-info/WHEEL +5 -0
  61. sinter-1.15.0.dist-info/entry_points.txt +2 -0
  62. sinter-1.15.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,108 @@
1
+ import io
2
+
3
+ from matplotlib import pyplot as plt
4
+
5
+ import sinter
6
+
7
+
8
+ def test_better_sorted_str_terms():
9
+ f = sinter.better_sorted_str_terms
10
+ assert f('everyone et al. 2020') == ('everyone et al', '.', ' ', 2020)
11
+ assert f('a') == ('a',)
12
+ assert f('abc') == ('abc',)
13
+ assert f('a1b2') == ('a', 1, 'b', 2)
14
+ assert f('a1.5b2') == ('a', 1.5, 'b', 2)
15
+ assert f('a1.5.3b2') == ('a', (1, 5, 3), 'b', 2)
16
+ assert f(1) < f(None)
17
+ assert f(1) < f('2')
18
+ assert f('2') > f(1)
19
+ assert sorted([
20
+ "planar d=10 r=30",
21
+ "planar d=16 r=36",
22
+ "planar d=4 r=12",
23
+ "toric d=10 r=30",
24
+ "toric d=18 r=54",
25
+ ], key=f) == [
26
+ "planar d=4 r=12",
27
+ "planar d=10 r=30",
28
+ "planar d=16 r=36",
29
+ "toric d=10 r=30",
30
+ "toric d=18 r=54",
31
+ ]
32
+ assert sorted([
33
+ "a1",
34
+ "1a",
35
+ ], key=f) == [
36
+ "1a",
37
+ "a1",
38
+ ]
39
+
40
+
41
+ def test_plotting_does_not_crash():
42
+ data = io.StringIO()
43
+ data.write(""" shots,errors,discards,seconds,decoder,strong_id,json_metadata
44
+ 1000000, 837,0,36.6,pymatching,9f7e20c54fec45b6aef7491b774dd5c0a3b9a005aa82faf5b9c051d6e40d60a9,"{""d"":3,""p"":0.001}"
45
+ 53498,1099,0,6.52,pymatching,3f40432443a99b933fb548b831fb54e7e245d9d73a35c03ea5a2fb2ce270f8c8,"{""d"":3,""p"":0.005}"
46
+ 16269,1023,0,3.23,pymatching,17b2e0c99560d20307204494ac50e31b33e50721b4ebae99d9e3577ae7248874,"{""d"":3,""p"":0.01}"
47
+ 1000000, 151,0,77.3,pymatching,e179a18739201250371ffaae0197d8fa19d26b58dfc2942f9f1c85568645387a,"{""d"":5,""p"":0.001}"
48
+ 11363,1068,0,12.5,pymatching,a4dec28934a033215ff1389651a26114ecc22016a6e122008830cf7dd04ba5ad,"{""d"":5,""p"":0.01}"
49
+ 61569,1001,0,24.5,pymatching,2fefcc356752482fb4c6d912c228f6d18762f5752796c668b6abeb7775f5de92,"{""d"":5,""p"":0.005}"
50
+ """)
51
+ data.seek(0)
52
+ stats = sinter.stats_from_csv_files(data)
53
+
54
+ fig, ax = plt.subplots(1, 1)
55
+ sinter.plot_error_rate(
56
+ ax=ax,
57
+ stats=stats,
58
+ group_func=lambda e: f"Rotated Surface Code d={e.json_metadata['d']}",
59
+ x_func=lambda e: e.json_metadata['p'],
60
+ plot_args_func=lambda k, e: {'marker': "ov*sp^<>8PhH+xXDd|"[k]},
61
+ )
62
+ sinter.plot_error_rate(
63
+ ax=ax,
64
+ stats=stats,
65
+ group_func=lambda e: f"Rotated Surface Code d={e.json_metadata['d']}",
66
+ x_func=lambda e: e.json_metadata['p'],
67
+ plot_args_func=lambda k, e: {'marker': "ov*sp^<>8PhH+xXDd|"[k]},
68
+ failure_units_per_shot_func=lambda stats: stats.json_metadata['d'] * 3,
69
+ )
70
+ sinter.plot_error_rate(
71
+ ax=ax,
72
+ stats=stats,
73
+ group_func=lambda e: f"Rotated Surface Code d={e.json_metadata['d']}",
74
+ x_func=lambda e: e.json_metadata['p'],
75
+ plot_args_func=lambda k, e: {'marker': "ov*sp^<>8PhH+xXDd|"[k]},
76
+ failure_units_per_shot_func=lambda stats: stats.json_metadata['d'] * 3,
77
+ highlight_max_likelihood_factor=None,
78
+ )
79
+ sinter.plot_discard_rate(
80
+ ax=ax,
81
+ stats=stats,
82
+ group_func=lambda e: f"Rotated Surface Code d={e.json_metadata['d']}",
83
+ x_func=lambda e: e.json_metadata['p'],
84
+ plot_args_func=lambda k, e: {'marker': "ov*sp^<>8PhH+xXDd|"[k]},
85
+ )
86
+ sinter.plot_discard_rate(
87
+ ax=ax,
88
+ stats=stats,
89
+ group_func=lambda e: f"Rotated Surface Code d={e.json_metadata['d']}",
90
+ x_func=lambda e: e.json_metadata['p'],
91
+ plot_args_func=lambda k, e: {'marker': "ov*sp^<>8PhH+xXDd|"[k]},
92
+ highlight_max_likelihood_factor=None,
93
+ )
94
+ sinter.plot_discard_rate(
95
+ ax=ax,
96
+ stats=stats,
97
+ group_func=lambda e: f"Rotated Surface Code d={e.json_metadata['d']}",
98
+ x_func=lambda e: e.json_metadata['p'],
99
+ plot_args_func=lambda k, e: {'marker': "ov*sp^<>8PhH+xXDd|"[k]},
100
+ failure_units_per_shot_func=lambda stats: stats.json_metadata['d'] * 3,
101
+ )
102
+
103
+
104
+ def test_group_by():
105
+ assert sinter.group_by([1, 2, 3], key=lambda i: i == 2) == {False: [1, 3], True: [2]}
106
+ assert sinter.group_by(range(10), key=lambda i: i % 3) == {0: [0, 3, 6, 9], 1: [1, 4, 7], 2: [2, 5, 8]}
107
+ assert sinter.group_by([], key=lambda i: 0) == {}
108
+ assert sinter.group_by([1, 2, 3, 1], key=lambda i: 0) == {0: [1, 2, 3, 1]}
sinter/_predict.py ADDED
@@ -0,0 +1,381 @@
1
+ import os.path
2
+ import pathlib
3
+
4
+ import math
5
+ import numpy as np
6
+ import stim
7
+ import tempfile
8
+ from typing import Optional, Union, Dict, TYPE_CHECKING
9
+
10
+ from sinter._collection import post_selection_mask_from_4th_coord
11
+ from sinter._decoding import Decoder, BUILT_IN_DECODERS, streaming_post_select
12
+
13
+ if TYPE_CHECKING:
14
+ import sinter
15
+
16
+
17
+ def _converted_on_disk(
18
+ in_path: pathlib.Path,
19
+ out_path: pathlib.Path,
20
+ num_dets: int,
21
+ num_obs: int,
22
+ in_format: str,
23
+ out_format: str) -> pathlib.Path:
24
+ if in_format == out_format:
25
+ return in_path
26
+ raw = stim.read_shot_data_file(
27
+ path=str(in_path),
28
+ format=in_format,
29
+ bit_pack=True,
30
+ num_detectors=num_dets,
31
+ num_observables=num_obs,
32
+ )
33
+ stim.write_shot_data_file(
34
+ data=raw,
35
+ path=str(out_path),
36
+ format=out_format,
37
+ num_detectors=num_dets,
38
+ num_observables=num_obs,
39
+ )
40
+ return out_path
41
+
42
+
43
+ def predict_on_disk(
44
+ *,
45
+ decoder: str,
46
+ dem_path: Union[str, pathlib.Path],
47
+ dets_path: Union[str, pathlib.Path],
48
+ dets_format: str,
49
+ obs_out_path: Union[str, pathlib.Path],
50
+ obs_out_format: str,
51
+ postselect_detectors_with_non_zero_4th_coord: bool = False,
52
+ discards_out_path: Optional[Union[str, pathlib.Path]] = None,
53
+ discards_out_format: Optional[str] = None,
54
+ custom_decoders: Dict[str, 'sinter.Decoder'] = None,
55
+ ) -> None:
56
+ """Performs decoding and postselection on disk.
57
+
58
+ Args:
59
+ decoder: The decoder to use for decoding.
60
+ dem_path: The detector error model to use to configure the decoder.
61
+ dets_path: Where the detection event data is stored on disk.
62
+ dets_format: The format the detection event data is stored in (e.g. '01' or 'b8').
63
+ obs_out_path: Where to write predicted observable flip data on disk.
64
+ Note that the predicted observable flip data will not included data from shots discarded by postselection.
65
+ Use the data in discards_out_path to determine which shots were discarded.
66
+ obs_out_format: The format to write the observable flip data in (e.g. '01' or 'b8').
67
+ postselect_detectors_with_non_zero_4th_coord: Activates postselection. Detectors that have a non-zero 4th
68
+ coordinate will be postselected. Any shot where a postselected detector fires will be discarded.
69
+ Requires specifying discards_out_path, for indicating which shots were discarded.
70
+ discards_out_path: Only used if postselection is being used. Where to write discard data on disk.
71
+ discards_out_format: The format to write discard data in (e.g. '01' or 'b8').
72
+ custom_decoders: Custom decoders that can be used if requested by name.
73
+ """
74
+ if (discards_out_path is not None) != (discards_out_format is not None):
75
+ raise ValueError('(discards_out_path is not None) != (discards_out_format is not None)')
76
+ if (discards_out_path is not None) != postselect_detectors_with_non_zero_4th_coord:
77
+ raise ValueError('(discards_out_path is not None) != postselect_detectors_with_non_zero_4th_coord')
78
+
79
+ dem_path = pathlib.Path(dem_path)
80
+ dets_path = pathlib.Path(dets_path)
81
+ obs_out_path = pathlib.Path(obs_out_path)
82
+ if discards_out_path is not None:
83
+ discards_out_path = pathlib.Path(discards_out_path)
84
+
85
+ with tempfile.TemporaryDirectory() as tmp_dir:
86
+ tmp_dir = pathlib.Path(tmp_dir)
87
+ decode_obj: Optional[Decoder] = None
88
+ if custom_decoders is not None:
89
+ decode_obj = custom_decoders.get(decoder)
90
+ if decode_obj is None:
91
+ decode_obj = BUILT_IN_DECODERS.get(decoder)
92
+ if decode_obj is None:
93
+ raise NotImplementedError(f"Unrecognized decoder: {decoder!r}")
94
+
95
+ with open(dem_path) as f:
96
+ dem = stim.DetectorErrorModel(f.read())
97
+
98
+ num_dets = dem.num_detectors
99
+ num_det_bytes = math.ceil(num_dets / 8)
100
+ num_obs = dem.num_observables
101
+
102
+ dets_b8_path = _converted_on_disk(
103
+ in_path=dets_path,
104
+ out_path=tmp_dir / 'sinter_dets.b8',
105
+ in_format=dets_format,
106
+ out_format='b8',
107
+ num_dets=num_dets,
108
+ num_obs=0)
109
+ if num_det_bytes == 0:
110
+ raise NotImplementedError("Don't know how many shots there are, because num_det_bytes=0.")
111
+ num_shots = os.path.getsize(dets_b8_path) // num_det_bytes
112
+
113
+ if discards_out_path is not None:
114
+ if discards_out_format == 'b8':
115
+ discards_b8_path = discards_out_path
116
+ else:
117
+ discards_b8_path = tmp_dir / 'sinter_discards.b8'
118
+ post_selection_mask = np.zeros(dtype=np.uint8, shape=math.ceil(num_dets / 8))
119
+ if postselect_detectors_with_non_zero_4th_coord:
120
+ post_selection_mask = post_selection_mask_from_4th_coord(dem)
121
+ kept_dets_b8_path = tmp_dir / 'sinter_dets.kept.b8'
122
+ num_discards = streaming_post_select(
123
+ num_shots=num_shots,
124
+ num_dets=num_dets,
125
+ num_obs=num_obs,
126
+ dets_in_b8=dets_b8_path,
127
+ obs_in_b8=None,
128
+ obs_out_b8=None,
129
+ discards_out_b8=discards_b8_path,
130
+ dets_out_b8=kept_dets_b8_path,
131
+ post_mask=post_selection_mask,
132
+ )
133
+ assert discards_out_format is not None
134
+ _converted_on_disk(
135
+ in_path=discards_b8_path,
136
+ out_path=discards_out_path,
137
+ out_format=discards_out_format,
138
+ in_format='b8',
139
+ num_dets=1,
140
+ num_obs=0,
141
+ )
142
+ num_kept_shots = num_shots - num_discards
143
+ else:
144
+ kept_dets_b8_path = dets_b8_path
145
+ num_kept_shots = num_shots
146
+ if postselect_detectors_with_non_zero_4th_coord:
147
+ raise ValueError('postselect_detectors_with_non_zero_4th_coord and discards_out_path is None')
148
+
149
+ if obs_out_format != 'b8':
150
+ obs_inter = tmp_dir / 'sinter_obs_inter.b8'
151
+ else:
152
+ obs_inter = obs_out_path
153
+ decode_obj.decode_via_files(
154
+ num_shots=num_kept_shots,
155
+ num_dets=num_dets,
156
+ num_obs=num_obs,
157
+ dem_path=dem_path,
158
+ dets_b8_in_path=kept_dets_b8_path,
159
+ obs_predictions_b8_out_path=obs_inter,
160
+ tmp_dir=tmp_dir,
161
+ )
162
+ _converted_on_disk(
163
+ in_path=obs_inter,
164
+ out_path=obs_out_path,
165
+ out_format=obs_out_format,
166
+ in_format='b8',
167
+ num_dets=0,
168
+ num_obs=num_obs,
169
+ )
170
+
171
+
172
+ def predict_discards_bit_packed(
173
+ *,
174
+ dem: stim.DetectorErrorModel,
175
+ dets_bit_packed: np.ndarray,
176
+ postselect_detectors_with_non_zero_4th_coord: bool,
177
+ ) -> np.ndarray:
178
+ """Determines which shots to discard due to postselected detectors firing.
179
+
180
+ Args:
181
+ dem: The detector error model the detector data applies to.
182
+ This is also where coordinate data is read from, in order to
183
+ determine which detectors to postselect as not having fired.
184
+ dets_bit_packed: A uint8 numpy array with shape
185
+ (num_shots, math.ceil(num_dets / 8)). Contains bit packed detection
186
+ event data.
187
+ postselect_detectors_with_non_zero_4th_coord: Determines how
188
+ postselection is done. Currently, this is the only option so it has
189
+ to be set to True. Any detector from the detector error model that
190
+ specifies coordinate data with at least four coordinates where the
191
+ fourth coordinate (coord index 3) is non-zero will be postselected.
192
+
193
+ Returns:
194
+ A numpy bool_ array with shape (num_shots,) where False means not discarded and
195
+ True means yes discarded.
196
+ """
197
+ if not postselect_detectors_with_non_zero_4th_coord:
198
+ raise ValueError("not postselect_detectors_with_non_zero_4th_coord")
199
+ num_dets = dem.num_detectors
200
+ nb = math.ceil(num_dets / 8)
201
+ if len(dets_bit_packed.shape) != 2:
202
+ raise ValueError(f'len(dets_data_bit_packed.shape={dets_bit_packed.shape}) != 2')
203
+ if dets_bit_packed.shape[1] != nb:
204
+ raise ValueError(f'dets_data_bit_packed.shape[1]={dets_bit_packed.shape[1]} != math.ceil(dem.num_detectors={dem.num_detectors} / 8)')
205
+ if dets_bit_packed.dtype != np.uint8:
206
+ raise ValueError(f'dets_data_bit_packed.dtype={dets_bit_packed.dtype} != np.uint8')
207
+
208
+ post_selection_mask = np.zeros(dtype=np.uint8, shape=nb)
209
+ if postselect_detectors_with_non_zero_4th_coord:
210
+ for k, coord in dem.get_detector_coordinates().items():
211
+ if len(coord) >= 4 and coord[3]:
212
+ post_selection_mask[k // 8] |= 1 << (k % 8)
213
+ return np.any(dets_bit_packed & post_selection_mask, axis=1)
214
+
215
+
216
+ def predict_observables(
217
+ *,
218
+ dem: stim.DetectorErrorModel,
219
+ dets: np.ndarray,
220
+ decoder: str,
221
+ bit_pack_result: bool = False,
222
+ custom_decoders: Optional[Dict[str, 'sinter.Decoder']] = None,
223
+ ) -> np.ndarray:
224
+ """Predicts which observables were flipped based on detection event data.
225
+
226
+ Args:
227
+ dem: The detector error model the detector data applies to.
228
+ This is also where coordinate data is read from, in order to
229
+ determine which detectors to postselect as not having fired.
230
+ dets: The detection event data. Can be bit packed or not bit packed.
231
+ If dtype=np.bool_ then shape=(num_shots, num_detectors)
232
+ If dtype=np.uint8 then shape=(num_shots, math.ceil(num_detectors/8))
233
+ decoder: The decoder to use for decoding, e.g. "pymatching".
234
+ bit_pack_result: Defaults to False. Determines if the result is bit packed
235
+ or not.
236
+ custom_decoders: Custom decoders that can be used if requested by name.
237
+ If not specified, only decoders built into sinter, such as
238
+ 'pymatching' and 'fusion_blossom', can be used.
239
+
240
+ Returns:
241
+ If bit_packed_result=False (default):
242
+ dtype=np.bool_
243
+ shape=(num_shots, num_observables)
244
+ If bit_packed_result=True:
245
+ dtype=np.uint8
246
+ shape=(num_shots, math.ceil(num_observables / 8))
247
+
248
+ Examples:
249
+ >>> import numpy as np
250
+ >>> import sinter
251
+ >>> import stim
252
+ >>> dem = stim.DetectorErrorModel('''
253
+ ... error(0.1) D0 L0
254
+ ... error(0.1) D0 D1
255
+ ... error(0.1) D1
256
+ ... ''')
257
+ >>> sinter.predict_observables(
258
+ ... dem=dem,
259
+ ... dets=np.array([
260
+ ... [False, False],
261
+ ... [True, False],
262
+ ... [False, True],
263
+ ... [True, True],
264
+ ... ], dtype=np.bool_),
265
+ ... decoder='vacuous', # try replacing with 'pymatching'
266
+ ... bit_pack_result=False,
267
+ ... )
268
+ array([[False],
269
+ [False],
270
+ [False],
271
+ [False]])
272
+ """
273
+
274
+ if dets.dtype == np.bool_:
275
+ dets = np.packbits(dets, axis=1, bitorder='little')
276
+ result = predict_observables_bit_packed(
277
+ dem=dem,
278
+ dets_bit_packed=dets,
279
+ decoder=decoder,
280
+ custom_decoders=custom_decoders,
281
+ )
282
+ if not bit_pack_result:
283
+ return np.unpackbits(result, axis=1, bitorder='little', count=dem.num_observables).astype(np.bool_)
284
+ return result
285
+
286
+
287
+ def predict_observables_bit_packed(
288
+ *,
289
+ dem: stim.DetectorErrorModel,
290
+ dets_bit_packed: np.ndarray,
291
+ decoder: str,
292
+ custom_decoders: Optional[Dict[str, 'sinter.Decoder']] = None,
293
+ ) -> np.ndarray:
294
+ """Predicts which observables were flipped based on detection event data.
295
+
296
+ This method predates `sinter.predict_observables` gaining optional bit
297
+ packing arguments.
298
+
299
+ Args:
300
+ dem: The detector error model the detector data applies to.
301
+ This is also where coordinate data is read from, in order to
302
+ determine which detectors to postselect as not having fired.
303
+ dets_bit_packed: A uint8 numpy array with shape
304
+ (num_shots, math.ceil(num_dets / 8)). Contains bit packed detection
305
+ event data.
306
+ decoder: The decoder to use for decoding, e.g. "pymatching".
307
+ custom_decoders: Custom decoders that can be used if requested by name.
308
+ If not specified, only decoders built into sinter, such as
309
+ 'pymatching' and 'fusion_blossom', can be used.
310
+
311
+ Returns:
312
+ A numpy uint8 array with shape (num_shots, math.ceil(num_obs / 8)).
313
+ Contains bit packed observable prediction data.
314
+
315
+ Examples:
316
+ >>> import numpy as np
317
+ >>> import sinter
318
+ >>> import stim
319
+ >>> dem = stim.DetectorErrorModel('''
320
+ ... error(0.1) D0 L0
321
+ ... error(0.1) D0 D1
322
+ ... error(0.1) D1
323
+ ... ''')
324
+ >>> sinter.predict_observables_bit_packed(
325
+ ... dem=dem,
326
+ ... dets_bit_packed=np.array([
327
+ ... [0b00],
328
+ ... [0b01],
329
+ ... [0b10],
330
+ ... [0b11],
331
+ ... ], dtype=np.uint8),
332
+ ... decoder='vacuous', # try replacing with 'pymatching'
333
+ ... )
334
+ array([[0],
335
+ [0],
336
+ [0],
337
+ [0]], dtype=uint8)
338
+ """
339
+
340
+ decode_obj: Optional[Decoder] = None
341
+ if custom_decoders is not None:
342
+ decode_obj = custom_decoders.get(decoder)
343
+ if decode_obj is None:
344
+ decode_obj = BUILT_IN_DECODERS.get(decoder)
345
+ if decode_obj is None:
346
+ raise NotImplementedError(f"Unrecognized decoder: {decoder!r}")
347
+
348
+ with tempfile.TemporaryDirectory() as tmp_dir:
349
+ tmp_dir = pathlib.Path(tmp_dir)
350
+ dets_b8_path = tmp_dir / 'sinter_dets.b8'
351
+ pred_b8_path = tmp_dir / 'sinter_predictions.b8'
352
+ dem_path = tmp_dir / 'dem.dem'
353
+ dem.to_file(dem_path)
354
+ num_dets = dem.num_detectors
355
+ num_obs = dem.num_observables
356
+
357
+ stim.write_shot_data_file(
358
+ data=dets_bit_packed,
359
+ path=str(dets_b8_path),
360
+ format='b8',
361
+ num_detectors=num_dets,
362
+ num_observables=0,
363
+ )
364
+
365
+ decode_obj.decode_via_files(
366
+ num_shots=dets_bit_packed.shape[0],
367
+ num_dets=num_dets,
368
+ num_obs=num_obs,
369
+ dem_path=dem_path,
370
+ dets_b8_in_path=dets_b8_path,
371
+ obs_predictions_b8_out_path=pred_b8_path,
372
+ tmp_dir=tmp_dir,
373
+ )
374
+
375
+ return stim.read_shot_data_file(
376
+ path=str(pred_b8_path),
377
+ format='b8',
378
+ bit_pack=True,
379
+ num_detectors=0,
380
+ num_observables=num_obs,
381
+ )