Dist m4ri 0.16.1
Computing distance of a classical or quantum CSS code
Loading...
Searching...
No Matches
test_dist_m4ri.py
Go to the documentation of this file.
1"""
2Unit tests for dist_m4ri.py Python wrapper.
3"""
4
5import json
6import os
7import re
8import shutil
9import sys
10from pathlib import Path
11import pytest
12import numpy as np
13import scipy.sparse as sp
14
15# Add root directory to sys.path
16sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
17
18import dist_m4ri
19
20EXAMPLES_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "examples"))
21
22
25 assert os.path.isfile(bin_path)
26 assert os.access(bin_path, os.X_OK)
27
28
30 h_file = os.path.join(EXAMPLES_DIR, "c204H.mmx")
31 d = dist_m4ri.compute_classical_distance(h_file, d_exp=10, threads=4)
32 assert d == 8
33
34
36 # Hamming [7, 4, 3] code
37 H = np.array([
38 [1, 0, 0, 1, 1, 0, 1],
39 [0, 1, 0, 1, 0, 1, 1],
40 [0, 0, 1, 0, 1, 1, 1]
41 ], dtype=np.int8)
43 assert d == 3
44
45 # With codewords
46 d, cws = dist_m4ri.compute_classical_distance(H, do_cws=True, threads=2)
47 assert d == 3
48 assert len(cws) > 0
49 assert all(len(cw) == 3 for cw in cws)
50
51
52def test_matrix_writer(tmp_path):
53 # a numpy array (or an array-like) or a scipy sparse matrix is written to a temporary MatrixMarket coordinate
54 # pattern file of its entries over GF(2) (since version 0.15.2): an entry is 1 iff its value is odd (floats are
55 # truncated; the duplicate entries of a sparse matrix are summed first, without changing the caller's matrix); up
56 # to _MTX_PY_MAX_NNZ entries, the file is formatted in Python, larger matrices are written by scipy.io.mmwrite
57 def read_back(path): # (the header, and the matrix as the dist_m4ri binary reads a pattern file)
58 with open(path) as f:
59 banner = f.readline().strip()
60 lines = [ln for ln in f if not ln.startswith("%")]
61 m, n, nnz = map(int, lines[0].split())
62 M = np.zeros((m, n), dtype=np.uint8)
63 for ln in lines[1:]:
64 i, j = map(int, ln.split())
65 M[i - 1, j - 1] += 1
66 assert len(lines) - 1 == nnz and M.max(initial=0) <= 1 # (no duplicate entries)
67 return banner, M
68
69 pattern = "%%MatrixMarket matrix coordinate pattern general"
70 A = np.array([[1, 2, 3, 0], [-1, 0, 5, 4], [0, 0, 0, 0]])
71 expect = np.array([[1, 0, 1, 0], [1, 0, 1, 0], [0, 0, 0, 0]], dtype=np.uint8)
72 for M in (A, A.astype(np.uint8), A % 2 != 0, A.astype(float), A.tolist(), A.T.copy().T, sp.csr_matrix(A),
73 sp.csc_matrix(A), sp.coo_array(A)):
74 banner, B = read_back(dist_m4ri._matrix_to_file(M, temp_dir=str(tmp_path)))
75 assert banner == pattern and np.array_equal(B, expect), type(M)
76 banner, B = read_back(dist_m4ri._matrix_to_file(np.array([[1.5, 2.9, -1.2]]), temp_dir=str(tmp_path)))
77 assert B.tolist() == [[1, 0, 1]] # (floats truncated: 1, 2, -1)
78 N = sp.csr_matrix((np.array([1, 1, 1, 0]), np.array([1, 1, 2, 3]), np.array([0, 2, 3, 4])), shape=(3, 4))
79 assert not N.has_canonical_format # (a duplicate entry in row 0, an explicit zero in row 2)
80 banner, M = read_back(dist_m4ri._matrix_to_file(N, temp_dir=str(tmp_path)))
81 assert banner == pattern and M.tolist() == [[0, 0, 0, 0], [0, 0, 1, 0], [0, 0, 0, 0]]
82 assert N.nnz == 4 and not N.has_canonical_format
83 banner, M = read_back(dist_m4ri._matrix_to_file(np.zeros((2, 5), dtype=np.int8), temp_dir=str(tmp_path)))
84 assert banner == pattern and M.shape == (2, 5) and not M.any()
85 assert dist_m4ri._matrix_to_file(os.path.join(EXAMPLES_DIR, "QX40.mtx")) == os.path.join(EXAMPLES_DIR, "QX40.mtx")
86 with pytest.raises(ValueError, match="two dimensions"):
87 dist_m4ri._matrix_to_file(np.ones(4, dtype=np.int8), temp_dir=str(tmp_path))
88 big = np.random.default_rng(1).random((300, 400)) < 0.6 # more than _MTX_PY_MAX_NNZ entries: scipy.io.mmwrite
89 assert big.sum() > dist_m4ri._MTX_PY_MAX_NNZ
90 path = dist_m4ri._matrix_to_file(big, temp_dir=str(tmp_path))
91 banner, M = read_back(path)
92 assert banner == pattern and np.array_equal(M, big)
93 res = dist_m4ri.run_dist_m4ri(method=1, finH=path, classical=1, steps=20, threads=1, seed=1)
94 assert res[1] > 0 # (the binary reads the file: an upper bound from RW)
95
96
98 hx_file = os.path.join(EXAMPLES_DIR, "surf_d5_H.mmx")
99 hz_file = os.path.join(EXAMPLES_DIR, "surf_d5_L.mmx")
100 # For surf_d5: Hx as finH, Hz as finL gives d=5
101 dist, d_x, d_z = dist_m4ri.compute_css_distance(
102 Hx=hx_file, Hz=hx_file, Lz=hz_file, Lx=hz_file,
103 d_exp=5, threads=4
104 )
105 assert dist == 5
106 assert d_x == [5, 5, 0]
107 assert d_z == [5, 5, 0]
108
109
111 # Surface code d=3
112 hx = sp.csr_matrix([
113 [1, 1, 0, 1, 1, 0, 0, 0, 0],
114 [0, 1, 1, 0, 1, 1, 0, 0, 0],
115 [0, 0, 0, 1, 1, 0, 1, 1, 0],
116 [0, 0, 0, 0, 1, 1, 0, 1, 1]
117 ], dtype=np.int8)
118 hz = sp.csr_matrix([
119 [1, 0, 0, 1, 0, 0, 1, 0, 0],
120 [0, 1, 0, 0, 1, 0, 0, 1, 0],
121 [0, 0, 1, 0, 0, 1, 0, 0, 1]
122 ], dtype=np.int8)
123 dist, d_x, d_z = dist_m4ri.compute_css_distance(hx, hz, threads=2)
124 assert dist > 0
125 assert len(d_x) == 3
126 assert len(d_z) == 3
127
128
130 dem_file = os.path.join(EXAMPLES_DIR, "surf_d3.dem")
131 dist, d_info = dist_m4ri.compute_dem_distance(dem=dem_file, threads=4)
132 assert dist == 3
133 assert d_info == [3, 3, 0]
134
135 # With codewords (method=3 bracketing mode collects discovered cws)
136 dist, d_info, cws = dist_m4ri.compute_dem_distance(dem=dem_file, do_cws=True, threads=4)
137 assert dist == 3
138 assert d_info == [3, 3, 0]
139 assert len(cws) > 0
140 assert all(len(cw) == 3 for cw in cws)
141
142 # Exhaustive CC scan (method=2) finds all 128 codewords with drop=0 (a fresh cache: the codewords of this run)
144 dist, d_info, cws_cc = dist_m4ri.compute_dem_distance(dem=dem_file, method=2, wmax=3, do_cws=True, threads=4,
145 drop=0)
146 assert dist == 3
147 assert d_info == [3, 3, 0]
148 assert len(cws_cc) == 128
149 assert all(len(cw) == 3 for cw in cws_cc)
150
151 # With drop=auto (the default), the binary drops 8 of the 24 detectors: the 21 codewords of the reduced DEM (its
152 # 55 columns touch no dropped detector) are codewords of the DEM
154 dist, d_info, cws_red = dist_m4ri.compute_dem_distance(dem=dem_file, method=2, wmax=3, do_cws=True, threads=4)
155 assert (dist, d_info, len(cws_red)) == (3, [3, 3, 0], 21)
156 assert set(map(tuple, cws_red)) <= set(map(tuple, cws_cc))
157
158
160 if not dist_m4ri._HAS_STIM:
161 pytest.skip("stim is not installed")
162 import stim
163 circuit = stim.Circuit.generated(
164 "surface_code:rotated_memory_z",
165 rounds=3,
166 distance=3,
167 after_clifford_depolarization=0.001
168 )
169 dem = circuit.detector_error_model(decompose_errors=True)
170 dist, d_info = dist_m4ri.compute_dem_distance(dem=dem, threads=4)
171 assert dist == 3
172 assert d_info == [3, 3, 0]
173
174
178
179 H = np.array([
180 [1, 0, 0, 1, 1, 0, 1],
181 [0, 1, 0, 1, 0, 1, 1],
182 [0, 0, 1, 0, 1, 1, 1]
183 ], dtype=np.int8)
184
185 d1 = dist_m4ri.compute_classical_distance(H, threads=2)
186 assert len(dist_m4ri._distance_cache) == 1
187
188 # Second call should be a cache hit
189 d2 = dist_m4ri.compute_classical_distance(H, threads=2)
190 assert d1 == d2
191 assert len(dist_m4ri._distance_cache) == 1
192
194 assert len(dist_m4ri._distance_cache) == 0
195
196
198 if not dist_m4ri._HAS_CODEDISTANCE:
199 pytest.skip("codedistance is not installed")
200
201 H = np.array([
202 [1, 0, 0, 1, 1, 0, 1],
203 [0, 1, 0, 1, 0, 1, 1],
204 [0, 0, 1, 0, 1, 1, 1]
205 ], dtype=np.int8)
206
208 # Method 2 (CC only): rw_steps must be 0
209 h_file = os.path.join(EXAMPLES_DIR, "surf_d5_H.mmx")
210 l_file = os.path.join(EXAMPLES_DIR, "surf_d5_L.mmx")
211 dmin, dmax, rw_steps = dist_m4ri.run_dist_m4ri(method=2, finH=h_file, finL=l_file, wmax=5, threads=4)
212 assert (dmin, dmax, rw_steps) == (5, 5, 0)
213
214 # Method 2 (CC not found): dmin=wmax+1, dmax=0, rw_steps=0
215 dmin, dmax, rw_steps = dist_m4ri.run_dist_m4ri(method=2, finH=h_file, finL=l_file, wmax=3, threads=4)
216 assert (dmin, dmax, rw_steps) == (4, 0, 0)
217
218 # Method 1 (RW): rw_steps reported (min_hits=0: all steps; the reduced DEM, drop=auto, has only 55 columns)
219 dem_file = os.path.join(EXAMPLES_DIR, "surf_d3.dem")
220 dmin, dmax, rw_steps = dist_m4ri.run_dist_m4ri(method=1, fdem=dem_file, steps=100, threads=4, min_hits=0)
221 assert dmin == 1
222 assert dmax == 3
223 assert rw_steps >= 100
224
225
227 # Test dmin/dmax in run_dist_m4ri
228 h_file = os.path.join(EXAMPLES_DIR, "surf_d5_H.mmx")
229 l_file = os.path.join(EXAMPLES_DIR, "surf_d5_L.mmx")
230 dmin, dmax, rw_steps = dist_m4ri.run_dist_m4ri(
231 method=3, finH=h_file, finL=l_file, dmin=4, dmax=5, timeout=5, threads=4
232 )
233 assert (dmin, dmax) == (5, 5)
234
235 # Test dmin/dmax in compute_classical_distance
236 c_file = os.path.join(EXAMPLES_DIR, "c204H.mmx")
237 d = dist_m4ri.compute_classical_distance(c_file, dmin=5, dmax=8, threads=4)
238 assert d == 8
239
240 # Test dmin/dmax in compute_dem_distance
241 dem_file = os.path.join(EXAMPLES_DIR, "surf_d3.dem")
242 d_dem, _ = dist_m4ri.compute_dem_distance(dem=dem_file, dmin=2, dmax=4, threads=4)
243 assert d_dem == 3
244
245
249
250 c_file = os.path.join(EXAMPLES_DIR, "c1920H.mmx")
251
252 # Run 1: 50 steps
253 d1 = dist_m4ri.compute_classical_distance(c_file, method=1, num_steps=50, threads=4)
254 entry1 = dist_m4ri.get_cached_distance(H=c_file)
255 assert entry1 is not None
256 assert entry1["rw_steps"] == 50
257 assert entry1["dmax"] > 0
258 assert d1 == entry1["dmax"]
259
260 # Run 2: another 100 steps
261 d2 = dist_m4ri.compute_classical_distance(c_file, method=1, num_steps=100, threads=4)
262 entry2 = dist_m4ri.get_cached_distance(H=c_file)
263 assert entry2 is not None
264 assert entry2["rw_steps"] == 150
265 assert entry2["dmax"] <= entry1["dmax"]
266 assert d2 == entry2["dmax"]
267
269
270
272 # Exact known distance
273 assert dist_m4ri.format_bounds_list(5, 5, 0) == [5, 5, 0]
274 assert dist_m4ri.format_bounds_str([5, 5, 0]) == "5 5 0 (exact)"
275
276 # No upper bound (dmax == 0)
277 assert dist_m4ri.format_bounds_list(4, 0, 0) == [4, 0, 0]
278 assert dist_m4ri.format_bounds_str([4, 0, 0]) == "4 0 0"
279
280 # No lower bound (dmin <= 1)
281 assert dist_m4ri.format_bounds_list(0, 323, 100) == [0, 323, 100]
282 assert dist_m4ri.format_bounds_str([0, 323, 100]) == "0 323 100"
283
284 # Lower and upper bounds differing
285 assert dist_m4ri.format_bounds_list(4, 6, 1000) == [4, 6, 1000]
286 assert dist_m4ri.format_bounds_str([4, 6, 1000]) == "4 6 1000"
287
288
290 import json
291 json_file = str(tmp_path / "test_cache.json")
292
294 c_file = os.path.join(EXAMPLES_DIR, "c1920H.mmx")
295
296 # Run 1: 50 steps with cache_file
297 d1 = dist_m4ri.compute_classical_distance(c_file, method=1, num_steps=50, threads=4, cache_file=json_file)
298 assert os.path.isfile(json_file)
299
300 with open(json_file, "r") as f:
301 data1 = json.load(f)
302 assert data1.get("__version__") == dist_m4ri.__version__
303 cache_entries1 = {k: v for k, v in data1.items() if not k.startswith("__")}
304 assert len(cache_entries1) == 1
305 key = list(cache_entries1.keys())[0]
306 assert data1[key]["rw_steps"] == 50
307 assert data1[key]["dmax"] == d1
308
309 # Clear memory cache and re-read from JSON
311 entry = dist_m4ri.get_cached_distance(H=c_file, cache_file=json_file)
312 assert entry is not None
313 assert entry["rw_steps"] == 50
314
315 # Run 2: another 100 steps
316 d2 = dist_m4ri.compute_classical_distance(c_file, method=1, num_steps=100, threads=4, cache_file=json_file)
317 with open(json_file, "r") as f:
318 data2 = json.load(f)
319 assert data2[key]["rw_steps"] == 150
320 assert data2[key]["dmax"] <= d1
321
322 # CSS persistent caching
323 hx_file = os.path.join(EXAMPLES_DIR, "surf_d5_H.mmx")
324 hz_file = os.path.join(EXAMPLES_DIR, "surf_d5_L.mmx")
325 # First run with method=2 up to wmax=3 (lower bound dmin=4, dmax=0): must NOT be treated as exact
326 d_css_lb, dx_lb, dz_lb = dist_m4ri.compute_css_distance(
327 Hx=hx_file, Hz=hx_file, Lz=hz_file, Lx=hz_file,
328 method=2, wmax=3, threads=4, cache_file=json_file
329 )
330 assert d_css_lb == 4
331 assert dx_lb == [4, 0, 0]
332 assert dz_lb == [4, 0, 0]
333 entry_css_lb = dist_m4ri.get_cached_distance(Hx=hx_file, Hz=hx_file, Lx=hz_file, Lz=hz_file, cache_file=json_file)
334 assert entry_css_lb["dmin"] == 4
335 assert entry_css_lb["dmax"] == 0
336
337 # Second run with method=1 (RW): must execute RW (not falsely hit exact cache) and merge bounds [4, 5, steps]
338 d_css_rw, dx_rw, dz_rw = dist_m4ri.compute_css_distance(
339 Hx=hx_file, Hz=hx_file, Lz=hz_file, Lx=hz_file,
340 method=1, num_steps=50, min_hits=0, threads=4, cache_file=json_file
341 )
342 assert d_css_rw == 5
343 assert dx_rw[0] == 4 and dx_rw[1] == 5 and dx_rw[2] >= 50
344 assert dz_rw[0] == 4 and dz_rw[1] == 5 and dz_rw[2] >= 50
345
346 # Third run with method=3: certifies exact d=5 -> [5, 5, 0]
347 d_css, dx_info, dz_info = dist_m4ri.compute_css_distance(
348 Hx=hx_file, Hz=hx_file, Lz=hz_file, Lx=hz_file,
349 d_exp=5, threads=4, cache_file=json_file
350 )
351 assert d_css == 5
352 assert dx_info == [5, 5, 0]
353 assert dz_info == [5, 5, 0]
354 with open(json_file, "r") as f:
355 data_css = json.load(f)
356 assert len(data_css) >= 2
357
358 # DEM persistent caching
359 dem_file = os.path.join(EXAMPLES_DIR, "surf_d3.dem")
360 d_dem, d_info = dist_m4ri.compute_dem_distance(dem=dem_file, threads=4, cache_file=json_file)
361 assert d_dem == 3
362 with open(json_file, "r") as f:
363 data_dem = json.load(f)
364 assert len(data_dem) >= 3
365
366 dist_m4ri.clear_distance_cache(cache_file=json_file, clear_file=True)
367 assert not os.path.exists(json_file)
368
369
371 h_file = os.path.join(EXAMPLES_DIR, "surf_d5_H.mmx")
372 l_file = os.path.join(EXAMPLES_DIR, "surf_d5_L.mmx")
374 H=h_file, L=l_file, method=3, d_exp=5, threads=4, return_info=True
375 )
376 assert dist == 5
377 assert d_info == [5, 5, 0]
378
379
381 # Auto-infer classical = 0 when finG or finL is given
382 args1 = dist_m4ri.parse_cli_args(["finH=h.mtx", "finG=g.mtx", "smax=0", "finC=init.nz", "start=2"])
383 assert args1["classical"] == 0
384 assert args1["finH"] == "h.mtx"
385 assert args1["finG"] == "g.mtx"
386 assert args1["smax"] == 0
387 assert args1["finC"] == "init.nz"
388 assert args1["start"] == [2] # start is a list of CC start columns (since version 0.10.0)
389
390 # Auto-infer classical = 1 when only finH is given
391 args2 = dist_m4ri.parse_cli_args(["finH=h.mtx", "method=2"])
392 assert args2["classical"] == 1
393 assert args2["finH"] == "h.mtx"
394 assert args2["finG"] is None
395
396 # Auto-infer classical = 0 when fdem is given
397 args3 = dist_m4ri.parse_cli_args(["fdem=model.dem"])
398 assert args3["classical"] == 0
399
400
401def test_debug_bits(capsys):
402 # debug values: decimal or hexadecimal (no octal), OR-combined in the CLI
403 assert dist_m4ri.parse_debug_value("0x10004") == dist_m4ri.PY_DBG_COMMANDS | 4
404 assert dist_m4ri.parse_debug_value("011") == 11
405 for bad in ("-1", "0x", "abc", "1.5"):
406 with pytest.raises(ValueError):
408 args = dist_m4ri.parse_cli_args(["fdem=model.dem", "debug=1", "debug=0x10000"])
409 assert args["debug"] == 1 | dist_m4ri.PY_DBG_COMMANDS
410 # the Python bits are not passed to the binary: the command line is echoed with debug=0 (binary silent)
411 dem_file = os.path.join(EXAMPLES_DIR, "surf_d3.dem")
412 res = dist_m4ri.run_dist_m4ri(method=2, fdem=dem_file, wmax=3, threads=2, debug=dist_m4ri.PY_DBG_COMMANDS)
413 assert res == (3, 3, 0)
414 out = capsys.readouterr().out
415 assert " debug=0 method=2 " in out and "Finished in" in out
416 assert "# input:" not in out
417 # binary summary bit: the input summary (the DEM reduced by drop=auto, the default) and why the run ended
418 dist_m4ri.run_dist_m4ri(method=2, fdem=dem_file, wmax=3, threads=2, debug=1)
419 out = capsys.readouterr().out
420 assert "# input: detector error model, n=55" in out
421 assert "dropped 8 of 24 detectors (drop=auto), exact" in out
422 assert "CC found min-weight codeword: d=3" in out
423 dist_m4ri.run_dist_m4ri(method=2, fdem=dem_file, wmax=3, threads=2, debug=1, drop="0")
424 assert "# input: detector error model, n=221" in capsys.readouterr().out
425 # keep (and list) the temporary files
428 try:
429 H = np.array([[1, 1, 0], [0, 1, 1]], dtype=np.int8)
430 d = dist_m4ri.compute_classical_distance(H, method=2, wmax=3, threads=1, debug=dist_m4ri.PY_DBG_KEEP_FILES)
431 assert d == 3
432 out = capsys.readouterr().out
433 kept = [line.split("Kept temporary file: ", 1)[1] for line in out.splitlines() if "Kept temporary" in line]
434 assert kept and all(os.path.exists(f) for f in kept)
435 for f in kept:
436 os.remove(f)
437 finally:
439
440
444
445 h_file = os.path.join(EXAMPLES_DIR, "surf_d5_H.mmx")
446 l_file = os.path.join(EXAMPLES_DIR, "surf_d5_L.mmx")
447
448 # Classical distance of H
449 d_class = dist_m4ri.compute_classical_distance(h_file, dmax=5, threads=4)
450 # Quantum distance of (H, L)
451 d_quant = dist_m4ri.compute_quantum_distance(h_file, L=l_file, dmax=5, threads=4)
452
454 class_keys = [k for k in cache if k.startswith("classical:")]
455 quant_keys = [k for k in cache if k.startswith("quantum:")]
456
457 assert len(class_keys) >= 1
458 assert len(quant_keys) >= 1
459
461
462
464 # Exact distance
465 exp1 = dist_m4ri.explain_bounds([5, 5, 0], method=2, label="dX")
466 assert "Lower bound (dmin = 5): Exact distance certified" in exp1
467 assert "Upper bound (dmax = 5): Weight of the smallest non-trivial codeword discovered" in exp1
468 assert "Random window steps (rw_steps = 0): Set to 0 because the exact distance d = 5 was proven" in exp1
469
470 # Pure lower bound in method 2
471 exp2 = dist_m4ri.explain_bounds([4, 0, 0], method=2)
472 assert "All cluster weights w <= 3 were exhaustively analyzed" in exp2
473 assert "Method 2 (Connected Cluster) is an exhaustive search" in exp2
474
475 # RW search
476 exp3 = dist_m4ri.explain_bounds([1, 8, 120], method=1)
477 assert "No non-trivial lower bound certified" in exp3
478 assert "120 completed random information set searches" in exp3
479
480
482 # P1(w) = w C(n-w, k-1) / C(n, k) with k = n - rank: surf_d3.dem (n=221, rank(H)=24), w=3
483 from math import comb
484 p1 = dist_m4ri.rw_infoset_find_prob(221, 24, 3)
485 assert p1 == pytest.approx(3 * comb(218, 196) / comb(221, 197), rel=1e-9)
486 assert dist_m4ri.rw_infoset_find_prob(221, 24, 26) == 0.0 # w > rank + 1: never found
487 # c204 (n=204, rank(H)=101, d=8): the hardest lighter weight is w=7; miss probability (1-P1)^steps
488 est = dist_m4ri.rw_infoset_estimate(204, 101, 6000, 8, step_time=1e-5, steps_total=12000)
489 p7 = dist_m4ri.rw_infoset_find_prob(204, 101, 7)
490 assert est["w"] == 7
491 assert est["p_find"] == pytest.approx(p7)
492 assert est["p_miss"] == pytest.approx((1.0 - p7) ** 6000)
493 n1 = int(np.ceil(np.log(0.01) / np.log1p(-p7)))
494 assert est["steps_target"] == int(np.ceil(n1 * 2.0)) # half of the steps uniform
495 assert est["time_target"] == pytest.approx(est["steps_target"] * 1e-5)
496 # weights below a certified dmin are excluded; nothing to check if dmin >= d
497 assert dist_m4ri.rw_infoset_estimate(204, 101, 6000, 8, dmin=8) is None
498 # explanation from the stderr statistics of the binary
499 stderr = (
500 "# codewords accumulated: total=100, min_w=8: cws=1, total_hits=170, hits min=170, max=170, avg=170.00, "
501 "stdev=0.00, <n>=5.27 over 100 cws of w=8..15 (min_hits=5, cov_cws=100)\n"
502 "# RW information sets: n=204, rank(H)=101, steps=6000 (uniform permutations: 6000), ksub=0, "
503 "4.4e-05 s/step per thread, 8 RW threads\n"
504 )
505 stats = dist_m4ri._parse_stderr_stats(stderr)
506 assert stats["rw_n"] == 204 and stats["rw_rank"] == 101 and stats["rw_steps_uniform"] == 6000
507 assert stats["rw_pairs_l"] == 0 # (the summary line of a binary without the RW pairs)
508 exp = dist_m4ri.explain_bounds([0, 8, 6000], method=1, stats=stats)
509 assert "Hit-based estimate: P_fail ~ exp(-<n>) = 0.0051 (below 1%)" in exp
510 assert "a single codeword of weight 7 < 8 is found with probability" in exp
511 assert "Model check: expected hits per codeword of weight 8" in exp
512 # with the RW pairs (l pivot rows of the buckets): P = P1 + P2, P2 = C(w,2) C(n-w,k-2) / C(n,k) C(r-w+2,l) / C(r,l)
513 p2 = comb(7, 2) * comb(197, 101) / comb(204, 103) * comb(96, 4) / comb(101, 4)
514 assert dist_m4ri.rw_infoset_find_prob(204, 101, 7, pairs_l=4) == pytest.approx(p7 + p2, rel=1e-9)
515 assert dist_m4ri.rw_infoset_find_prob(204, 101, 7, pairs_l=0) == pytest.approx(p7, rel=1e-12)
516 est2 = dist_m4ri.rw_infoset_estimate(204, 101, 6000, 8, pairs_l=4)
517 assert est2["w"] == 7 and est2["p_find"] == pytest.approx(p7 + p2)
518 stderr2 = stderr.replace("ksub=0, ", "ksub=0, pairs=4, ") + (
519 "# RW pairs (p=2, buckets of l=4 random pivot rows): 1.23e+03 pairs checked per step; codewords recorded with "
520 "weight up to the upper bound: 517 from pairs, 170 from single columns\n")
521 stats2 = dist_m4ri._parse_stderr_stats(stderr2)
522 assert stats2["rw_pairs_l"] == 4 and stats2["rw_step_time"] == pytest.approx(4.4e-05)
523 assert stats2["rw_threads"] == 8
524 assert (stats2["rw_pairs_per_step"], stats2["rw_hits_pairs"], stats2["rw_hits_single"]) == (1230.0, 517, 170)
525 exp2 = dist_m4ri.explain_bounds([0, 8, 6000], method=1, stats=stats2)
526 assert "Information-set estimate (uniform random information sets with pairs, l = 4; n = 204" in exp2
527 assert f"is found with probability {p7 + p2:.3g} per RW step" in exp2
528 assert ("RW pairs (p=2, buckets of l = 4 pivot rows): 1.23e+03 pairs checked per RW step; codewords recorded "
529 "with weight up to the upper bound: 517 from pairs, 170 from single columns.") in exp2
530
531
533 hx_file = os.path.join(EXAMPLES_DIR, "surf_d5_H.mmx")
534 lz_file = os.path.join(EXAMPLES_DIR, "surf_d5_L.mmx")
535 ret = dist_m4ri.main([
536 f"Hx={hx_file}", f"Hz={hx_file}", f"Lx={lz_file}", f"Lz={lz_file}",
537 "method=2", "wmax=5", "--no-cache", "threads=4"
538 ])
539 assert ret == 0
540 captured = capsys.readouterr()
541 assert "dX: 5 5 0 (exact)" in captured.out
542 assert "dZ: 5 5 0 (exact)" in captured.out
543 assert "(d = 5) (exact)" in captured.out
544
545
547 h_file = os.path.join(EXAMPLES_DIR, "surf_d5_H.mmx")
548 l_file = os.path.join(EXAMPLES_DIR, "surf_d5_L.mmx")
549 ret = dist_m4ri.main([
550 "--verbose", "--no-cache", "method=3", f"finH={h_file}", f"finL={l_file}",
551 "wmax=5", "threads=4", "min_hits=3", "cov_cws=5"
552 ])
553 assert ret == 0
554 captured = capsys.readouterr()
555 assert "Cache retrieval:" in captured.out
556 assert "Lower bound" in captured.out
557 assert "Upper bound" in captured.out
558 assert "Random window steps" in captured.out
559 assert "Codewords accumulated:" in captured.out
560 assert "hits min =" in captured.out
561 assert "avg =" in captured.out
562 assert "stdev =" in captured.out
563
564
566 import tempfile
567
568 # Non-existent file when finC == outC
569 assert dist_m4ri.check_finc_outc("nonexistent.nz", "nonexistent.nz") is None
570
571 # Non-existent file when finC != outC
572 assert dist_m4ri.check_finc_outc("nonexistent.nz", "other.nz") == "nonexistent.nz"
573
574 # Empty 0-byte file when finC == outC
575 with tempfile.NamedTemporaryFile(suffix=".nz", delete=False) as f:
576 tmp_empty = f.name
577 try:
578 assert dist_m4ri.check_finc_outc(tmp_empty, tmp_empty) is None
579 finally:
580 if os.path.exists(tmp_empty):
581 os.remove(tmp_empty)
582
583 # Existing non-empty file when finC == outC
584 with tempfile.NamedTemporaryFile(suffix=".nz", delete=False, mode="w") as f:
585 f.write("1 2 3\n")
586 tmp_nonempty = f.name
587 try:
588 assert dist_m4ri.check_finc_outc(tmp_nonempty, tmp_nonempty) == tmp_nonempty
589 finally:
590 if os.path.exists(tmp_nonempty):
591 os.remove(tmp_nonempty)
592
593
595 import tempfile
596 h_file = os.path.join(EXAMPLES_DIR, "surf_d5_H.mmx")
597 l_file = os.path.join(EXAMPLES_DIR, "surf_d5_L.mmx")
598
599 tmp_out = os.path.join(tempfile.gettempdir(), f"tmp_test_cw_{os.getpid()}.nz")
600 if os.path.exists(tmp_out):
601 os.remove(tmp_out)
602
603 try:
604 ret = dist_m4ri.main([
605 "--verbose", "--no-cache", "method=2",
606 f"finH={h_file}", f"finL={l_file}",
607 f"finC={tmp_out}", f"outC={tmp_out}",
608 "wmax=5", "threads=4"
609 ])
610 assert ret == 0
611 captured = capsys.readouterr()
612 assert "Warning: finC=" in captured.out
613 assert "is empty or non-existent; silently ignoring input codewords." in captured.out
614 assert "5 5 0 (exact)" in captured.out
615 assert os.path.exists(tmp_out)
616 finally:
617 if os.path.exists(tmp_out):
618 os.remove(tmp_out)
619
620
621def test_cli_help(capsys):
622 ret = dist_m4ri.main(["--help"])
623 assert ret == 0
624 captured = capsys.readouterr()
625 assert "--morehelp" in captured.out
626 assert "threads=N" in captured.out
627 # Check that it fits within 80 rows
628 lines = [line for line in captured.out.splitlines() if line.strip()]
629 assert len(lines) < 80
630
631
633 ret = dist_m4ri.main(["--morehelp"])
634 assert ret == 0
635 captured = capsys.readouterr()
636 assert "Required input" in captured.out
637 assert "Calculation method:" in captured.out
638 assert "threads=N" in captured.out
639 assert "nothrottle=1" in captured.out
640
641
643 def entries(out): # (section headers, the first words of the entries)
644 lines = out.splitlines()
645 return ([s for s in lines if s and not s[0].isspace()],
646 [s.split()[0] for s in lines if s.startswith(" ") and not s.startswith(" ")])
647
648 # --morehelp TOPIC: the entries of TOPIC, under their section header, with the other entries of its group (see
649 # also) and the group (section 'Option groups')
650 groups = "Option groups (--help GROUP prints all entries of GROUP):"
651 assert dist_m4ri.main(["--morehelp", "maxC"]) == 0
652 assert entries(capsys.readouterr().out) == (
653 ["Codeword collection and export:", groups],
654 ["--cws", "outC=FILE", "finC=FILE", "maxC=N", "dW=N", "irred=0|1", "expand=0|1", "codewords"])
655 # several topics (case-insensitive, aliases): in the order of the full help, each section header once
656 assert dist_m4ri.main(["--morehelp=SEED,method,v"]) == 0
657 assert entries(capsys.readouterr().out) == (["Calculation method:", "General options:"],
658 ["method=1|2|3", "--verbose", "seed=N"])
659 # '-', '_', and '=VALUE' are ignored; aliases in the descriptions ('Alias: batch=N', 'also trust_start=1')
660 for topic, first in (("no-cache", "--no-cache"), ("--rounds=3", "--rounds"), ("batch", "chunk_size=N"),
661 ("trust_start", "--trust-start"), ("v", "--verbose"), ("out_dem=1", "--out-dem")):
662 assert dist_m4ri.main([f"--morehelp={topic}"]) == 0
663 assert first in entries(capsys.readouterr().out)[1], topic
664 # a group (also singular or plural): all its entries; a parameter of a group gives the whole group
665 for topic in ("windows", "window", "kwin", "WIN_HYB"):
666 assert dist_m4ri.main(["-h", topic]) == 0
667 assert entries(capsys.readouterr().out) == (["Multithreading & throttling:", groups],
668 ["kwin=N", "win_mode=0|1", "win_hyb=N", "windows"]), topic
669 assert dist_m4ri.main(["--help", "experimental"]) == 0
670 assert entries(capsys.readouterr().out)[1] == ["ksub=N", "refresh=N", "experimental"]
671 assert dist_m4ri.main(["--help", "windos"]) == 1
672 assert "no help entry for 'windos'; did you mean windows" in capsys.readouterr().err
673 # an unknown topic: the closest names on stderr, exit code 1 (the known topics are still printed)
674 assert dist_m4ri.main(["--morehelp", "mthod"]) == 1
675 captured = capsys.readouterr()
676 assert captured.out == ""
677 assert "no help entry for 'mthod'; did you mean method" in captured.err # (the closest name first, then ortho)
678 assert dist_m4ri.main(["--morehelp", "maxC,xyz"]) == 1
679 captured = capsys.readouterr()
680 assert "maxC=N" in entries(captured.out)[1]
681 assert "no help entry for 'xyz'" in captured.err
682 # an option after --morehelp is not a topic: the full help
683 assert dist_m4ri.main(["--morehelp", "--verbose"]) == 0
684 assert "Calculation method:" in capsys.readouterr().out
685 # --help TOPIC (also -h TOPIC, --help=TOPIC, help=TOPIC): the same as --morehelp TOPIC; topics are collected
686 assert dist_m4ri.main(["--morehelp", "maxC,dest"]) == 0
687 expected = capsys.readouterr().out
688 assert {"dexp=N", "maxC=N", "bounds", "codewords"} <= set(entries(expected)[1])
689 for argv in (["--help", "maxC,dest"], ["-h", "maxC,dest"], ["--help=maxC,dest"], ["help=maxC,dest"],
690 ["--help", "maxC", "--morehelp", "dest"]):
691 assert dist_m4ri.main(argv) == 0
692 assert capsys.readouterr().out == expected, argv
693
694
696 ret = dist_m4ri.main([])
697 assert ret == 255
698 captured = capsys.readouterr()
699 assert "no input matrix or model specified" in captured.err
700 assert "Allowed parameters:" in captured.err
701
702
704 ret = dist_m4ri.main(["--version"])
705 assert ret == 0
706 captured = capsys.readouterr()
707 assert "0.16.1" in captured.out
708 # also the version of the binary
709 assert re.search(r"^dist_m4ri version \S+ \‍(binary '.*dist_m4ri'\‍)$", captured.out, re.M)
710
711
713 ret = dist_m4ri.main(["--help"])
714 assert ret == 0
715 captured = capsys.readouterr()
716 # When binary is found and up to date, stderr should be silent (no warnings)
717 assert "Warning:" not in captured.err
718 assert "0.16.1" in captured.out
719
720
722 # Current binary should be compatible and return None
724
725 # Non-existent binary should return a warning
726 missing_warn = dist_m4ri.check_binary_compatibility(str(tmp_path / "nonexistent"))
727 assert missing_warn is not None
728 assert "not found" in missing_warn
729
730 # Older binary script (version 0.5.0) should return a version mismatch warning
731 fake_bin = tmp_path / "fake_dist_m4ri"
732 fake_bin.write_text("#!/bin/sh\necho \"dist_m4ri version 0.5.0\"\n")
733 fake_bin.chmod(0o755)
734 older_warn = dist_m4ri.check_binary_compatibility(str(fake_bin))
735 assert older_warn is not None
736 assert "version 0.5.0" in older_warn
737 assert "expected >= 0.16.1" in older_warn
738
739
741 import json
742 cache_file = tmp_path / "test_cache.json"
743
745 dist_m4ri._distance_cache["code_test"] = {"dist": 3, "dmin": 3, "dmax": 3}
746 dist_m4ri.save_distance_cache(str(cache_file))
747
748 # Verify version info is silently added to JSON file
749 with open(cache_file) as f:
750 data = json.load(f)
751 assert data.get("__version__") == dist_m4ri.__version__
752
753 # Verify loading does not pollute in-memory code keys
755 dist_m4ri.load_distance_cache(str(cache_file))
756 assert "__version__" not in dist_m4ri._distance_cache
757 assert "code_test" in dist_m4ri._distance_cache
758
759
763 try:
764 dem_file = os.path.join(EXAMPLES_DIR, "surf_d3.dem")
765 capsys.readouterr()
767 dem=dem_file, method=1, num_steps=2000, ksub=32, kwin=48,
768 win_mode=0, min_hits=3, cov_cws=2, refresh=50, threads=4, seed=42
769 )
770 assert d == 3
771 assert d_info[1] == 3
772 assert 0 < d_info[2] < 2000
773 # the experimental ksub: one warning per call (also when the binary falls back to full-matrix RW)
774 assert capsys.readouterr().err.count("# WARNING: ksub=32 is experimental and should not be used") == 1
775
777 "method=1", "ksub=64", "win=128", "win_mode=1",
778 "min_hits=5", "cov_cws=3", "refresh=100"
779 ])
780 assert args["ksub"] == 64
781 assert args["kwin"] == 128
782 assert args["win_mode"] == 1
783 assert args["min_hits"] == 5
784 assert args["cov_cws"] == 3
785 assert args["refresh"] == 100
786
787 default_args = dist_m4ri.parse_cli_args(["method=1"])
788 assert default_args["cov_cws"] == 100
789
790 # Test method=3 with min_hits (stops only RW workers while CC proves exact distance)
791 d3, d3_info = dist_m4ri.compute_dem_distance(
792 dem=dem_file, method=3, num_steps=50000, min_hits=2, cov_cws=2, threads=4, seed=42
793 )
794 assert d3 == 3
795 assert d3_info[0] == 3 and d3_info[1] == 3
796 assert "is experimental" not in capsys.readouterr().err
797 finally:
799
800
802 if not dist_m4ri._HAS_STIM:
803 pytest.skip("stim is not installed")
804 import stim
805 # Create a noiseless surface code circuit
806 circuit = stim.Circuit.generated(
807 "surface_code:rotated_memory_z",
808 rounds=2,
809 distance=3,
810 after_clifford_depolarization=0.0
811 )
812 assert not dist_m4ri.has_noise(circuit)
813 noisy = dist_m4ri.add_noise(circuit, p=0.001)
814 assert dist_m4ri.has_noise(noisy)
815
816 # compute_dem_distance should automatically add noise if given a noiseless circuit
818 dist, d_info = dist_m4ri.compute_dem_distance(circuit=circuit, threads=4)
819 assert dist == 3
820 assert d_info[0] == 3 and d_info[1] == 3
821
822
823def _make_xzzx_circuit(distance: int = 3, rounds: int = 3, memory_basis: str = "Z"):
824 """Constructs a rotated-surface-code XZZX circuit by conjugating checkerboard data qubits."""
825 import stim
826 task = (
827 "surface_code:rotated_memory_z"
828 if memory_basis.upper() == "Z"
829 else "surface_code:rotated_memory_x"
830 )
831 base_circ = stim.Circuit.generated(
832 task,
833 rounds=rounds,
834 distance=distance,
835 after_clifford_depolarization=0.001,
836 before_measure_flip_probability=0.001,
837 after_reset_flip_probability=0.001,
838 )
839 t_res = dist_m4ri.classify_qubits_thorough(base_circ, basis_arg=memory_basis)
840 data_set = set(t_res["data_qubits"])
841
842 coords = {}
843 for inst in base_circ.flattened():
844 if inst.name == "QUBIT_COORDS":
845 args = inst.gate_args_copy()
846 for t in inst.targets_copy():
847 if t.is_qubit_target:
848 coords[t.value] = args
849
850 rot_data = set()
851 for q in data_set:
852 x, y = coords[q][0], coords[q][1]
853 if int(round((x + y) / 2)) % 2 == 1:
854 rot_data.add(q)
855
856 def transform_block(blk):
857 out = stim.Circuit()
858 for inst in blk:
859 if isinstance(inst, stim.CircuitRepeatBlock):
860 out.append(
861 stim.CircuitRepeatBlock(
862 inst.repeat_count, transform_block(inst.body_copy())
863 )
864 )
865 elif inst.name in ["R", "RX"]:
866 out.append(inst)
867 rot_t = [
868 t.value for t in inst.targets_copy()
869 if t.is_qubit_target and t.value in rot_data
870 ]
871 if rot_t:
872 out.append("H", rot_t)
873 elif inst.name in ["M", "MX"]:
874 rot_t = [
875 t.value for t in inst.targets_copy()
876 if t.is_qubit_target and t.value in rot_data
877 ]
878 if rot_t:
879 out.append("H", rot_t)
880 out.append(inst)
881 elif inst.name == "CX":
882 t_vals = [t.value for t in inst.targets_copy()]
883 for i in range(0, len(t_vals), 2):
884 c, t = t_vals[i], t_vals[i + 1]
885 if t in rot_data:
886 out.append("CZ", [c, t])
887 elif c in rot_data:
888 out.append("H", [c])
889 out.append("CX", [c, t])
890 out.append("H", [c])
891 else:
892 out.append("CX", [c, t])
893 else:
894 out.append(inst)
895 return out
896
897 return transform_block(base_circ), rot_data
898
899
901 if not dist_m4ri._HAS_STIM:
902 pytest.skip("stim is not installed")
903 import stim
904
905 circuit = stim.Circuit.generated(
906 "surface_code:rotated_memory_z",
907 rounds=3,
908 distance=3,
909 after_clifford_depolarization=0.001,
910 before_measure_flip_probability=0.001,
911 after_reset_flip_probability=0.001,
912 )
913 stim_file = tmp_path / "surf_d3_r3_Z.stim"
914 circuit.to_file(str(stim_file))
915
917 assert t_res["is_css"] is True
918 assert t_res["is_rotated_css"] is False
919 assert t_res["basis"] == "Z"
920 assert len(t_res["data_qubits"]) == 9
921 assert len(t_res["x_ancillas"]) == 4
922 assert len(t_res["z_ancillas"]) == 4
923
924 simp_circ, stripped, kept = dist_m4ri.strip_minority_detectors(
925 circuit, "Z", thorough_res=t_res
926 )
927 assert stripped == 8
928 assert kept == 16
929 assert simp_circ.num_detectors == 16
930
933 try:
934 # The DEM has all 24 detectors; the binary drops the 8 minority-basis detectors (drop=auto, the default)
935 ret = dist_m4ri.main([str(stim_file), "-v", "--no-cache", "threads=2"])
936 assert ret == 0
937 captured = capsys.readouterr()
938 assert "Circuit detectors: 24\n" in captured.out # (the minority-basis detectors only with drop=css)
939 assert "dropped 8 of 24 detectors (drop=auto)" in captured.out # (the binary's summary, printed with -v)
940 assert "3 3 0 (exact)" in captured.out
941 # drop=css: the minority-basis detectors of the classification, passed to the binary in a file
942 ret = dist_m4ri.main([str(stim_file), "drop=css", "-v", "--no-cache", "threads=2"])
943 assert ret == 0
944 captured = capsys.readouterr()
945 assert "Circuit detectors: 24, minority-basis=8" in captured.out
946 assert re.search(r"dropped 8 of 24 detectors \‍(drop=\S+_drop\.txt\‍)", captured.out)
947 assert "3 3 0 (exact)" in captured.out
948
949 # --simple and --full are disabled (since 0.13.0): ignored with a warning, the same DEM
950 for opt in ("--simple", "--full", "full=1", "simple=0"):
951 ret = dist_m4ri.main([str(stim_file), opt, "-v", "--no-cache", "threads=2"])
952 assert ret == 0
953 captured = capsys.readouterr()
954 name = "--" + opt.split("=")[0].lstrip("-")
955 assert f"# Warning: {name}: disabled since version 0.13.0, ignored" in captured.err
956 assert "dropped 8 of 24 detectors (drop=auto)" in captured.out
957 assert "3 3 0 (exact)" in captured.out
958 finally:
960
961
962def test_stim_xzzx_rotated_css(tmp_path, capsys):
963 if not dist_m4ri._HAS_STIM:
964 pytest.skip("stim is not installed")
965
966 for mem_basis in ("Z", "X"):
967 xzzx_circ, rot_data = _make_xzzx_circuit(
968 distance=3, rounds=3, memory_basis=mem_basis
969 )
971 xzzx_circ, basis_arg=mem_basis
972 )
973 assert t_res["is_css"] is True
974 assert t_res["is_rotated_css"] is True
975 assert t_res["basis"] == mem_basis
976 assert len(t_res["data_qubits"]) == 9
977 assert len(t_res["x_ancillas"]) == 4
978 assert len(t_res["z_ancillas"]) == 4
979 assert set(t_res["rotated_data_qubits"]) == rot_data
980
981 simp_circ, stripped, kept = dist_m4ri.strip_minority_detectors(
982 xzzx_circ, mem_basis, thorough_res=t_res
983 )
984 assert stripped == 8
985 assert kept == 16
986 assert simp_circ.num_detectors == 16
987
988 stim_path = tmp_path / f"xzzx_d3_r3_{mem_basis}.stim"
989 xzzx_circ.to_file(str(stim_path))
990
992 d_full, info_full = dist_m4ri.compute_dem_distance(dem=str(stim_path), threads=2, debug=1)
993 assert d_full == 3
994 assert info_full[0] == 3 and info_full[1] == 3
995 captured = capsys.readouterr()
996 assert "dropped 8 of 24 detectors (drop=auto)" in captured.out # (the minority-basis detectors)
997
998 # simple= and full= are disabled (since 0.13.0): ignored with a warning to stderr
999 for kw in ({"simple": True}, {"full": True}):
1001 d, info = dist_m4ri.compute_dem_distance(dem=str(stim_path), threads=2, **kw)
1002 assert (d, info[0], info[1]) == (3, 3, 3)
1003 assert f"# Warning: {next(iter(kw))}=: disabled since version 0.13.0" in capsys.readouterr().err
1004
1005
1006def test_stim_rounds_option(tmp_path, capsys):
1007 if not dist_m4ri._HAS_STIM:
1008 pytest.skip("stim is not installed")
1009 import stim
1010
1011 circuit = stim.Circuit.generated(
1012 "surface_code:rotated_memory_z",
1013 rounds=5,
1014 distance=3,
1015 after_clifford_depolarization=0.001,
1016 before_measure_flip_probability=0.001,
1017 after_reset_flip_probability=0.001,
1018 )
1019 stim_with_repeat = tmp_path / "surf_r5_Z.stim"
1020 circuit.to_file(str(stim_with_repeat))
1021
1022 # CLI argument parsing checks for --rounds, --simple, --full (the last two are disabled, parsed for the warning)
1023 args1 = dist_m4ri.parse_cli_args(["--rounds", str(stim_with_repeat)])
1024 assert args1["rounds"] == 2
1025 assert args1["fdem"] == str(stim_with_repeat)
1026
1027 args2 = dist_m4ri.parse_cli_args(["--rounds", "4", "--simple", str(stim_with_repeat)])
1028 assert args2["rounds"] == 4
1029 assert args2["simple"] is True
1030 assert args2["full"] is None
1031 assert args2["fdem"] == str(stim_with_repeat)
1032
1033 args3 = dist_m4ri.parse_cli_args(["rounds=3", "--full", "basis=X", str(stim_with_repeat)])
1034 assert args3["rounds"] == 3
1035 assert args3["full"] is True
1036 assert args3["simple"] is None
1037 assert args3["basis"] == "X"
1038
1041 try:
1042 # Bare --rounds sets rounds=2 (REPEAT 2 block -> 24 detectors, of which 8 X-detectors are dropped)
1043 ret = dist_m4ri.main(
1044 [str(stim_with_repeat), "--rounds", "-v", "--no-cache", "threads=2"]
1045 )
1046 assert ret == 0
1047 out = capsys.readouterr().out
1048 assert "rounds=2" in out
1049 assert "Circuit detectors: 24\n" in out
1050 assert "dropped 8 of 24 detectors (drop=auto)" in out
1051
1052 # Flattened circuit has no REPEAT block -> issues a warning and continues
1053 flat_stim = tmp_path / "surf_flat_Z.stim"
1054 circuit.flattened().to_file(str(flat_stim))
1055 d_flat, info_flat = dist_m4ri.compute_dem_distance(
1056 dem=str(flat_stim), rounds=2, threads=2
1057 )
1058 assert d_flat == 3
1059 assert info_flat[0] == 3 and info_flat[1] == 3
1060 err_api = capsys.readouterr().err
1061 assert "Warning:" in err_api and "REPEAT block" in err_api
1062
1063 ret_flat = dist_m4ri.main(
1064 [str(flat_stim), "--rounds", "--no-cache", "threads=2"]
1065 )
1066 assert ret_flat == 0
1067 captured_flat = capsys.readouterr()
1068 assert "Warning:" in captured_flat.err and "REPEAT block" in captured_flat.err
1069
1070 # Non-stim input with --rounds must signal an error
1071 dem_file = os.path.join(EXAMPLES_DIR, "surf_d3.dem")
1072 ret_dem_err = dist_m4ri.main([dem_file, "--rounds", "--no-cache"])
1073 assert ret_dem_err != 0
1074 assert "--rounds" in capsys.readouterr().err
1075 finally:
1077
1078
1080 if not dist_m4ri._HAS_STIM:
1081 pytest.skip("stim is not installed")
1082 import stim
1083
1084 in_dir = tmp_path / "inputs"
1085 in_dir.mkdir(parents=True, exist_ok=True)
1086 stim_file = in_dir / "surf_d3_Z.stim"
1087
1088 # Create a noiseless circuit to also verify that --out-stim saves the noisy/processed circuit
1089 circuit = stim.Circuit.generated(
1090 "surface_code:rotated_memory_z",
1091 rounds=4,
1092 distance=3,
1093 )
1094 circuit.to_file(str(stim_file))
1095
1096 # 1. Test CLI parsing of bare --out-dem and --out-stim vs explicit filenames
1097 p1 = dist_m4ri.parse_cli_args(["--out-dem", "--out-stim", str(stim_file)])
1098 assert p1["out_dem"] is True
1099 assert p1["out_stim"] is True
1100 assert p1["fdem"] == str(stim_file)
1101
1103 ["--out-dir", "/tmp/out", "--out-dem", "a.dem", "--out-stim", "b.stim", str(stim_file)]
1104 )
1105 assert p2["out_dir"] == "/tmp/out"
1106 assert p2["out_dem"] == "a.dem"
1107 assert p2["out_stim"] == "b.stim"
1108 assert p2["fdem"] == str(stim_file)
1109
1112 try:
1113 # 2. Bare --out-dem and --out-stim (all detectors, default out-dir = input file dir)
1114 ret = dist_m4ri.main(
1115 [str(stim_file), "--rounds", "2", "--out-dem", "--out-stim", "--no-cache", "threads=2"]
1116 )
1117 assert ret == 0
1118 exp_dem = in_dir / "surf_d3_Z_full.dem"
1119 exp_stim = in_dir / "surf_d3_Z_full.stim"
1120 assert exp_dem.exists()
1121 assert exp_stim.exists()
1122
1123 saved_circ = stim.Circuit.from_file(str(exp_stim))
1124 assert dist_m4ri.has_noise(saved_circ)
1125 assert saved_circ.num_detectors == 24
1126 saved_dem = stim.DetectorErrorModel.from_file(str(exp_dem))
1127 assert saved_dem.num_detectors == 24
1128
1129 # 3. Bare --out-dem and --out-stim with explicit --out-dir
1130 custom_dir = tmp_path / "custom_out"
1131 ret_full = dist_m4ri.main(
1132 [
1133 str(stim_file),
1134 "--rounds",
1135 "2",
1136 "--out-dir",
1137 str(custom_dir),
1138 "--out-dem",
1139 "--out-stim",
1140 "--no-cache",
1141 "threads=2",
1142 ]
1143 )
1144 assert ret_full == 0
1145 exp_full_dem = custom_dir / "surf_d3_Z_full.dem"
1146 exp_full_stim = custom_dir / "surf_d3_Z_full.stim"
1147 assert exp_full_dem.exists()
1148 assert exp_full_stim.exists()
1149 saved_full_dem = stim.DetectorErrorModel.from_file(str(exp_full_dem))
1150 assert saved_full_dem.num_detectors == 24
1151
1152 # 4. Explicit bare filenames without --out-dir default to input file's directory
1153 ret_named_in_dir = dist_m4ri.main(
1154 [
1155 str(stim_file),
1156 "--rounds",
1157 "2",
1158 "--out-dem",
1159 "named_in_dir.dem",
1160 "--out-stim",
1161 "named_in_dir.stim",
1162 "--no-cache",
1163 "threads=2",
1164 ]
1165 )
1166 assert ret_named_in_dir == 0
1167 assert (in_dir / "named_in_dir.dem").exists()
1168 assert (in_dir / "named_in_dir.stim").exists()
1169
1170 # 5. Explicit bare filenames with --out-dir save into --out-dir
1171 ret_named = dist_m4ri.main(
1172 [
1173 str(stim_file),
1174 "--rounds",
1175 "2",
1176 "--out-dir",
1177 str(custom_dir),
1178 "--out-dem",
1179 "explicit.dem",
1180 "--out-stim",
1181 "explicit.stim",
1182 "--no-cache",
1183 "threads=2",
1184 ]
1185 )
1186 assert ret_named == 0
1187 assert (custom_dir / "explicit.dem").exists()
1188 assert (custom_dir / "explicit.stim").exists()
1189 finally:
1191
1192
1193S5_H = os.path.join(EXAMPLES_DIR, "surf_d5_H.mmx")
1194S5_L = os.path.join(EXAMPLES_DIR, "surf_d5_L.mmx")
1195
1196
1198 norm = dist_m4ri._normalize_start_list
1199 assert norm(None) is None
1200 assert norm(5) == [5]
1201 assert norm(-1) is None
1202 assert norm("-1") is None
1203 assert norm("") is None
1204 assert norm("48,0,48") == [0, 48]
1205 assert norm([7, 3, 3]) == [3, 7]
1206 assert norm((2,)) == [2]
1207 assert norm(np.array([4, 1])) == [1, 4]
1208 for bad in ("1,,2", "a", "1.5", "0,-1", [1, -2], 1.5, True, [True]):
1209 with pytest.raises(ValueError):
1210 norm(bad)
1211 assert dist_m4ri._start_key_suffix([0, 48]) == ":start=0,48"
1212 assert dist_m4ri._start_key_suffix(None) == ""
1213
1214
1216 # start list is passed sorted and deduplicated; noscan/cbeg/cend are ignored with a warning
1218 method=2, finH=S5_H, finL=S5_L, wmax=7, start=[41, 0, 41], noscan=1, cbeg=3, cend=7,
1219 threads=2, verbose=True
1220 )
1221 assert res == (5, 5, 0)
1222 captured = capsys.readouterr()
1223 run_line = [ln for ln in captured.out.splitlines() if "[dist_m4ri] Running:" in ln][0]
1224 assert "start=0,41" in run_line
1225 assert "noscan=" not in run_line and "cbeg=" not in run_line and "cend=" not in run_line
1226 for opt in ("noscan=1", "cbeg=3", "cend=7"):
1227 assert f"{opt} is disabled in the Python interface" in captured.err
1228 assert "WARNING: start=0,41 (expert option)" in captured.err
1229
1230
1231def test_start_scan_option(tmp_path, capsys):
1232 # start_scan=True (expert, with start; since version 0.15.1) is passed as start_scan=1: a cluster grown from a
1233 # listed column c only uses the columns > c, as in the scan. With all columns listed, it finds the same codewords
1234 # as the scan (QX150 at the supplied dmin=6); the compute_*_distance() functions and the CLI pass it on, with the
1235 # cache key suffix ':start=...:scan'; without a start list, or for a DEM, it is ignored with a warning
1236 H, G = os.path.join(EXAMPLES_DIR, "QX150.mtx"), os.path.join(EXAMPLES_DIR, "QZ150.mtx")
1237 cws = {}
1238 for scan in (False, True):
1239 out = str(tmp_path / f"cws_{scan}.nz")
1240 res = dist_m4ri.run_dist_m4ri(method=2, finH=H, finG=G, dmin=6, outC=out, threads=2, start_scan=scan,
1241 start=list(range(150)) if scan else None, verbose=True)
1242 assert tuple(res) == (6, 6, 0)
1243 captured = capsys.readouterr()
1244 run_line = [ln for ln in captured.out.splitlines() if "[dist_m4ri] Running:" in ln][0]
1245 assert ("start_scan=1" in run_line) == scan
1246 assert ("WARNING: start=0,1,2,3,4,5,6,7,... start_scan=1 (expert options)" in captured.err) == scan
1247 cws[scan] = sorted(tuple(cw) for cw in dist_m4ri.read_sparse_vectors(out))
1248 assert len(cws[False]) == 500 and cws[False] == cws[True]
1249 dist_m4ri.run_dist_m4ri(method=2, finH=H, finG=G, dmin=6, wmax=6, threads=2, start_scan=True)
1250 assert "start_scan=1 has no effect without a start list" in capsys.readouterr().err
1251 dem_file = os.path.join(EXAMPLES_DIR, "surf_d3.dem")
1252 with pytest.raises(ValueError, match="start_scan=1 is not available with fdem"):
1253 dist_m4ri.run_dist_m4ri(method=2, fdem=dem_file, wmax=3, start=[0, 7], start_scan=True)
1255 try:
1256 dist_m4ri.compute_quantum_distance(H, G=G, method=2, wmax=6, threads=2, start=[0, 75], start_scan=True,
1257 debug=dist_m4ri.PY_DBG_COMMANDS)
1258 captured = capsys.readouterr()
1259 assert "start=0,75 start_scan=1" in captured.out and "start_scan=1 (expert options)" in captured.err
1260 assert dist_m4ri.get_cached_distance(H=H, G=G, start=[0, 75], start_scan=True) is not None
1261 assert dist_m4ri.get_cached_distance(H=H, G=G, start=[0, 75]) is None
1262 dist_m4ri.compute_dem_distance(dem=dem_file, method=2, wmax=3, threads=2, start=[0, 7], start_scan=True,
1263 debug=dist_m4ri.PY_DBG_COMMANDS)
1264 captured = capsys.readouterr()
1265 assert "start_scan=1 is not available for a DEM (ignored)" in captured.err
1266 assert "start_scan=" not in captured.out
1267 assert dist_m4ri.parse_cli_args(["start_scan=1"])["start_scan"] is True
1268 assert dist_m4ri.parse_cli_args(["--start-scan"])["start_scan"] is True
1269 assert dist_m4ri.parse_cli_args([])["start_scan"] is False
1270 assert dist_m4ri.main([f"finH={H}", f"finG={G}", "method=2", "wmax=6", "threads=2", "start=0,75",
1271 "start_scan=1", "--no-cache", f"debug={dist_m4ri.PY_DBG_COMMANDS}"]) == 0
1272 assert "start=0,75 start_scan=1" in capsys.readouterr().out
1273 assert dist_m4ri.main([f"finH={H}", "start=0,75", "start_scan=2", "--no-cache"]) == 1
1274 assert "invalid 'start_scan=2': start_scan should be 0 or 1" in capsys.readouterr().err
1275 finally:
1278
1279
1280def test_sort_rows_option(tmp_path, capsys):
1281 # sort_rows=None leaves the binary's default (rows of H sorted by weight for matrices, not for a DEM); True / False
1282 # pass sort_rows=1 / sort_rows=0; the distance and the codewords enumerated (outC) do not depend on the row order
1283 cws = {}
1284 for val, opt in ((None, None), (True, "sort_rows=1"), (False, "sort_rows=0")):
1285 out = str(tmp_path / f"cws_{opt}.nz")
1286 res = dist_m4ri.run_dist_m4ri(method=2, finH=S5_H, finL=S5_L, wmax=5, threads=2, outC=out, sort_rows=val,
1287 verbose=True)
1288 assert tuple(res) == (5, 5, 0)
1289 captured = capsys.readouterr()
1290 run_line = [ln for ln in captured.out.splitlines() if "[dist_m4ri] Running:" in ln][0]
1291 if opt is None:
1292 assert "sort_rows=" not in run_line
1293 else:
1294 assert opt in run_line
1295 sorted_note = "rows of H sorted by increasing weight for CC" in captured.out + captured.err
1296 assert sorted_note == (val is not False)
1297 cws[opt] = sorted(tuple(cw) for cw in dist_m4ri.read_sparse_vectors(out))
1298 assert len(cws[None]) > 1 and cws[None] == cws["sort_rows=1"] == cws["sort_rows=0"]
1299 # the compute_*_distance() functions and the CLI pass it on (the cache key does not depend on it)
1301 try:
1302 for val in (True, False):
1303 assert dist_m4ri.compute_quantum_distance(S5_H, L=S5_L, method=2, wmax=5, threads=2, sort_rows=val,
1304 debug=dist_m4ri.PY_DBG_COMMANDS, cache_file=None) == 5
1305 assert f"sort_rows={int(val)}" in capsys.readouterr().out
1307 assert dist_m4ri.parse_cli_args(["sort_rows=0"])["sort_rows"] is False
1308 assert dist_m4ri.parse_cli_args(["sort_rows=1"])["sort_rows"] is True
1309 assert dist_m4ri.parse_cli_args([])["sort_rows"] is None
1310 assert dist_m4ri.main([f"finH={S5_H}", f"finL={S5_L}", "method=2", "wmax=5", "threads=2", "sort_rows=0",
1311 "--no-cache", f"debug={dist_m4ri.PY_DBG_COMMANDS}"]) == 0
1312 assert "sort_rows=0" in capsys.readouterr().out
1313 assert dist_m4ri.main([f"finH={S5_H}", "sort_rows=2", "--no-cache"]) == 1
1314 assert "invalid 'sort_rows=2': sort_rows should be 0 or 1" in capsys.readouterr().err
1315 finally:
1318
1319
1320def test_cc_prune_option(tmp_path, capsys):
1321 # cc_prune=None leaves the binary's default (1, the branch and bound); 0 / 1 / 2 are passed as cc_prune=N; the
1322 # distance and the codewords enumerated (outC) do not depend on it, and with cc_prune=1 or 2 each codeword is found
1323 # exactly once (surf_d5 as matrices: with cc_prune=0, codewords with equal columns are found several times)
1324 cws = {}
1325 for val in (None, 0, 1, 2):
1326 out = str(tmp_path / f"cws_{val}.nz")
1327 res = dist_m4ri.run_dist_m4ri(method=2, finH=S5_H, finL=S5_L, wmax=5, threads=2, outC=out, cc_prune=val,
1328 verbose=True)
1329 assert tuple(res) == (5, 5, 0)
1330 captured = capsys.readouterr()
1331 run_line = [ln for ln in captured.out.splitlines() if "[dist_m4ri] Running:" in ln][0]
1332 if val is None:
1333 assert "cc_prune=" not in run_line
1334 else:
1335 assert f"cc_prune={val}" in run_line
1336 hits = re.search(r"# codewords accumulated: total=(\d+), min_w=5: cws=\d+, total_hits=(\d+),",
1337 captured.out + captured.err)
1338 assert hits, captured.err
1339 assert (hits.group(1) == hits.group(2)) == (val != 0)
1340 cws[val] = sorted(tuple(cw) for cw in dist_m4ri.read_sparse_vectors(out))
1341 assert len(cws[None]) > 1 and cws[None] == cws[0] == cws[1] == cws[2]
1342 with pytest.raises(ValueError, match="cc_prune should be 0, 1, or 2"):
1343 dist_m4ri.run_dist_m4ri(method=2, finH=S5_H, finL=S5_L, wmax=5, cc_prune=3)
1344 # the compute_*_distance() functions and the CLI pass it on (the cache key does not depend on it)
1346 try:
1347 for val in (0, 2):
1348 assert dist_m4ri.compute_quantum_distance(S5_H, L=S5_L, method=2, wmax=5, threads=2, cc_prune=val,
1349 debug=dist_m4ri.PY_DBG_COMMANDS, cache_file=None) == 5
1350 assert f"cc_prune={val}" in capsys.readouterr().out
1352 assert dist_m4ri.parse_cli_args(["cc_prune=0"])["cc_prune"] == 0
1353 assert dist_m4ri.parse_cli_args(["--cc-prune", "2"])["cc_prune"] == 2
1354 assert dist_m4ri.parse_cli_args([])["cc_prune"] is None
1355 assert dist_m4ri.main([f"finH={S5_H}", f"finL={S5_L}", "method=2", "wmax=5", "threads=2", "--cc-prune", "0",
1356 "--no-cache", f"debug={dist_m4ri.PY_DBG_COMMANDS}"]) == 0
1357 assert "cc_prune=0" in capsys.readouterr().out
1358 assert dist_m4ri.main([f"finH={S5_H}", "cc_prune=3", "--no-cache"]) == 1
1359 assert "invalid 'cc_prune=3': cc_prune should be 0, 1, or 2" in capsys.readouterr().err
1360 finally:
1363
1364
1366 # ortho=True (the default): the binary checks that H G^T = 0 (an error for QX40 as both H and G); ortho=False
1367 # (expert, since version 0.15.2) passes ortho=0, which skips the check, with the warning of the binary also from the
1368 # wrapper (the same result for the orthogonal pair QX150, QZ150); no effect without G (a warning); the
1369 # compute_*_distance() functions and the CLI pass it on (the cache key does not depend on it)
1370 X40 = os.path.join(EXAMPLES_DIR, "QX40.mtx")
1371 H, G = os.path.join(EXAMPLES_DIR, "QX150.mtx"), os.path.join(EXAMPLES_DIR, "QZ150.mtx")
1372 ortho_warning = "WARNING: ortho=0 (expert option): the rows of H and G are not checked for orthogonality"
1373 with pytest.raises(RuntimeError, match="rows of H and G matrices are not orthogonal"):
1374 dist_m4ri.run_dist_m4ri(method=1, finH=X40, finG=X40, steps=100, threads=1)
1375 res = dist_m4ri.run_dist_m4ri(method=1, finH=X40, finG=X40, steps=100, threads=1, seed=1, ortho=False,
1376 debug=dist_m4ri.PY_DBG_COMMANDS)
1377 assert res[1] > 0
1378 captured = capsys.readouterr()
1379 assert "ortho=0" in captured.out and captured.err.count(ortho_warning) == 1
1380 assert tuple(dist_m4ri.run_dist_m4ri(method=2, finH=H, finG=G, dmin=6, wmax=6, threads=2, ortho=False)) == (6, 6, 0)
1381 assert tuple(dist_m4ri.run_dist_m4ri(method=2, finH=S5_H, finL=S5_L, wmax=5, threads=2, ortho=False,
1382 debug=dist_m4ri.PY_DBG_COMMANDS)) == (5, 5, 0)
1383 captured = capsys.readouterr()
1384 assert "ortho=0 has no effect without G" in captured.err and "ortho=" not in captured.out
1386 try:
1387 assert dist_m4ri.compute_quantum_distance(H, G=G, method=2, dmin=6, wmax=6, threads=2, ortho=False,
1388 debug=dist_m4ri.PY_DBG_COMMANDS, cache_file=None) == 6
1389 captured = capsys.readouterr()
1390 assert "ortho=0" in captured.out and captured.err.count(ortho_warning) == 1
1392 res = dist_m4ri.compute_css_distance(H, G, method=2, dmin=6, wmax=6, threads=2, ortho=False,
1393 debug=dist_m4ri.PY_DBG_COMMANDS, cache_file=None)
1394 assert res[0] == 6
1395 captured = capsys.readouterr()
1396 assert captured.out.count("ortho=0") == 2 and captured.err.count(ortho_warning) == 1
1398 assert dist_m4ri.parse_cli_args(["ortho=0"])["ortho"] is False
1399 assert dist_m4ri.parse_cli_args([])["ortho"] is True
1400 assert dist_m4ri.main([f"finH={H}", f"finG={G}", "method=2", "dmin=6", "wmax=6", "threads=2", "ortho=0",
1401 "--no-cache", f"debug={dist_m4ri.PY_DBG_COMMANDS}"]) == 0
1402 captured = capsys.readouterr()
1403 assert "ortho=0" in captured.out and captured.err.count(ortho_warning) == 1
1404 dem_file = os.path.join(EXAMPLES_DIR, "surf_d3.dem")
1405 assert dist_m4ri.main([f"fdem={dem_file}", "method=2", "wmax=3", "threads=2", "ortho=0", "--no-cache"]) == 0
1406 assert "ortho=0: only used for a quantum code with G" in capsys.readouterr().err
1407 assert dist_m4ri.main([f"finH={H}", "ortho=2", "--no-cache"]) == 1
1408 assert "invalid 'ortho=2': ortho should be 0 or 1" in capsys.readouterr().err
1409 finally:
1412
1413
1415 # with debug=8 (bit 8 of the binary), _last_run_stats["cc_rounds"] lists the completed CC rounds (weight, CC thread
1416 # time, predicted work, wall time, CC threads, and nodes: the calls of the CC recursion), and "cc_nodes" is the
1417 # total of the nodes; the lines of binaries before 0.15.0 have no nodes (no "cc_nodes"). The number of nodes of a
1418 # completed round does not depend on the threads (surf_d5: the rounds w=1..4 are completed, the round w=5 stops at
1419 # the first codeword; method=2 or method=3 without RW steps)
1421 "# CC round w=3: started 2 more threads, 4 in total (work not known)\n"
1422 "# CC round w=3 work: 0.00125 thread-s measured, unknown predicted (0.001s on 4 threads), 1956 nodes\n"
1423 "# CC round w=4 started: 8 CC threads, 0 RW threads (bounds [4, 0], rem_rw=0, rem_time=0.00s, est 0.002 "
1424 "thread-s)\n"
1425 "# CC round w=4 work: 1.5e-05 thread-s measured, 0.002 thread-s predicted (0.004s, 8 CC threads at the end), "
1426 "24644 nodes\n")
1427 assert stats["cc_rounds"] == [
1428 {"w": 3, "work": 0.00125, "predicted": None, "time": 0.001, "threads": 4, "nodes": 1956},
1429 {"w": 4, "work": 1.5e-05, "predicted": 0.002, "time": 0.004, "threads": 8, "nodes": 24644}]
1430 assert stats["cc_nodes"] == 1956 + 24644
1432 "# CC round w=3 work: 0.001 thread-s measured, unknown predicted (0.001s on 2 threads)\n")
1433 assert old["cc_rounds"][0]["nodes"] is None and "cc_nodes" not in old
1434 assert "cc_rounds" not in dist_m4ri._parse_stderr_stats("# codewords accumulated: total=0\n")
1435 nodes = {}
1436 for method, threads in ((2, 1), (2, 2), (3, 2)):
1437 res = dist_m4ri.run_dist_m4ri(method=method, finH=S5_H, finL=S5_L, wmax=5, threads=threads, debug=8,
1438 steps=0 if method == 3 else None)
1439 assert tuple(res) == (5, 5, 0)
1440 capsys.readouterr()
1441 stats = dist_m4ri._last_run_stats
1442 rounds = stats["cc_rounds"]
1443 assert [r["w"] for r in rounds] == [1, 2, 3, 4]
1444 assert all(1 <= r["threads"] <= threads and r["time"] >= 0.0 and r["work"] >= 0.0 for r in rounds)
1445 assert rounds[0]["nodes"] == 0 and all(r["nodes"] > 0 for r in rounds[1:])
1446 assert stats["cc_nodes"] == sum(r["nodes"] for r in rounds)
1447 nodes[(method, threads)] = [r["nodes"] for r in rounds]
1448 assert nodes[(2, 1)] == nodes[(2, 2)] == nodes[(3, 2)]
1449 dist_m4ri.run_dist_m4ri(method=2, finH=S5_H, finL=S5_L, wmax=5, threads=2, debug=1)
1450 capsys.readouterr()
1451 assert "cc_rounds" not in dist_m4ri._last_run_stats and "cc_nodes" not in dist_m4ri._last_run_stats
1452
1453
1455 # win_hyb (window size of the hybrid RW steps, n >= 500; 0, the default: 256 columns for a DEM, 512 for matrices)
1456 # is passed to the binary if nonzero by run_dist_m4ri(), the compute_*_distance() functions, and the CLI; the
1457 # input summary (debug&1) reports the windows, and the codewords found by the uniform and by the window steps are
1458 # parsed (_last_run_stats["rw_step_types"]), as the rounds of a DEM (_last_run_stats["rounds"])
1459 for val, note in ((0, "512 columns on every other step, uniform permutations on the others (win_hyb=0: 256 for a "
1460 "DEM, 512 for matrices)"),
1461 (100, "100 columns on every other step, uniform permutations on the others (win_hyb=100)")):
1462 res = dist_m4ri.run_dist_m4ri(method=1, finH=S5_H, finL=S5_L, steps=200, threads=2, win_hyb=val, verbose=True)
1463 assert res[2] > 1
1464 captured = capsys.readouterr()
1465 run_line = [ln for ln in captured.out.splitlines() if "[dist_m4ri] Running:" in ln][0]
1466 assert ("win_hyb=" in run_line) == (val != 0)
1467 windows = [ln for ln in (captured.out + captured.err).splitlines() if "RW windows:" in ln]
1468 assert len(windows) == 1 and windows[0].endswith(note)
1469 types = dist_m4ri._last_run_stats["rw_step_types"]
1470 assert types and all(t["uniform_cws"] + t["window_cws"] - t["both_cws"] == t["cws"] for t in types.values())
1471 assert "rounds" not in dist_m4ri._last_run_stats # (a matrix)
1472 assert "by RW step type" in dist_m4ri.explain_bounds(list(res), method=1, stats=dist_m4ri._last_run_stats)
1473 dem_file = os.path.join(EXAMPLES_DIR, "surf_d3.dem")
1474 dist_m4ri.run_dist_m4ri(method=2, fdem=dem_file, wmax=3, threads=2, debug=1)
1475 capsys.readouterr()
1476 rounds = dist_m4ri._last_run_stats["rounds"]
1477 assert set(rounds) == {"D", "L", "score"} and 0.0 <= rounds["score"] <= 1.0
1479 try:
1480 assert dist_m4ri.compute_quantum_distance(S5_H, L=S5_L, method=1, num_steps=20, min_hits=0, threads=2,
1481 win_hyb=300, debug=dist_m4ri.PY_DBG_COMMANDS, cache_file=None) > 0
1482 assert "win_hyb=300" in capsys.readouterr().out
1484 assert dist_m4ri.parse_cli_args(["win_hyb=200"])["win_hyb"] == 200
1485 assert dist_m4ri.parse_cli_args([])["win_hyb"] == 0
1486 assert dist_m4ri.main([f"finH={S5_H}", f"finL={S5_L}", "method=1", "steps=20", "threads=2", "win_hyb=100",
1487 "--no-cache", f"debug={dist_m4ri.PY_DBG_COMMANDS}"]) == 0
1488 assert "win_hyb=100" in capsys.readouterr().out
1489 assert dist_m4ri.main([f"finH={S5_H}", "method=1", "win_hyb=-1", "--no-cache"]) == 1
1490 assert "invalid 'win_hyb=-1'" in capsys.readouterr().err
1491 finally:
1494
1495
1497 # pairs (the RW pairs of non-pivot columns, p=2: the pairs of columns which agree on N random pivot rows; -1, the
1498 # default: auto; 0: off) is passed to the binary if not -1 by run_dist_m4ri(), the compute_*_distance() functions,
1499 # and the CLI; the summary (debug&1) gives the number l of rows (auto: 8 for surf_d5 with n=1958, rank 120, and
1500 # d=5), the pairs checked per step, and the codewords found by pairs and by single columns (_last_run_stats)
1501 for val, l_exp in ((-1, 8), (0, 0), (12, 12)):
1502 res = dist_m4ri.run_dist_m4ri(method=1, finH=S5_H, finL=S5_L, steps=200, threads=1, seed=1, pairs=val,
1503 verbose=True)
1504 assert res[1] == 5
1505 captured = capsys.readouterr()
1506 run_line = [ln for ln in captured.out.splitlines() if "[dist_m4ri] Running:" in ln][0]
1507 assert ("pairs=" in run_line) == (val != -1)
1508 stats = dist_m4ri._last_run_stats
1509 assert stats["rw_pairs_l"] == l_exp
1510 assert ("rw_pairs_per_step" in stats) == (l_exp > 0)
1511 exp = dist_m4ri.explain_bounds(list(res), method=1, stats=stats)
1512 assert (f"RW pairs (p=2, buckets of l = {l_exp} pivot rows): " in exp) == (l_exp > 0)
1514 try:
1515 assert dist_m4ri.compute_quantum_distance(S5_H, L=S5_L, method=1, num_steps=20, min_hits=0, threads=2,
1516 pairs=0, debug=dist_m4ri.PY_DBG_COMMANDS, cache_file=None) > 0
1517 assert "pairs=0" in capsys.readouterr().out
1519 assert dist_m4ri.parse_cli_args(["pairs=4"])["pairs"] == 4
1520 assert dist_m4ri.parse_cli_args([])["pairs"] == -1
1521 assert dist_m4ri.main([f"finH={S5_H}", f"finL={S5_L}", "method=1", "steps=20", "threads=2", "pairs=6",
1522 "--no-cache", f"debug={dist_m4ri.PY_DBG_COMMANDS}"]) == 0
1523 assert "pairs=6" in capsys.readouterr().out
1524 for bad in (-2, 65):
1525 assert dist_m4ri.main([f"finH={S5_H}", "method=1", f"pairs={bad}", "--no-cache"]) == 1
1526 assert f"invalid 'pairs={bad}'" in capsys.readouterr().err
1527 finally:
1530
1531
1532def test_model_lower_bound(tmp_path, capsys):
1533 # the code-sized model of a Stim memory circuit (compute_model_lower_bound(), CLI --model-lb): one row per chain of
1534 # detectors linked by their measurements (the checks over the rounds; by default only the chains with final data
1535 # measurements, all of them with minority=True, CLI --model-minority), the sum of their rows, so that every DEM
1536 # codeword gives a model codeword of no larger weight and the model distance is a certified lower bound: 3 (tight)
1537 # for the surface code with d=3 (8 checks, 4 of the measured basis), 5 for the bb_72 circuit of distance 6 (also
1538 # with the minority-basis rows); the bound is stored as dmin in the cache record
1539 if not dist_m4ri._HAS_STIM:
1540 pytest.skip("stim is not installed")
1541 import stim
1542 circ = stim.Circuit.generated("surface_code:rotated_memory_x", distance=3, rounds=3,
1543 after_clifford_depolarization=0.001, before_measure_flip_probability=0.001,
1544 after_reset_flip_probability=0.001, before_round_data_depolarization=0.001)
1545 path = str(tmp_path / "surf_x_d3.stim")
1546 circ.to_file(path)
1547 cache = str(tmp_path / "cache.json")
1549 dist_m4ri.enable_distance_cache() # (a CLI test with --no-cache may have disabled it)
1550 try:
1551 b, info = dist_m4ri.compute_model_lower_bound(path, threads=2, cache_file=cache, out_model=str(tmp_path / "m"),
1552 return_info=True)
1553 assert b == 3 and info["model_d_info"][:2] == [3, 3] and info["d_info"] == [3, 0, 0]
1554 assert (info["groups"], info["groups_final"], info["rows"], info["rows_measured"]) == (8, 4, 4, 4)
1555 assert info["trivial"] == 0 and len(info["col_map"]) == info["dem_cols"]
1556 assert info["files"] == [str(tmp_path / "m_HR.mtx"), str(tmp_path / "m_LR.mtx")]
1557 assert all(os.path.isfile(f) for f in info["files"])
1558 entry = dist_m4ri.get_cached_distance(circuit=path, cache_file=cache)
1559 assert (entry["dmin"], entry["dmax"], entry["dmin_model"]) == (3, 0, 3)
1560 b, info = dist_m4ri.compute_model_lower_bound(path, threads=2, cache_file=cache, minority=True,
1561 return_info=True)
1562 assert b == 3 and (info["rows"], info["rows_measured"]) == (8, 4)
1563 # the DEM run starts CC from the cached bound; its codewords map to model codewords of no larger weight
1564 dist, d_info, cws = dist_m4ri.compute_dem_distance(circuit=path, method=2, wmax=4, dW=1, drop=0, do_cws=True,
1565 threads=2, cache_file=cache)
1566 assert dist == 3 and d_info[:2] == [3, 3] and len(cws) > 0
1567 entry = dist_m4ri.get_cached_distance(circuit=path, cache_file=cache)
1568 assert (entry["dmin"], entry["dmax"], entry["dmin_model"]) == (3, 3, 3)
1569 H, L = info["H"].toarray(), info["L"].toarray()
1570 for cw in cws:
1571 y = np.zeros(H.shape[1], dtype=int)
1572 for j in cw:
1573 if info["col_map"][j] >= 0:
1574 y[info["col_map"][j]] ^= 1
1575 assert not (H @ y % 2).any() and (L @ y % 2).any() and y.sum() <= len(cw)
1576 # a circuit in the repository: the model is 1 below the distance 6 (also with the measured-basis rows only)
1577 bb72 = os.path.join(EXAMPLES_DIR, "..", "benchmark", "stim_bb", "bb_72_12_6_si1000_r6_X.stim")
1578 for minority in (True, False):
1579 b, info = dist_m4ri.compute_model_lower_bound(bb72, threads=2, minority=minority, cache_file=None,
1580 return_info=True)
1581 assert b == 5 and info["rows"] == (72 if minority else 36) and info["rows_measured"] == 36
1582 # an error which flips the observable but no detector: the bound is 1, with a note
1583 triv = str(tmp_path / "triv.stim")
1584 with open(triv, "w") as f:
1585 f.write("R 0 1 2 3\nX_ERROR(0.1) 0 1 2\nCX 0 3 1 3\nMR 3\nDETECTOR rec[-1]\nM 0 1 2\n"
1586 "DETECTOR rec[-2] rec[-3] rec[-4]\nOBSERVABLE_INCLUDE(0) rec[-1]\n")
1587 b, info = dist_m4ri.compute_model_lower_bound(triv, threads=2, cache_file=None, return_info=True)
1588 assert b == 1 and info["trivial"] == 1 and (info["groups"], info["groups_final"]) == (1, 1)
1589 assert "flip an observable but no group of detectors" in capsys.readouterr().err
1590 # sort_rows and cc_prune are passed to each run of the binary (the bound does not depend on them)
1591 dist_m4ri.clear_distance_cache() # (the model of the first runs is cached)
1592 b = dist_m4ri.compute_model_lower_bound(path, threads=2, cache_file=None, minority=True, sort_rows=False,
1593 cc_prune=0, debug=dist_m4ri.PY_DBG_COMMANDS)
1594 runs = [ln for ln in capsys.readouterr().out.splitlines() if "[dist_m4ri] Running:" in ln]
1595 assert b == 3 and len(runs) >= 2 and all("sort_rows=0" in ln and "cc_prune=0" in ln for ln in runs)
1596 # CLI: the bounds of the circuit (without the cache: the bound alone), the model files; errors and warnings
1597 assert dist_m4ri.main([f"fdem={path}", "--model-lb", "--no-cache", "threads=2", "--out-model",
1598 str(tmp_path / "cli")]) == 0
1599 assert capsys.readouterr().out.strip().splitlines()[-1] == "3 0 0"
1600 assert os.path.isfile(str(tmp_path / "cli_HR.mtx")) and os.path.isfile(str(tmp_path / "cli_LR.mtx"))
1601
1602 def mtx_rows(path): # the number of rows of a MatrixMarket file (its size line; scipy's mmread warns since 1.18)
1603 with open(path) as f:
1604 return int(next(ln for ln in f if not ln.startswith("%")).split()[0])
1605
1606 assert mtx_rows(tmp_path / "cli_HR.mtx") == 4 # (the measured-basis rows only, the default)
1607 assert dist_m4ri.main([f"fdem={path}", "--model-lb", "--model-minority", "--no-cache", "threads=2",
1608 "--out-model", str(tmp_path / "clim")]) == 0
1609 assert capsys.readouterr().out.strip().splitlines()[-1] == "3 0 0"
1610 assert mtx_rows(tmp_path / "clim_HR.mtx") == 8 # (also the minority-basis rows)
1611 assert dist_m4ri.main([f"fdem={path}", "--model-lb", "--no-cache", "threads=2", "sort_rows=1", "--cc-prune",
1612 "2", f"debug={dist_m4ri.PY_DBG_COMMANDS}"]) == 0
1613 out = capsys.readouterr().out.strip().splitlines()
1614 runs = [ln for ln in out if "[dist_m4ri] Running:" in ln]
1615 assert out[-1] == "3 0 0" and runs and all("sort_rows=1" in ln and "cc_prune=2" in ln for ln in runs)
1616 assert dist_m4ri.main([f"fdem={path}", "model_minority=1", "--no-cache", "threads=2", "method=2"]) == 0
1617 assert "# Warning: --model-minority: only used with --model-lb; ignored" in capsys.readouterr().err
1618 dist_m4ri.enable_distance_cache() # (--no-cache disabled it)
1619 assert dist_m4ri.main([f"fdem={path}", "--model-lb", f"cache={cache}", "threads=2", "outC=x.nz"]) == 0
1620 captured = capsys.readouterr()
1621 assert captured.out.strip().splitlines()[-1] == "3 3 0 (exact)"
1622 assert "# Warning: outC=: not used with --model-lb; ignored" in captured.err
1623 dem_file = os.path.join(EXAMPLES_DIR, "surf_d3.dem")
1624 assert dist_m4ri.main([f"fdem={dem_file}", "--model-lb", "--no-cache", "threads=2"]) == 0
1625 assert capsys.readouterr().out.strip().splitlines()[-1] == "3 0 0"
1626 dem_bound, dem_info = dist_m4ri.compute_model_lower_bound(dem_file, threads=2, return_info=True)
1627 assert dem_bound == 3 and dem_info["groups"] == 8 and dem_info["groups_final"] == 4
1628 with pytest.raises(ValueError, match="not a Stim circuit"):
1630 with pytest.raises(TypeError, match="did you mean 'minority'"):
1631 dist_m4ri.compute_model_lower_bound(path, minorty=False)
1632 finally:
1635
1636
1637def test_start_list_cache_records(tmp_path, monkeypatch, capsys):
1638 import json
1639 cache_file = str(tmp_path / "start_cache.json")
1642 try:
1643 # 1. Start-list run: separate ':start=0,41' record; the main record only gets dmax (and codewords)
1645 S5_H, L=S5_L, method=2, wmax=7, start="41,0", threads=2, return_info=True, cache_file=cache_file
1646 )
1647 assert dist == 5 and d_info == [5, 5, 0]
1648 err = capsys.readouterr().err
1649 assert "WARNING: start=0,41 (expert option)" in err
1650 assert "use trust_start=1 to also accept dmin" in err
1651 plain = dist_m4ri.get_cached_distance(H=S5_H, L=S5_L, cache_file=cache_file)
1652 srec = dist_m4ri.get_cached_distance(H=S5_H, L=S5_L, cache_file=cache_file, start=[0, 41])
1653 assert srec["dmin"] == 5 and srec["dmax"] == 5
1654 assert plain["dmin"] == 0 and plain["dmax"] == 5
1655 with open(cache_file) as f:
1656 data = json.load(f)
1657 assert any(k.startswith("quantum:") and k.endswith(":start=0,41") for k in data)
1658
1659 # 2. Repeated start-list call is a cache hit (the binary is not called)
1660 def _no_run(*args, **kwargs):
1661 raise AssertionError("dist_m4ri binary should not be called")
1662 monkeypatch.setattr(dist_m4ri, "run_dist_m4ri", _no_run)
1663 dist2, d_info2 = dist_m4ri.compute_quantum_distance(
1664 S5_H, L=S5_L, method=2, wmax=7, start=[0, 41], threads=2, return_info=True, cache_file=cache_file
1665 )
1666 assert dist2 == 5 and d_info2 == [5, 5, 0]
1667 assert dist_m4ri.get_cached_distance(H=S5_H, L=S5_L, cache_file=cache_file)["dmin"] == 0
1668
1669 # 3. trust_start copies the exact start-list record to the main record without a calculation
1670 dist3, d_info3 = dist_m4ri.compute_quantum_distance(
1671 S5_H, L=S5_L, method=2, wmax=7, start=[0, 41], trust_start=True, threads=2, return_info=True,
1672 cache_file=cache_file
1673 )
1674 assert dist3 == 5 and d_info3 == [5, 5, 0]
1675 assert "trust_start=1: start-list results are accepted" in capsys.readouterr().err
1676 with open(cache_file) as f:
1677 data = json.load(f)
1678 plain_keys = [k for k in data if k.startswith("quantum:") and ":start=" not in k]
1679 assert len(plain_keys) == 1
1680 assert data[plain_keys[0]]["dmin"] == 5 and data[plain_keys[0]]["dmax"] == 5
1681
1682 # 4. The main record is now exact: a call without a start list is a cache hit
1683 dist4, d_info4 = dist_m4ri.compute_quantum_distance(
1684 S5_H, L=S5_L, method=2, wmax=7, threads=2, return_info=True, cache_file=cache_file
1685 )
1686 assert dist4 == 5 and d_info4 == [5, 5, 0]
1687 finally:
1689
1690
1692 cache_file = str(tmp_path / "start_css_cache.json")
1695 try:
1696 kw = dict(Hx=S5_H, Hz=S5_H, Lz=S5_L, Lx=S5_L, method=2, wmax=7, threads=2, cache_file=cache_file)
1697 # Without trust_start: only the upper bounds (and codewords) reach the main record
1698 dist, dx, dz = dist_m4ri.compute_css_distance(start="0,41", **kw)
1699 assert dist == 5 and dx == [5, 5, 0] and dz == [5, 5, 0]
1700 plain = dist_m4ri.get_cached_distance(Hx=S5_H, Hz=S5_H, Lx=S5_L, Lz=S5_L, cache_file=cache_file)
1701 assert plain["dmax_X"] == 5 and plain["dmax_Z"] == 5
1702 assert plain["dmin_X"] == 0 and plain["dmin_Z"] == 0
1703 # With trust_start and another start list: after the calculation, dmin is merged as well
1704 dist2, dx2, dz2 = dist_m4ri.compute_css_distance(start=[0, 41, 12], trust_start=True, **kw)
1705 assert dist2 == 5 and dx2 == [5, 5, 0] and dz2 == [5, 5, 0]
1706 plain2 = dist_m4ri.get_cached_distance(Hx=S5_H, Hz=S5_H, Lx=S5_L, Lz=S5_L, cache_file=cache_file)
1707 assert plain2["dX"] == [5, 5, 0] and plain2["dZ"] == [5, 5, 0]
1708 assert plain2["dmin"] == 5 and plain2["dmax"] == 5 and plain2["dist"] == 5
1709 finally:
1711
1712
1715 ["finH=h.mtx", "start=5,1,1", "--trust-start", "noscan=1", "cbeg=2", "cend=3", "irred=0"]
1716 )
1717 assert args["start"] == [1, 5]
1718 assert args["trust_start"] is True
1719 assert (args["noscan"], args["cbeg"], args["cend"], args["irred"]) == (1, 2, 3, 0)
1720 assert dist_m4ri.parse_cli_args([])["irred"] is None
1721 assert dist_m4ri.parse_cli_args(["trust_start=0"])["trust_start"] is False
1722 assert dist_m4ri.parse_cli_args(["trust-start=1"])["trust_start"] is True
1723 assert dist_m4ri.parse_cli_args(["--trust-start=yes"])["trust_start"] is True
1724 assert dist_m4ri.parse_cli_args(["start=-1"])["start"] is None
1725 assert dist_m4ri.parse_cli_args([])["trust_start"] is False
1726
1727 try:
1728 # Invalid start list is an error
1729 ret = dist_m4ri.main([f"finH={S5_H}", f"finL={S5_L}", "start=1,,2", "--no-cache"])
1730 assert ret == 1
1731 assert "invalid start='1,,2'" in capsys.readouterr().err
1732
1733 # Disabled options are ignored with a warning; the start list gives the distance with a warning
1734 ret = dist_m4ri.main([
1735 f"finH={S5_H}", f"finL={S5_L}", "method=2", "wmax=7", "noscan=1", "cbeg=30",
1736 "start=0,41", "--no-cache", "threads=2", "irred=0"
1737 ])
1738 assert ret == 0
1739 captured = capsys.readouterr()
1740 assert "5 5 0 (exact)" in captured.out
1741 assert "noscan=1 is disabled in the Python interface" in captured.err
1742 assert "cbeg=30 is disabled in the Python interface" in captured.err
1743 assert "irred=0 is disabled in the Python interface" in captured.err
1744 assert "WARNING: start=0,41 (expert option)" in captured.err
1745 # irred=1 (the binary's default) is accepted silently
1746 ret = dist_m4ri.main([f"finH={S5_H}", f"finL={S5_L}", "method=2", "wmax=7", "--no-cache", "irred=1"])
1747 assert ret == 0
1748 captured = capsys.readouterr()
1749 assert "5 5 0 (exact)" in captured.out
1750 assert "irred" not in captured.err
1751 finally:
1753
1754
1755TRY_X = os.path.join(EXAMPLES_DIR, "tryX.mtx") # small CSS code [[40,10,4]]
1756TRY_Z = os.path.join(EXAMPLES_DIR, "tryZ.mtx")
1757
1758
1760 empty = np.zeros((0, 40), dtype=np.int8)
1761 for hx, hz in ((TRY_X, None), (None, TRY_Z), (TRY_X, empty), (None, None)):
1762 with pytest.raises(ValueError, match="requires both Hx and Hz"):
1763 dist_m4ri.compute_css_distance(Hx=hx, Hz=hz, method=2, wmax=4, threads=2)
1764 try:
1765 # CLI: an error message and exit code 1
1766 ret = dist_m4ri.main([f"Hx={TRY_X}", "method=2", "wmax=4", "--no-cache"])
1767 assert ret == 1
1768 assert "requires both Hx and Hz" in capsys.readouterr().err
1769 finally:
1771
1772
1774 r = dist_m4ri.DistanceResult(5, 5, 0)
1775 assert r == 5 and r == np.int64(5) and r != 4
1776 assert r == (5, 5, 0) and r == [5, 5, 0]
1777 assert r == dist_m4ri.DistanceResult(5, 5, 0) and r != dist_m4ri.DistanceResult(5, 5, 10)
1778 assert r != "5"
1779 assert len(r) == 3 and r[1] == 5 and tuple(r) == (5, 5, 0)
1780 rc = dist_m4ri.DistanceResult(4, 4, 0, cws=[[0, 1, 2, 3]])
1781 assert len(rc) == 4 and rc[3] == [[0, 1, 2, 3]] and rc == (4, 4, 0, [[0, 1, 2, 3]])
1782 assert int(dist_m4ri.DistanceResult(3, 6, 100)) == 6
1783
1784
1786 import json
1787 cache_file = tmp_path / "newer_cache.json"
1788 newer = {"__version__": "99.0.0", "some_key": {"dist": 7}}
1789 cache_file.write_text(json.dumps(newer))
1792 try:
1793 dist_m4ri.load_distance_cache(str(cache_file))
1794 assert "has newer version 99.0.0" in capsys.readouterr().err
1795 assert "some_key" not in dist_m4ri._distance_cache
1796 # neither a calculation with this cache file nor save_distance_cache() overwrites it
1797 d = dist_m4ri.compute_classical_distance(S5_H, method=2, wmax=3, threads=2, cache_file=str(cache_file))
1798 assert d == 2
1799 dist_m4ri.save_distance_cache(str(cache_file))
1800 assert json.loads(cache_file.read_text()) == newer
1801 # once the file is deleted with clear_distance_cache(clear_file=True), it is written again
1802 dist_m4ri.clear_distance_cache(str(cache_file), clear_file=True)
1803 dist_m4ri._distance_cache["code_test"] = {"dist": 3, "dmin": 3, "dmax": 3}
1804 dist_m4ri.save_distance_cache(str(cache_file))
1805 assert json.loads(cache_file.read_text())["__version__"] == dist_m4ri.__version__
1806 finally:
1808
1809
1811 import threading
1812 import time
1813 s5 = dict(finH=S5_H, finL=S5_L)
1814 # More than 64 KB of stderr (the matrices: debug=128|2048) with a stop_event which is not set: no deadlock
1815 result = {}
1816
1817 def run():
1818 result["res"] = dist_m4ri.run_dist_m4ri(
1819 method=2, wmax=3, threads=2, debug=128 | 2048, stop_event=threading.Event(), **s5
1820 )
1821
1822 worker = threading.Thread(target=run, daemon=True)
1823 worker.start()
1824 worker.join(timeout=120)
1825 assert not worker.is_alive(), "run_dist_m4ri() with stop_event is blocked by a full stderr pipe"
1826 assert tuple(result["res"]) == (4, 0, 0)
1827 assert len(capsys.readouterr().out) > 65536 # the stderr of the binary is printed (debug > 0)
1828
1829 # Cancelled run: the binary is terminated, and RuntimeError is raised
1830 stop = threading.Event()
1831 timer = threading.Timer(0.3, stop.set)
1832 timer.start()
1833 t0 = time.time()
1834 try:
1835 with pytest.raises(RuntimeError, match="cancelled by stop_event"):
1836 dist_m4ri.run_dist_m4ri(method=1, steps=10**9, min_hits=0, timeout=60, threads=2, stop_event=stop, **s5)
1837 assert time.time() - t0 < 30
1838 finally:
1839 timer.cancel()
1840
1841
1843 if not dist_m4ri._HAS_STIM:
1844 pytest.skip("stim is not installed")
1845 import stim
1846 circuit = stim.Circuit("""
1847 R 0 1 2
1848 TICK
1849 H 0
1850 CX 0 1
1851 TICK
1852 M 0
1853 CX rec[-1] 1
1854 CZ sweep[0] 2
1855 TICK
1856 M 1 2
1857 DETECTOR rec[-2]
1858 OBSERVABLE_INCLUDE(0) rec[-1]
1859 """)
1860 noisy = dist_m4ri.add_noise(circuit, p=0.01)
1861 assert "DEPOLARIZE2(0.01) 0 1" in str(noisy)
1862 # noise only on qubits: a classically controlled gate gets DEPOLARIZE1(p/10) on its qubit only
1863 for inst in noisy.flattened():
1864 if inst.name in ("DEPOLARIZE1", "DEPOLARIZE2"):
1865 assert all(t.qubit_value is not None for t in inst.targets_copy())
1866 lines = [s.strip() for s in str(noisy).splitlines()]
1867 assert lines[lines.index("CX rec[-1] 1") + 1] == "DEPOLARIZE1(0.001) 1"
1868 assert lines[lines.index("CZ sweep[0] 2") + 1] == "DEPOLARIZE1(0.001) 2"
1869 assert noisy.detector_error_model().num_errors > 0
1870
1871
1873 # add_noise() classifies the gates with stim.gate_data(): DEPOLARIZE1(p/10) after every one-qubit gate,
1874 # DEPOLARIZE2(p) after every two-qubit gate, flips at resets and measurements, and no noise for the multi-qubit
1875 # measurements and Pauli product gates; strip_noise() removes all noise (heralded noise -> MPAD)
1876 if not dist_m4ri._HAS_STIM:
1877 pytest.skip("stim is not installed")
1878 import stim
1879 circuit = stim.Circuit("""
1880 R 0 1 2 3
1881 RX 4
1882 TICK
1883 H_XY 0
1884 ISWAP 1 2
1885 C_XYZ 3
1886 SQRT_XX 0 4
1887 TICK
1888 MPP X1*X2
1889 MXX 1 2
1890 SPP Z3
1891 MRX 4
1892 MY 0
1893 TICK
1894 M 1 2 3
1895 DETECTOR rec[-2]
1896 OBSERVABLE_INCLUDE(0) rec[-1]
1897 """)
1898 noisy = dist_m4ri.add_noise(circuit, p=0.01)
1899 lines = [s.strip() for s in str(noisy).splitlines()]
1900
1901 def after(line: str) -> str:
1902 return lines[lines.index(line) + 1]
1903
1904 def before(line: str) -> str:
1905 return lines[lines.index(line) - 1]
1906
1907 assert after("R 0 1 2 3") == "X_ERROR(0.01) 0 1 2 3" and after("RX 4") == "Z_ERROR(0.01) 4"
1908 assert after("H_XY 0") == "DEPOLARIZE1(0.001) 0" and after("C_XYZ 3") == "DEPOLARIZE1(0.001) 3"
1909 assert after("ISWAP 1 2") == "DEPOLARIZE2(0.01) 1 2" and after("SQRT_XX 0 4") == "DEPOLARIZE2(0.01) 0 4"
1910 assert after("MPP X1*X2") == "MXX 1 2" and after("MXX 1 2") == "SPP Z3" # (no noise)
1911 assert before("MRX 4") == after("MRX 4") == "Z_ERROR(0.01) 4"
1912 assert before("MY 0") == "X_ERROR(0.01) 0" and before("M 1 2 3") == "X_ERROR(0.01) 1 2 3"
1913 assert not dist_m4ri.has_noise(circuit) and dist_m4ri.has_noise(noisy)
1914 # strip_noise() and force=True: the same circuit, and the same noise model
1915 assert dist_m4ri.strip_noise(noisy) == circuit
1916 assert dist_m4ri.add_noise(noisy, p=0.01, force=True) == noisy
1917 assert dist_m4ri.add_noise(noisy, p=0.0, force=True) == circuit and dist_m4ri.add_noise(circuit, p=0.0) == circuit
1918 # a noisy measurement is noise; heralded noise becomes MPAD (the measurement record does not change)
1919 other = stim.Circuit("R 0 1\nHERALDED_ERASE(0.01) 0\nM(0.02) 0 1\nDETECTOR rec[-1] rec[-3]\n")
1920 assert dist_m4ri.has_noise(stim.Circuit("R 0\nM(0.01) 0\n"))
1921 stripped = dist_m4ri.strip_noise(other)
1922 assert str(stripped).splitlines() == ["R 0 1", "MPAD 0", "M 0 1", "DETECTOR rec[-1] rec[-3]"]
1923 assert stripped.num_measurements == other.num_measurements and not dist_m4ri.has_noise(stripped)
1924
1925
1927 cache_file = str(tmp_path / "css_cache.json")
1930 kw = dict(Hx=TRY_X, Hz=TRY_Z, method=2, wmax=8, threads=2)
1931 try:
1932 # A calculation writes the sector files outC_X / outC_Z ...
1933 res1 = dist_m4ri.compute_css_distance(outC=str(tmp_path / "cws1.nz"), cache_file=cache_file, **kw)
1934 assert res1 == (4, [4, 4, 0], [4, 4, 0])
1935 # ... and so does the cached exact result
1936 capsys.readouterr()
1938 outC=str(tmp_path / "cws2.nz"), cache_file=cache_file, debug=dist_m4ri.PY_DBG_CACHE, **kw
1939 )
1940 assert res2 == res1
1941 assert "Cache hit for CSS distance" in capsys.readouterr().out
1942 assert not (tmp_path / "cws2.nz").exists()
1943 for s in ("X", "Z"):
1944 cws1 = dist_m4ri.read_sparse_vectors(str(tmp_path / f"cws1_{s}.nz"))
1945 cws2 = dist_m4ri.read_sparse_vectors(str(tmp_path / f"cws2_{s}.nz"))
1946 assert len(cws1) == 40 and sorted(map(tuple, cws1)) == sorted(map(tuple, cws2))
1947
1949 # finC identical to outC: the sector files are read (finC itself does not exist)
1950 same = str(tmp_path / "cws1.nz")
1951 res3 = dist_m4ri.compute_css_distance(finC=same, outC=same, debug=1, **kw)
1952 assert res3 == res1
1953 out = capsys.readouterr().out
1954 assert "codewords from " + str(tmp_path / "cws1_X.nz") in out
1955 assert "codewords from " + str(tmp_path / "cws1_Z.nz") in out
1956 # Without sector files, finC itself is given to both runs (codewords not valid in a sector are skipped)
1957 mixed = tmp_path / "mixed.nz"
1958 mixed.write_text((tmp_path / "cws1_X.nz").read_text())
1959 res4 = dist_m4ri.compute_css_distance(finC=str(mixed), debug=1, **kw)
1960 assert res4 == res1
1961 assert capsys.readouterr().out.count("codewords from " + str(mixed)) == 2
1962 finally:
1965
1966
1967def _hgp_rep(a: int, b: int):
1968 """Hypergraph product of the repetition codes of lengths a and b (a planar surface code): dZ = a, dX = b."""
1969 def rep(n):
1970 H = np.zeros((n - 1, n), dtype=np.int8)
1971 for i in range(n - 1):
1972 H[i, i] = H[i, i + 1] = 1
1973 return H
1974 H1, H2 = rep(a), rep(b)
1975 Hx = np.hstack([np.kron(H1, np.eye(b, dtype=np.int8)), np.kron(np.eye(a - 1, dtype=np.int8), H2.T)]) % 2
1976 Hz = np.hstack([np.kron(np.eye(a, dtype=np.int8), H2), np.kron(H1.T, np.eye(b - 1, dtype=np.int8))]) % 2
1977 return Hx.astype(np.int8), Hz.astype(np.int8)
1978
1979
1980def test_dstop_stop_target(tmp_path, capsys):
1981 import time
1982 s5 = dict(finH=S5_H, finL=S5_L) # d = 5
1983 # method=3: the run ends once CC has certified dmin >= dstop, without waiting for RW (here: until the timeout)
1984 t0 = time.time()
1985 res = dist_m4ri.run_dist_m4ri(method=3, dstop=4, steps=10**8, min_hits=0, timeout=60, threads=4, **s5)
1986 assert res[0] == 4 and res[1] in (0, 5) and time.time() - t0 < 30
1987 # method=2 needs no wmax with dstop; dstop is not an upper bound (dmax = 0: no codeword found)
1988 assert tuple(dist_m4ri.run_dist_m4ri(method=2, dstop=4, threads=2, **s5)) == (4, 0, 0)
1989 with pytest.raises(ValueError, match="dstop>0"):
1990 dist_m4ri.run_dist_m4ri(method=2, timeout=0, threads=2, **s5)
1991
1992 cache_file = str(tmp_path / "dstop_cache.json")
1995 kw = dict(method=2, threads=2, return_info=True, cache_file=cache_file)
1996 try:
1997 assert dist_m4ri.compute_quantum_distance(S5_H, L=S5_L, dstop=4, **kw) == (4, [4, 0, 0])
1998 # a cached lower bound dmin >= dstop answers the request
1999 capsys.readouterr()
2000 res2 = dist_m4ri.compute_quantum_distance(S5_H, L=S5_L, dstop=3, debug=dist_m4ri.PY_DBG_CACHE, **kw)
2001 assert res2 == (4, [4, 0, 0])
2002 assert "Cache hit for quantum distance (dmin=4 >= dstop=3 known)!" in capsys.readouterr().out
2003 # a larger stop target continues from the cached lower bound, and so does the exact calculation
2004 assert dist_m4ri.compute_quantum_distance(S5_H, L=S5_L, dstop=5, **kw) == (5, [5, 0, 0])
2005 assert dist_m4ri.compute_quantum_distance(S5_H, L=S5_L, wmax=5, **kw) == (5, [5, 5, 0])
2006 finally:
2008
2009 text = dist_m4ri.explain_bounds([4, 0, 0], method=2, dstop=4)
2010 assert "Stop target (dstop = 4): reached" in text
2011 assert "Stop target" not in dist_m4ri.explain_bounds([4, 0, 0], method=2)
2012 assert "Stop target" not in dist_m4ri.explain_bounds([5, 5, 0], method=2, dstop=4)
2013
2014
2015def test_cli_dstop(capsys):
2016 assert dist_m4ri.parse_cli_args(["dstop=4"])["dstop"] == 4
2017 assert dist_m4ri.parse_cli_args([])["dstop"] == 0
2018 try:
2019 assert dist_m4ri.main([f"finH={S5_H}", "dstop=-1", "--no-cache"]) == 1
2020 assert "invalid 'dstop=-1': dstop must be non-negative" in capsys.readouterr().err
2021 ret = dist_m4ri.main([f"finH={S5_H}", f"finL={S5_L}", "method=2", "dstop=4", "threads=2", "--no-cache", "-v"])
2022 assert ret == 0
2023 out = capsys.readouterr().out
2024 assert out.rstrip().endswith("4 0 0") and "Stop target (dstop = 4): reached" in out
2025 # CSS: a user dmax (a bound on d = min(dX, dZ)) only stops both sector runs; then d = dmax
2026 ret = dist_m4ri.main([f"Hx={TRY_X}", f"Hz={TRY_Z}", "method=2", "dmax=4", "threads=2", "--no-cache"])
2027 assert ret == 0
2028 assert "dX: 4 0 0 dZ: 4 0 0 (d = 4) (exact)" in capsys.readouterr().out
2029 finally:
2031
2032
2034 Hx, Hz = _hgp_rep(3, 5) # dZ = 3, dX = 5
2035 cache_file = str(tmp_path / "css_sectors.json")
2038 kw = dict(method=2, wmax=6, threads=2, cache_file=cache_file)
2039 try:
2040 # A user dmax is a bound on d = min(dX, dZ), not on either sector: it stops both sector runs (dstop),
2041 # and it is neither reported as nor stored as the upper bound of a sector
2042 assert dist_m4ri.compute_css_distance(Hx, Hz, dmax=3, **kw) == (3, [3, 0, 0], [3, 0, 0])
2043 assert dist_m4ri.get_cached_distance(H=Hz, G=Hx, cache_file=cache_file)["d_info"] == [3, 0, 0]
2044 # Without dmax, both sectors are computed (from the cached lower bounds)
2045 assert dist_m4ri.compute_css_distance(Hx, Hz, **kw) == (3, [5, 5, 0], [3, 3, 0])
2046 # The sector records are those of compute_quantum_distance(): (H=Hz, G=Hx) -> dX, (H=Hx, G=Hz) -> dZ
2047 capsys.readouterr()
2048 dbg = dist_m4ri.PY_DBG_CACHE
2049 assert dist_m4ri.compute_quantum_distance(Hz, G=Hx, return_info=True, debug=dbg, **kw) == (5, [5, 5, 0])
2050 assert dist_m4ri.compute_quantum_distance(Hx, G=Hz, return_info=True, debug=dbg, **kw) == (3, [3, 3, 0])
2051 assert capsys.readouterr().out.count("Cache hit for quantum distance (exact distance known)!") == 2
2052 entry = dist_m4ri.get_cached_distance(Hx=Hx, Hz=Hz, cache_file=cache_file)
2053 assert entry["dX"] == [5, 5, 0] and entry["dZ"] == [3, 3, 0]
2054 assert (entry["dist"], entry["dmin"], entry["dmax"]) == (3, 3, 3)
2055 assert dist_m4ri.compute_css_distance(Hx, Hz, dstop=3, debug=dbg, **kw) == (3, [5, 5, 0], [3, 3, 0])
2056 assert "Cache hit for CSS distance (exact distance known)!" in capsys.readouterr().out
2057 finally:
2059
2060
2062 import json
2063 Hx, Hz = _hgp_rep(3, 5) # dZ = 3, dX = 5
2064 st = dist_m4ri.get_sparse_array_state
2065 key = f"css:X={st(Hx)}:Z={st(Hz)}"
2066 key_x, key_z = f"quantum:H={st(Hz)}:G={st(Hx)}", f"quantum:H={st(Hx)}:G={st(Hz)}"
2067 # A CSS record of an earlier version after compute_css_distance(dmax=3): the bound on d = min(dX, dZ) was
2068 # reported back as the upper bound of both sectors (wrong for dX = 5)
2069 old = {"dist": 3, "dmin": 3, "dmax": 3, "rw_steps": 0, "dmin_X": 3, "dmax_X": 3, "rw_steps_X": 0,
2070 "dmin_Z": 3, "dmax_Z": 3, "rw_steps_Z": 0, "dX": [3, 3, 0], "dZ": [3, 3, 0], "cws_X": [], "cws_Z": []}
2071 cache_file = tmp_path / "old_css_cache.json"
2072 cache_file.write_text(json.dumps({"__version__": dist_m4ri.__version__, key: old}))
2075 try:
2076 # The lower bounds are kept, the unconfirmed upper bounds are dropped, and the old record is removed
2077 entry = dist_m4ri.get_cached_distance(Hx=Hx, Hz=Hz, cache_file=str(cache_file))
2078 assert entry["dX"] == [3, 0, 0] and entry["dZ"] == [3, 0, 0]
2079 data = json.loads(cache_file.read_text())
2080 assert key not in data and key_x in data and key_z in data
2081 res = dist_m4ri.compute_css_distance(Hx, Hz, method=2, wmax=6, threads=2, cache_file=str(cache_file))
2082 assert res == (3, [5, 5, 0], [3, 3, 0])
2083
2084 # An upper bound confirmed by a stored codeword (only its weight is used here), or smaller than the upper
2085 # bound of the other sector, is kept
2087 old2 = dict(old, dmax_X=5, cws_X=[[0, 1, 2, 3, 4]], dmax_Z=3)
2088 cache_file.write_text(json.dumps({"__version__": dist_m4ri.__version__, key: old2}))
2089 entry2 = dist_m4ri.get_cached_distance(Hx=Hx, Hz=Hz, cache_file=str(cache_file))
2090 assert entry2["dX"] == [3, 5, 0] and entry2["dZ"] == [3, 3, 0] and entry2["cws_X"] == [[0, 1, 2, 3, 4]]
2091 assert (entry2["dist"], entry2["dmin"], entry2["dmax"]) == (3, 3, 3)
2092 finally:
2094
2095
2097 cache_file = str(tmp_path / "cws_cache.json")
2098 out = str(tmp_path / "cws.nz")
2101 kw = dict(method=2, wmax=5, threads=2, cache_file=cache_file)
2102 try:
2103 d, cws = dist_m4ri.compute_quantum_distance(S5_H, L=S5_L, outC=out, do_cws=True, **kw)
2104 assert d == 5 and len(cws) >= 2
2105 extra = sorted(set(cws[0]) ^ set(cws[1])) # another vector (of even weight, not in cws)
2106 dist_m4ri._write_nzlist_file(out, cws + [extra])
2107 # A cache hit with finC identical to outC keeps the codewords in the file
2108 d2, cws2 = dist_m4ri.compute_quantum_distance(S5_H, L=S5_L, finC=out, outC=out, do_cws=True, **kw)
2109 assert d2 == 5 and sorted(map(tuple, cws2)) == sorted(map(tuple, cws))
2110 assert sorted(map(tuple, dist_m4ri.read_sparse_vectors(out))) == sorted(map(tuple, cws + [extra]))
2111 # Without finC, outC gets the cached codewords
2112 dist_m4ri.compute_quantum_distance(S5_H, L=S5_L, outC=out, **kw)
2113 assert sorted(map(tuple, dist_m4ri.read_sparse_vectors(out))) == sorted(map(tuple, cws))
2114 finally:
2116
2117
2118SURF_D3_DEM = os.path.join(EXAMPLES_DIR, "surf_d3.dem")
2119
2120
2122 with pytest.raises(TypeError, match=r"compute_quantum_distance\‍(\‍) got an unexpected keyword argument 'dmx' "
2123 r"\‍(did you mean 'dmax'\?\‍)"):
2124 dist_m4ri.compute_quantum_distance(S5_H, L=S5_L, method=2, wmax=5, dmx=5)
2125 with pytest.raises(TypeError, match=r"compute_css_distance\‍(\‍) got unexpected keyword arguments 'stpes'.*, "
2126 r"'dexp' \‍(did you mean 'd_exp'\?\‍)"):
2127 dist_m4ri.compute_css_distance(TRY_X, TRY_Z, method=2, wmax=4, stpes=10, dexp=4)
2128 for func, kw in ((dist_m4ri.compute_classical_distance, dict(H=S5_H)),
2129 (dist_m4ri.compute_dem_distance, dict(dem=SURF_D3_DEM))):
2130 with pytest.raises(TypeError, match="unexpected keyword argument 'foo'"):
2131 func(foo=1, method=2, wmax=3, **kw)
2132 # the alias win (of kwin) is accepted
2133 assert dist_m4ri.compute_classical_distance(S5_H, method=2, wmax=3, threads=2, win=0) == 2
2134
2135
2137 try:
2138 ret = dist_m4ri.main([f"fdem={SURF_D3_DEM}", f"finH={S5_H}", "--simple", "method=2", "wmax=3", "threads=2",
2139 "--no-cache"])
2140 assert ret == 0
2141 captured = capsys.readouterr()
2142 assert f"# Warning: fdem={SURF_D3_DEM} given: ignoring finH=" in captured.err
2143 assert "# Warning: --simple: disabled since version 0.13.0, ignored" in captured.err
2144 assert captured.out.rstrip().endswith("3 3 0 (exact)")
2145
2146 ret = dist_m4ri.main([f"finH={S5_H}", f"finL={S5_L}", f"Lx={S5_L}", "pmin=0.01", "classical=1", "method=2",
2147 "wmax=5", "threads=2", "--no-cache"])
2148 assert ret == 0
2149 err = capsys.readouterr().err
2150 assert "# Warning: pmin=: only used with fdem= (a DEM or Stim circuit); ignored" in err
2151 assert "# Warning: Lx=: only used for a CSS code (Hx= and Hz=); ignored" in err
2152 assert "# Warning: classical=1 ignored: the input is a quantum code or a DEM" in err
2153
2154 prefix = os.path.join(EXAMPLES_DIR, "try")
2155 ret = dist_m4ri.main([f"fin={prefix}", f"finH={TRY_X}", "method=2", "wmax=4", "threads=2", "--no-cache"])
2156 assert ret == 0
2157 captured = capsys.readouterr()
2158 assert f"# Warning: fin={prefix} together with finH=: the explicit file names take precedence" in captured.err
2159 assert captured.out.rstrip().endswith("4 4 0 (exact)")
2160
2161 ret = dist_m4ri.main([f"Hx={TRY_X}", f"Hz={TRY_Z}", f"finH={S5_H}", "method=2", "wmax=4", "threads=2",
2162 "--no-cache"])
2163 assert ret == 0
2164 assert "# Warning: Hx=/Hz= given (CSS code): ignoring finH=" in capsys.readouterr().err
2165 finally:
2167
2168
2170 cache_file = str(tmp_path / "cli_cache.json")
2171 out = str(tmp_path / "cli_cws.nz")
2172 base = [f"finH={S5_H}", f"finL={S5_L}", "method=2", "wmax=5", "threads=2", f"cache={cache_file}"]
2174 try:
2175 assert dist_m4ri.main(base + [f"outC={out}"]) == 0
2177 assert len(cws) >= 2
2178 extra = sorted(set(cws[0]) ^ set(cws[1]))
2179 dist_m4ri._write_nzlist_file(out, cws + [extra])
2180 # a cache hit with finC identical to outC: the file is written once, keeping its codewords
2181 assert dist_m4ri.main(base + [f"finC={out}", f"outC={out}"]) == 0
2182 assert sorted(map(tuple, dist_m4ri.read_sparse_vectors(out))) == sorted(map(tuple, cws + [extra]))
2183 capsys.readouterr()
2184 finally:
2186
2187
2189 if not dist_m4ri._HAS_STIM:
2190 pytest.skip("stim is not installed")
2191 import stim
2192 circ = stim.Circuit.generated(
2193 "surface_code:rotated_memory_z", rounds=3, distance=3, after_clifford_depolarization=0.001,
2194 before_measure_flip_probability=0.001, after_reset_flip_probability=0.001,
2195 )
2196 cache_file = str(tmp_path / "dem_cache.json")
2199 kw = dict(method=2, threads=2, cache_file=cache_file)
2200 try:
2201 assert dist_m4ri.get_cached_distance(circuit=circ, cache_file=cache_file) is None
2202 assert dist_m4ri.compute_dem_distance(circuit=circ, dstop=2, **kw) == (2, [2, 0, 0])
2203 # get_cached_distance(circuit=...) finds the record of the processed DEM (with all detectors)
2204 assert dist_m4ri.get_cached_distance(circuit=circ, cache_file=cache_file)["d_info"] == [2, 0, 0]
2205 assert dist_m4ri.get_cached_distance(circuit=circ, rounds=4, cache_file=cache_file) is None
2206 # full= is disabled (ignored with a warning): the same record
2207 assert dist_m4ri.get_cached_distance(circuit=circ, full=True, cache_file=cache_file)["d_info"] == [2, 0, 0]
2208 assert "# Warning: full=: disabled since version 0.13.0" in capsys.readouterr().err
2209 # the exact distance from the cached lower bound; the cached dist is that of the merged bounds
2210 d, info, cws = dist_m4ri.compute_dem_distance(circuit=circ, wmax=3, do_cws=True, **kw)
2211 assert d == 3 and info == [3, 3, 0] and len(cws) > 0
2212 entry = dist_m4ri.get_cached_distance(circuit=circ, cache_file=cache_file)
2213 assert entry["dist"] == 3 and entry["d_info"] == [3, 3, 0] and len(entry["cws"]) == len(cws)
2214 stim_file = tmp_path / "c.stim"
2215 circ.to_file(str(stim_file))
2216 assert dist_m4ri.get_cached_distance(circuit=str(stim_file), cache_file=cache_file)["dist"] == 3
2217 finally:
2219
2220
2221def test_dem_merge_equal_columns(tmp_path, capsys):
2222 # the binary merges the equal columns of a DEM (merge=True, the default; exact): the codewords use the first one of
2223 # each set of equal columns (0-based columns 0=4, 1=3, and 5=6); merge=False (CLI: merge=0) keeps all columns
2224 dem_file = str(tmp_path / "dup.dem")
2225 with open(dem_file, "w") as f:
2226 f.write("error(0.1) D0 L0\nerror(0.1) D0 D1\nerror(0.1) D1\nerror(0.1) D0 D1\nerror(0.1) D0 L0\n"
2227 "error(0.1) D2\nerror(0.1) D2\n")
2229 try:
2230 kw = dict(dem=dem_file, method=2, wmax=3, do_cws=True, threads=1)
2231 assert dist_m4ri.compute_dem_distance(**kw) == (3, [3, 3, 0], [[0, 1, 2]])
2232 d, info, cws = dist_m4ri.compute_dem_distance(merge=False, **kw)
2233 assert (d, info) == (3, [3, 3, 0])
2234 assert sorted(map(tuple, cws)) == [(0, 1, 2), (0, 2, 3), (1, 2, 4), (2, 3, 4)]
2235 out = str(tmp_path / "cws.nz")
2236 assert dist_m4ri.main([f"fdem={dem_file}", "merge=0", "method=2", "wmax=3", "threads=1", f"outC={out}",
2237 "--no-cache"]) == 0
2238 assert len(dist_m4ri.read_sparse_vectors(out)) == 4
2239 capsys.readouterr()
2240 assert dist_m4ri.main([f"fdem={dem_file}", "merge=2", "method=2", "wmax=3", "--no-cache"]) == 1
2241 assert "invalid 'merge=2': merge should be 0 or 1" in capsys.readouterr().err
2242 assert dist_m4ri.main([f"finH={S5_H}", f"finL={S5_L}", "merge=0", "method=2", "wmax=5", "threads=2",
2243 "--no-cache"]) == 0
2244 assert "# Warning: merge=: only used with fdem= (a DEM or Stim circuit); ignored" in capsys.readouterr().err
2245 finally:
2247
2248
2249def test_dem_expand_equal_columns(tmp_path, capsys):
2250 # expand=True (Python only): each codeword of the merged list is listed for every choice among equal columns
2251 # (0-based columns 0=4, 1=3, and 5=6); the cache keeps the merged list; maxC limits the expanded list (warning)
2252 dem_file = str(tmp_path / "dup.dem")
2253 with open(dem_file, "w") as f:
2254 f.write("error(0.1) D0 L0\nerror(0.1) D0 D1\nerror(0.1) D1\nerror(0.1) D0 D1\nerror(0.1) D0 L0\n"
2255 "error(0.1) D2\nerror(0.1) D2\n")
2256 full = [[0, 1, 2], [0, 2, 3], [1, 2, 4], [2, 3, 4]]
2257 cache_file = str(tmp_path / "cache.json")
2258 kw = dict(dem=dem_file, method=2, wmax=3, do_cws=True, threads=1, cache_file=cache_file)
2261 try:
2262 assert dist_m4ri.compute_dem_distance(**kw) == (3, [3, 3, 0], [[0, 1, 2]])
2263 # from the cache (the merged list), expanded
2264 d, info, cws = dist_m4ri.compute_dem_distance(expand=True, **kw)
2265 assert (d, info) == (3, [3, 3, 0]) and sorted(cws) == full
2266 assert dist_m4ri.get_cached_distance(dem=dem_file, cache_file=cache_file)["cws"] == [[0, 1, 2]]
2267 capsys.readouterr()
2268 d, info, cws = dist_m4ri.compute_dem_distance(expand=True, maxC=2, **kw)
2269 assert len(cws) == 2 and all(cw in full for cw in cws)
2270 assert ("# Warning: expand: the expanded list has 4 codewords (1 up to equal columns); only the first maxC=2 "
2271 "are written (the list is incomplete)") in capsys.readouterr().err
2272 # a run of the binary (no cache), CLI
2273 out = str(tmp_path / "cws.nz")
2274 assert dist_m4ri.main([f"fdem={dem_file}", "expand=1", "method=2", "wmax=3", "threads=1", f"outC={out}",
2275 "--no-cache"]) == 0
2276 assert sorted(dist_m4ri.read_sparse_vectors(out)) == full
2277 capsys.readouterr()
2278 assert dist_m4ri.main([f"fdem={dem_file}", "expand=2", "method=2", "wmax=3", "--no-cache"]) == 1
2279 assert "invalid 'expand=2': expand should be 0 or 1" in capsys.readouterr().err
2280 assert dist_m4ri.main([f"fdem={dem_file}", "expand=1", "method=2", "wmax=3", "threads=1", "--no-cache"]) == 0
2281 assert "# Warning: expand=: only used with outC= or --cws; ignored" in capsys.readouterr().err
2282 finally:
2285
2286
2288 # the columns of a DEM as read by the binary: repeat blocks unrolled with shift_detectors, components summed mod 2,
2289 # and the errors with probability < pmin skipped
2290 dem_file = tmp_path / "r.dem"
2291 dem_file.write_text("error(0.1) D0 L0\nrepeat 2 {\n error(0.1) D0 D1\n shift_detectors 1\n}\n"
2292 "error(0.01) D5\nerror(0.1) D0 ^ D0 D2 # comment\ndetector(1, 2) D3\n")
2293 cols = dist_m4ri._dem_error_columns(str(dem_file))
2294 assert cols == [({0}, {0}), ({0, 1}, set()), ({1, 2}, set()), ({7}, set()), ({4}, set())]
2295 assert dist_m4ri._dem_error_columns(str(dem_file), pmin=0.05) == [cols[i] for i in (0, 1, 2, 4)]
2296 if dist_m4ri._HAS_STIM: # the same columns from the stim object (flattened)
2297 import stim
2298 assert dist_m4ri._dem_error_columns(stim.DetectorErrorModel(dem_file.read_text())) == cols
2299
2300
2302 if not dist_m4ri._HAS_STIM:
2303 pytest.skip("stim is not installed")
2304 import stim
2305 circ = stim.Circuit.generated("repetition_code:memory", rounds=2, distance=3, after_clifford_depolarization=0.01)
2306 kw = dict(circuit=circ, method=2, wmax=3, threads=1, outC=str(tmp_path / "cws.nz"), out_dir=str(tmp_path))
2308 try:
2309 assert dist_m4ri.compute_dem_distance(**kw)[0] == 3
2310 assert "the codewords index the error lines of the DEM generated from the Stim circuit in this run" in \
2311 capsys.readouterr().err
2312 assert dist_m4ri.compute_dem_distance(out_dem=str(tmp_path / "c.dem"), **kw)[0] == 3
2313 assert "the codewords index the error lines" not in capsys.readouterr().err
2314 finally:
2316
2317
2319 # drop=auto (the default): the binary finds the detectors which can be dropped exactly (all errors touching them
2320 # have twins), here the minority-basis detectors of a CSS circuit, with the distance and codewords of the full DEM;
2321 # drop=css: the minority-basis detectors of the classification of the circuit, checked by the binary
2322 if not dist_m4ri._HAS_STIM:
2323 pytest.skip("stim is not installed")
2324 import stim
2325 circ = stim.Circuit.generated(
2326 "surface_code:rotated_memory_z", rounds=3, distance=3, after_clifford_depolarization=0.001,
2327 before_measure_flip_probability=0.001, after_reset_flip_probability=0.001,
2328 )
2329 drop = []
2330 dem, _, _ = dist_m4ri._dem_from_input(None, circ, drop_out=drop)
2331 assert len(drop) == 8 and dem.num_detectors == 24 # the X detectors of rounds 2 and 3 (4 each)
2333 try:
2334 out_d = tmp_path / "d.txt"
2335 kw = dict(circuit=circ, method=2, wmax=3, threads=1, do_cws=True, outD=str(out_d))
2337 for mode in (None, "auto", 1, True, "css"):
2338 d, info, cws = dist_m4ri.compute_dem_distance(debug=1, drop=mode, **kw)
2339 out = capsys.readouterr().out
2340 assert (d, info) == (3, [3, 3, 0]) and len(cws) > 0
2341 assert re.search(r"dropped 8 of 24 detectors \‍(drop=.*\‍), exact: the \d+ columns touching them have twins "
2342 r"\‍(the same observables\‍)", out)
2343 # the detectors dropped (outD): those of the classification, also with drop=auto
2344 assert dist_m4ri.read_drop_file(out_d) == (drop, 24)
2345 label = "css" if mode == "css" else "auto"
2346 assert out_d.read_text().startswith(f"# 8 of 24 detectors dropped (drop={label}), 0-based\n")
2347 for cw in cws: # codewords of the full DEM
2348 dets, obs = set(), set()
2349 for j in cw:
2350 dets ^= cols[j][0]
2351 obs ^= cols[j][1]
2352 assert not dets and obs
2353 for mode in (False, 0, "0"):
2354 assert dist_m4ri.compute_dem_distance(drop=mode, debug=1, **kw)[:2] == (3, [3, 3, 0])
2355 assert "dropped" not in capsys.readouterr().out
2356 assert dist_m4ri.read_drop_file(out_d) == ([], 24)
2357 with pytest.raises(ValueError, match="no such file"):
2358 dist_m4ri.compute_dem_distance(circuit=circ, drop=str(tmp_path / "none.txt"))
2359 # a DEM file with the detectors to drop in a file: A != 0 (exact), and a column without a twin (a note)
2360 dem_file, drop_file = tmp_path / "a.dem", tmp_path / "drop.txt"
2361 drop_file.write_text("# the other basis\n2\n")
2362 dem_file.write_text("error(0.1) D0 L0\nerror(0.1) D0 D1\nerror(0.1) D1\nerror(0.1) D0 D2\nerror(0.1) D2 L0\n"
2363 "error(0.1) D1 D2 L0\n")
2364 kw = dict(dem=str(dem_file), drop=str(drop_file), method=2, wmax=3, threads=1)
2365 assert dist_m4ri.compute_dem_distance(**kw) == (3, [3, 3, 0])
2366 assert "# Note: dropping" not in capsys.readouterr().err
2367 with open(dem_file, "a") as f:
2368 f.write("error(0.1) D2 D3\n")
2369 assert dist_m4ri.compute_dem_distance(**kw) == (3, [3, 3, 0])
2370 assert (f"# Note: dropping the detectors in {drop_file} is not exact for this DEM (column 7 (1-based) "
2371 f"touches a dropped detector") in capsys.readouterr().err
2372 drop_file.write_text("9\n") # (not a detector of the DEM)
2373 assert dist_m4ri.compute_dem_distance(**kw) == (3, [3, 3, 0])
2374 assert f"# Note: none of the detectors in {drop_file} is in the DEM; nothing dropped" in capsys.readouterr().err
2375 # CLI: the values of drop, drop=css for a DEM file (nothing dropped), a missing drop file (an error), drop=,
2376 # outD=, and --out-reduced without fdem, and basis= without drop=css (warnings)
2377 for val, exp in (("1", True), ("auto", True), ("true", True), ("0", False), ("off", False), ("css", "css"),
2378 ("CSS", "css"), ("d.txt", "d.txt")):
2379 assert dist_m4ri.parse_cli_args([f"drop={val}"])["drop"] == exp
2380 assert dist_m4ri.main([f"fdem={dem_file}", "drop=css", "method=2", "wmax=3", "threads=1", "--no-cache"]) == 0
2381 assert "# Warning: drop=css: no minority-basis detectors" in capsys.readouterr().err
2382 assert dist_m4ri.compute_dem_distance(dem=str(dem_file), drop="css", method=2, wmax=3, threads=1,
2383 outD=str(out_d)) == (3, [3, 3, 0])
2384 assert "using drop=auto" in capsys.readouterr().err
2385 assert out_d.read_text().startswith("# ") and "(drop=auto" in out_d.read_text()
2386 assert dist_m4ri.main([f"fdem={dem_file}", f"drop={tmp_path / 'none.txt'}", "method=2", "wmax=3",
2387 "--no-cache"]) == 1
2388 assert "none.txt: no such file" in capsys.readouterr().err
2389 assert dist_m4ri.main([f"finH={S5_H}", f"finL={S5_L}", f"drop={drop_file}", f"outD={out_d}", "--out-reduced",
2390 "method=2", "wmax=5", "threads=2", "--no-cache"]) == 0
2391 assert ("# Warning: drop=, outD=, --out-reduced: only used with fdem= (a DEM or Stim circuit); ignored" in
2392 capsys.readouterr().err)
2393 stim_file = tmp_path / "surf_d3_Z.stim"
2394 circ.to_file(str(stim_file))
2395 for opts, warn in (([], True), (["drop=css"], False), (["-v"], False)):
2396 assert dist_m4ri.main([str(stim_file), "basis=Z", "method=2", "wmax=3", "threads=1", "--no-cache"] +
2397 opts) == 0
2398 assert ("# Warning: basis=: only used with drop=css" in capsys.readouterr().err) == warn
2399 finally:
2401
2402
2403def test_dem_out_reduced(tmp_path, capsys):
2404 # outD and out_reduced: the detectors dropped, and the reduced DEM with its maps to the DEM (also with a cached
2405 # result: the binary runs only for the detectors dropped)
2406 dem_file = tmp_path / "surf_d3_full.dem"
2407 shutil.copyfile(SURF_D3_DEM, dem_file)
2408 red_dem, red_json = tmp_path / "surf_d3_red.dem", tmp_path / "surf_d3_red.json"
2409 out_d = tmp_path / "d.txt"
2411 try:
2412 for run in range(2):
2413 for f in (red_dem, red_json, out_d):
2414 if f.exists():
2415 f.unlink()
2416 res = dist_m4ri.compute_dem_distance(dem=str(dem_file), method=2, wmax=3, threads=1, out_reduced=True,
2417 outD=str(out_d), cache_file=str(tmp_path / "cache.json"), verbose=True)
2418 assert res == (3, [3, 3, 0])
2419 out = capsys.readouterr().out
2420 assert ("Cached result: running the binary only for the detectors dropped (outD)" in out) == (run == 1)
2421 assert f"Saved the reduced DEM (8 detectors dropped) to '{red_dem}'" in out
2422 assert red_dem.exists() and red_json.exists()
2423 assert dist_m4ri.read_drop_file(out_d) == ([5, 7, 8, 10, 13, 15, 16, 18], 24)
2424 info = json.loads(red_json.read_text())
2425 assert (info["__version__"], info["source"], info["dem"], info["drop"]) == \
2426 (dist_m4ri.__version__, str(dem_file), str(dem_file), "auto")
2427 assert (info["num_detectors"], info["num_errors"], info["pmin"]) == (24, 221, 0.0)
2428 assert info["dropped"] == [5, 7, 8, 10, 13, 15, 16, 18]
2429 assert info["detectors"] == [r for r in range(24) if r not in info["dropped"]]
2430 cols = dist_m4ri._dem_error_columns(str(dem_file))
2431 red_cols = dist_m4ri._dem_error_columns(str(red_dem))
2432 assert len(red_cols) == len(info["errors"]) == 55
2433 for (S, O), j in zip(red_cols, info["errors"]): # the maps of the detectors and errors
2434 assert {info["detectors"][r] for r in S} == cols[j][0] and O == cols[j][1]
2435 assert all(not (S & set(info["dropped"])) for S, _ in (cols[j] for j in info["errors"]))
2436 # the reduced DEM has the same distance, and its codewords map to codewords of the DEM
2438 d, d_info, cws = dist_m4ri.compute_dem_distance(dem=str(red_dem), method=2, wmax=3, threads=1, drop=0,
2439 do_cws=True)
2440 assert (d, d_info, len(cws)) == (3, [3, 3, 0], 21)
2441 for cw in cws:
2442 dets, obs = set(), set()
2443 for i in cw:
2444 dets ^= cols[info["errors"][i]][0]
2445 obs ^= cols[info["errors"][i]][1]
2446 assert not dets and obs
2447 # CLI: --out-reduced PREFIX (a path), and write_reduced_dem() keeps the number of detectors and observables
2448 assert dist_m4ri.main([f"fdem={dem_file}", "--out-reduced", str(tmp_path / "x" / "pfx"), "method=2", "wmax=3",
2449 "threads=1", "--no-cache"]) == 0
2450 assert (tmp_path / "x" / "pfx_red.dem").read_text().splitlines()[1:] == red_dem.read_text().splitlines()[1:]
2451 args = dist_m4ri.parse_cli_args(["--out-reduced", str(dem_file)]) # (an existing input file, not a prefix)
2452 assert (args["out_reduced"], args["fdem"]) == (True, str(dem_file))
2453 assert dist_m4ri.parse_cli_args(["out_reduced=0", "outD=y.txt"])["out_reduced"] is None
2454 small = tmp_path / "s.dem"
2455 small.write_text("error(0.1) D0 L0\nerror(0.2) D0 D1\nerror(0.3) D2 L1\n")
2456 dem_s, json_s = dist_m4ri.write_reduced_dem(str(small), [1, 2], str(tmp_path / "s"), num_detectors=4)
2457 assert Path(dem_s).read_text().splitlines()[1:] == ["error(0.1) D0 L0", "detector D1", "logical_observable L1"]
2458 assert json.loads(Path(json_s).read_text())["detectors"] == [0, 3]
2459 finally:
2462
2463
2465 # p_noise: the strength of the noise added to a noiseless circuit (0: none); force_noise: the noise of the circuit
2466 # is replaced (p_noise=0: removed), e.g., the same noise model for circuits with other noise
2467 if not dist_m4ri._HAS_STIM:
2468 pytest.skip("stim is not installed")
2469 import stim
2470 c0 = stim.Circuit.generated("surface_code:rotated_memory_z", rounds=3, distance=3)
2471 cn = stim.Circuit.generated("surface_code:rotated_memory_z", rounds=3, distance=3,
2472 after_clifford_depolarization=0.002, before_measure_flip_probability=0.003)
2473 noisy_file, cache = tmp_path / "surf_Z.stim", str(tmp_path / "cache.json")
2474 cn.to_file(str(noisy_file))
2475 kw = dict(method=2, wmax=3, threads=1)
2477 try:
2478 # the same DEM (and cache record) as for the noiseless circuit with the noise added
2479 assert dist_m4ri.compute_dem_distance(circuit=cn, force_noise=True, out_dem=str(tmp_path / "f.dem"),
2480 cache_file=cache, **kw) == (3, [3, 3, 0])
2481 assert dist_m4ri.compute_dem_distance(circuit=c0, out_dem=str(tmp_path / "a.dem"), **kw) == (3, [3, 3, 0])
2482 assert (tmp_path / "f.dem").read_text() == (tmp_path / "a.dem").read_text()
2483 assert dist_m4ri.get_cached_distance(circuit=c0, cache_file=cache)["dmin"] == 3
2484 assert dist_m4ri.get_cached_distance(circuit=cn, force_noise=True, cache_file=cache)["dmin"] == 3
2485 assert dist_m4ri.get_cached_distance(circuit=cn, cache_file=cache) is None # (its own noise: another DEM)
2486 # p_noise=0: no noise added (no distance for a noiseless circuit); with force_noise, all noise is removed
2488 assert dist_m4ri.compute_dem_distance(circuit=c0, p_noise=0, **kw) == (0, [0, 0, 0])
2489 assert "the DEM has no error mechanisms" in capsys.readouterr().err
2490 stripped = tmp_path / "s.stim"
2491 assert dist_m4ri.compute_dem_distance(circuit=cn, p_noise=0, force_noise=True, out_stim=str(stripped),
2492 **kw) == (0, [0, 0, 0])
2493 assert stim.Circuit.from_file(str(stripped)) == c0
2494 assert dist_m4ri.compute_dem_distance(circuit=cn, p_noise=0, **kw) == (3, [3, 3, 0]) # (its own noise)
2495 with pytest.raises(ValueError, match="noise strength"):
2496 dist_m4ri.compute_dem_distance(circuit=c0, p_noise=0.7)
2497 # CLI: --p-noise P (p_noise=P), --force-noise (--force, force_noise=1); for a DEM file: ignored (a warning)
2498 for argv, exp in ((["--p-noise", "0.002"], (0.002, False)), (["p_noise=0"], (0.0, False)),
2499 (["--force-noise"], (None, True)), (["--force"], (None, True)),
2500 (["force_noise=1", "pnoise=0.01"], (0.01, True))):
2501 args = dist_m4ri.parse_cli_args(argv + [str(noisy_file)])
2502 assert (args["p_noise"], args["force_noise"], args["fdem"]) == exp + (str(noisy_file),)
2503 assert dist_m4ri.main([str(noisy_file), "--force-noise", "--p-noise", "0", "--out-stim", str(stripped),
2504 "--no-cache"]) == 0
2505 out = capsys.readouterr()
2506 assert out.out.strip() == "0 0 0" and "no error mechanisms" in out.err
2507 assert dist_m4ri.main([str(noisy_file), "--force", "method=2", "wmax=3", "threads=1", "--no-cache"]) == 0
2508 assert capsys.readouterr().out.strip() == "3 3 0 (exact)"
2509 assert dist_m4ri.main([str(noisy_file), "p_noise=0.7", "--no-cache"]) == 1
2510 assert "the noise strength must be in [0, 0.5]" in capsys.readouterr().err
2511 assert dist_m4ri.main([f"fdem={SURF_D3_DEM}", "--force-noise", "p_noise=0.01", "method=2", "wmax=3",
2512 "threads=1", "--no-cache"]) == 0
2513 assert ("# Warning: --p-noise, --force-noise: only used for a Stim circuit (.stim); ignored" in
2514 capsys.readouterr().err)
2515 finally:
2518
2519
2521 # an error channel with disjoint cases which stim cannot write as independent errors (here: PAULI_CHANNEL_1 with X
2522 # and Z, but no Y) needs approximate_disjoint_errors=True in stim
2523 if not dist_m4ri._HAS_STIM:
2524 pytest.skip("stim is not installed")
2525 import stim
2526 circ = stim.Circuit.generated("surface_code:rotated_memory_z", rounds=3, distance=3,
2527 before_round_data_depolarization=0.001, before_measure_flip_probability=0.001)
2528 circ = stim.Circuit(str(circ).replace("DEPOLARIZE1(0.001)", "PAULI_CHANNEL_1(0.0005, 0, 0.0005)"))
2529 assert "PAULI_CHANNEL_1" in str(circ) and dist_m4ri.has_noise(circ)
2530 with pytest.raises(ValueError):
2531 circ.detector_error_model(decompose_errors=False) # (without the approximation)
2532 dem, decomp, approx = dist_m4ri._circuit_dem(circ)
2533 assert approx is True and dem.num_errors > 0
2535 try:
2536 assert dist_m4ri.compute_dem_distance(circuit=circ, method=2, wmax=3, threads=2) == (3, [3, 3, 0])
2537 stim_file = tmp_path / "pc.stim"
2538 circ.to_file(str(stim_file))
2539 assert dist_m4ri.main([str(stim_file), "method=2", "wmax=3", "threads=2", "--no-cache", "-v"]) == 0
2540 out = capsys.readouterr().out
2541 assert "approximate_disjoint_errors=True" in out and "3 3 0 (exact)" in out
2542 # a circuit without such channels is converted as before
2543 assert dist_m4ri._circuit_dem(stim.Circuit.generated("surface_code:rotated_memory_z", rounds=2, distance=3,
2544 after_clifford_depolarization=0.001))[2] is False
2545 finally:
2547
2548
2549def _surface_memory_z(x_in_h_frame: bool, meas_before_h: bool, rounds: int = 3):
2550 """
2551 Memory-Z circuit of the d=3 rotated surface code (data 0..8, Z ancillas 9..12, X ancillas 13..16). With
2552 x_in_h_frame, the X checks are measured as Z checks in the Hadamard frame of the data qubits (H on all data, CX
2553 data -> ancilla, H on all data), with the X ancillas measured before or after the closing H gates.
2554 """
2555 import stim
2556 x_checks = [[1, 2, 4, 5], [3, 4, 6, 7], [0, 1], [7, 8]]
2557 z_checks = [[0, 1, 3, 4], [4, 5, 7, 8], [3, 6], [2, 5]]
2558 data, za, xa = list(range(9)), [9, 10, 11, 12], [13, 14, 15, 16]
2559
2560 def line(name, qubits):
2561 return f"{name} " + " ".join(map(str, qubits)) + "\n"
2562
2563 def body():
2564 s = "".join(line("CX", [x for q in row for x in (q, a)]) for a, row in zip(za, z_checks))
2565 if x_in_h_frame:
2566 s += line("MR", za) + line("H", data)
2567 s += "".join(line("CX", [x for q in row for x in (q, a)]) for a, row in zip(xa, x_checks))
2568 s += (line("MR", xa) + line("H", data)) if meas_before_h else (line("H", data) + line("MR", xa))
2569 else:
2570 s += line("H", xa)
2571 s += "".join(line("CX", [x for q in row for x in (a, q)]) for a, row in zip(xa, x_checks))
2572 s += line("H", xa) + line("MR", za + xa)
2573 return s
2574
2575 c = line("R", data + za + xa) + body() + "".join(f"DETECTOR rec[-{k}]\n" for k in (8, 7, 6, 5))
2576 c += f"REPEAT {rounds - 1} {{\n" + body()
2577 c += "".join(f"DETECTOR rec[-{k}] rec[-{k + 8}]\n" for k in range(8, 0, -1)) + "}\n"
2578 c += line("M", data) # data 0..8: rec[-9]..rec[-1]; the last Z-ancilla results: rec[-17]..rec[-14]
2579 for a_rec, row in zip((17, 16, 15, 14), z_checks):
2580 c += "DETECTOR " + " ".join(f"rec[-{9 - q}]" for q in row) + f" rec[-{a_rec}]\n"
2581 c += "OBSERVABLE_INCLUDE(0) rec[-9] rec[-8] rec[-7]\n" # logical Z on the data qubits 0, 1, 2
2582 return stim.Circuit(c)
2583
2584
2586 # E.2.10: single-qubit gates on data qubits act on the Paulis of all checks, including those already measured,
2587 # so that all checks are expressed in the same frame
2588 if not dist_m4ri._HAS_STIM:
2589 pytest.skip("stim is not installed")
2590 for x_in_h_frame, meas_before_h in ((False, False), (True, False), (True, True)):
2591 circ = _surface_memory_z(x_in_h_frame, meas_before_h)
2592 circ.detector_error_model() # deterministic detectors and observable
2594 assert t_res["is_css"] is True and t_res["is_rotated_css"] is False and t_res["basis"] == "Z"
2595 assert t_res["data_qubits"] == list(range(9))
2596 assert t_res["z_ancillas"] == [9, 10, 11, 12] and t_res["x_ancillas"] == [13, 14, 15, 16]
2597 _, stripped, kept = dist_m4ri.strip_minority_detectors(circ, "Z", thorough_res=t_res)
2598 assert (stripped, kept) == (8, 16)
2599
2600
2602 # the "Model check" compares the observed hits with the uniform model: only if the binary counted the hits
2603 # (min_hits > 0); with min_hits=0, every codeword has a single hit, and no model check is printed
2604 stats = {"rw_n": 40, "rw_rank": 20, "rw_steps_uniform": 1000, "rw_steps": 1000, "min_w": 6, "hits_avg": 1.0}
2605 assert "Model check" not in dist_m4ri.explain_bounds([1, 6, 1000], method=1, stats=stats)
2606 assert "Model check" in dist_m4ri.explain_bounds([1, 6, 1000], method=1, stats=dict(stats, min_hits=5))
2607
2608
2610 # refresh=0 is passed to the binary (no basis refresh of the experimental ksub RW, whose default is 5000), and the
2611 # default refresh=None passes nothing
2612 H = os.path.join(EXAMPLES_DIR, "c96H.mmx")
2613 for refresh in (0, None):
2614 dist_m4ri.run_dist_m4ri(method=1, finH=H, steps=10, threads=1, refresh=refresh,
2615 debug=dist_m4ri.PY_DBG_COMMANDS)
2616 assert (" refresh=0" in capsys.readouterr().out) == (refresh is not None)
2617
2618
2620 # array-likes such as lists of rows are accepted by compute_css_distance() (the Steane code [[7, 1, 3]])
2621 H = [[1, 0, 0, 1, 1, 0, 1], [0, 1, 0, 1, 0, 1, 1], [0, 0, 1, 0, 1, 1, 1]]
2622 assert dist_m4ri.compute_css_distance(Hx=H, Hz=H, num_steps=100)[0] == 3
2623 # G and L together are rejected (the binary accepts only one of them)
2624 with pytest.raises(ValueError, match="not both"):
2625 dist_m4ri.compute_quantum_distance(H, G=H, L=[[1] * 7])
2626
2627
2629 # kwin=-1 (any negative value): no windows, uniform random permutations in every RW step; passed to the binary
2630 H = os.path.join(EXAMPLES_DIR, "c96H.mmx")
2631 dist_m4ri.run_dist_m4ri(method=1, finH=H, steps=10, threads=1, kwin=-1, debug=dist_m4ri.PY_DBG_COMMANDS)
2632 assert " kwin=-1" in capsys.readouterr().out
2633 use_cache = dist_m4ri._use_distance_cache
2635 try:
2636 dist_m4ri.compute_classical_distance(H, method=1, num_steps=10, kwin=-1, debug=dist_m4ri.PY_DBG_COMMANDS)
2637 assert " kwin=-1" in capsys.readouterr().out
2638 finally:
2639 if use_cache:
2641
2642
2644 # the defaults of _CODEDISTANCE_UNUSED are those of the compute_*() functions
2645 import inspect
2646 for func in (dist_m4ri.compute_classical_distance, dist_m4ri.compute_quantum_distance,
2647 dist_m4ri.compute_css_distance, dist_m4ri.compute_dem_distance):
2648 params = inspect.signature(func).parameters
2649 for name, default in dist_m4ri._CODEDISTANCE_UNUSED.items():
2650 if name in params:
2651 assert params[name].default == default, (func.__name__, name)
2652
2653
2654def test_ignored_inputs_warnings(tmp_path, capsys, monkeypatch):
2655 # the inputs which are ignored give a warning on stderr, unless --silent (in Python: set_silent())
2656 other = tmp_path / "notes.txt"
2657 other.write_text("x")
2658 args = dist_m4ri.parse_cli_args(["fdem=c.stim", "--model-lb", "kwin=5", "min_hits=0", "css=1", str(other)])
2660 err = capsys.readouterr().err
2661 assert "kwin=, min_hits=: not used with --model-lb; ignored" in err
2662 assert "css=1: not used by the Python interface" in err and f"{other}: a positional file" in err
2663 H = os.path.join(EXAMPLES_DIR, "c96H.mmx")
2664 try:
2665 for silent in (False, True):
2666 argv = [f"finH={H}", "css=1", "method=1", "steps=10", "--no-cache"] + (["--silent"] if silent else [])
2667 assert dist_m4ri.main(argv) == 0
2668 assert ("css=1" in capsys.readouterr().err) == (not silent)
2669 finally:
2671 # solver="codedistance" does not use the options of the binary (the solver itself is not run here)
2672 def no_codedistance():
2673 raise RuntimeError("codedistance not run")
2674 monkeypatch.setattr(dist_m4ri, "_get_codedistance", no_codedistance)
2675 with pytest.raises(RuntimeError, match="not run"):
2676 dist_m4ri.compute_classical_distance(H, solver="codedistance", threads=4, kwin=-1)
2677 assert "threads=, kwin=: not used with solver='codedistance'; ignored" in capsys.readouterr().err
2679 try:
2680 with pytest.raises(RuntimeError, match="not run"):
2681 dist_m4ri.compute_classical_distance(H, solver="codedistance", threads=4)
2682 assert "not used" not in capsys.readouterr().err
2683 finally:
2685
2686
2688 if not dist_m4ri._HAS_STIM:
2689 pytest.skip("stim is not installed")
2690 import stim
2691 circ = stim.Circuit.generated(
2692 "surface_code:rotated_memory_z", rounds=3, distance=3,
2693 after_clifford_depolarization=0.001, before_measure_flip_probability=0.001
2694 ).flattened()
2695 non_det = stim.Circuit()
2696 det_obs = stim.Circuit()
2697 meas_so_far = 0
2698 total_meas = circ.num_measurements
2699 for inst in circ:
2700 if inst.name == "DETECTOR":
2701 shifted = [stim.target_rec((meas_so_far + t.value) - total_meas)
2702 for t in inst.targets_copy() if t.is_measurement_record_target]
2703 det_obs.append("DETECTOR", shifted, inst.gate_args_copy())
2704 elif inst.name == "OBSERVABLE_INCLUDE":
2705 shifted = [stim.target_rec((meas_so_far + t.value) - total_meas)
2706 for t in inst.targets_copy() if t.is_measurement_record_target]
2707 det_obs.append("OBSERVABLE_INCLUDE", shifted, inst.gate_args_copy())
2708 else:
2709 if stim.gate_data(inst.name).produces_measurements:
2710 meas_so_far += len(inst.target_groups())
2711 non_det.append(inst)
2712 circ_end_dets = non_det + det_obs
2713 g1, f1, nf1 = dist_m4ri._model_detector_groups(circ)
2714 g2, f2, nf2 = dist_m4ri._model_detector_groups(circ_end_dets)
2715 assert (g1, f1, nf1) == (g2, f2, nf2)
2716 assert len(f2) == 8 and sum(f2) == 4 and nf2 == 9
2717
2719 try:
2720 b_end, info_end = dist_m4ri.compute_model_lower_bound(circ_end_dets, threads=2, return_info=True)
2721 assert b_end == 3 and info_end["groups"] == 8 and info_end["groups_final"] == 4
2722 with open(SURF_D3_DEM) as f:
2723 raw_lines = [
2724 ln for ln in f.read().splitlines()
2725 if not ln.strip().startswith(("detector", "shift_detectors"))
2726 ]
2727 nocoord_dem = tmp_path / "surf_d3_nocoord.dem"
2728 nocoord_dem.write_text("\n".join(raw_lines) + "\n")
2729 b_nc, info_nc = dist_m4ri.compute_model_lower_bound(str(nocoord_dem), threads=2, return_info=True)
2730 assert b_nc == 3 and info_nc["groups"] == 8 and info_nc["groups_final"] == 4
2731 finally:
2733
2734
2736 # 1. Classical even-weight code: Extended Hamming [8, 4, 4] with 1_8 in rowspan(H)
2737 H_even = np.array([
2738 [1, 0, 0, 1, 1, 0, 1, 0],
2739 [0, 1, 0, 1, 0, 1, 1, 0],
2740 [0, 0, 1, 0, 1, 1, 1, 0],
2741 [1, 1, 1, 1, 1, 1, 1, 1],
2742 ], dtype=np.int8)
2743 # 2. Quantum code (H_q, L_q) with a (0,0) column at index 0 AND 1_n in rowspan(H_q)
2744 H_q = np.pad(H_even, ((0, 0), (1, 0)), constant_values=0)
2745 L_q = np.array([[0, 1, 1, 0, 1, 0, 0, 1, 0]], dtype=np.int8)
2747 try:
2748 d, d_info, cws = dist_m4ri.compute_classical_distance(
2749 H_even, method=2, wmax=4, threads=2, do_cws=True, return_info=True
2750 )
2751 assert d == 4 and d_info == [4, 4, 0] and len(cws) == 14
2752 # Sub-distance wmax=2 on even code (d=4) certifies w=2 and steps dmin to w+2=4 (not w+4)
2753 d_lo, d_info_lo = dist_m4ri.compute_classical_distance(
2754 H_even, method=2, wmax=2, threads=2, engine="dist_m4ri", return_info=True
2755 )
2756 assert abs(d_lo) == 4 and d_info_lo == [4, 0, 0]
2757 d_s, d_info_s, cws_s = dist_m4ri.compute_classical_distance(
2758 H_even, method=2, wmax=4, smax=2, threads=2, do_cws=True, return_info=True
2759 )
2760 assert d_s == 4 and d_info_s == [4, 4, 0] and sorted(cws_s) == sorted(cws)
2761
2762 dq, dq_info, cws_q = dist_m4ri.compute_quantum_distance(
2763 H_q, L=L_q, method=2, wmax=4, threads=2, do_cws=True, return_info=True
2764 )
2765 dq_s, dq_info_s, cws_qs = dist_m4ri.compute_quantum_distance(
2766 H_q, L=L_q, method=2, wmax=4, smax=2, threads=2, do_cws=True, return_info=True
2767 )
2768 assert dq == 4 and dq_info == [4, 4, 0] and dq_s == 4 and dq_info_s == [4, 4, 0]
2769 assert sorted(cws_q) == sorted(cws_qs) and all(0 not in cw for cw in cws_q)
2770
2771 # 3. DEM with both a (0,0) column and a (0,1) weight-1 logical column (zero detectors, nonzero observable):
2772 # strip_zero_hl must NOT strip the (0,1) column (distance must be 1, not 2).
2773 dem_w1 = "error(0.1) D0 ^ D0\nerror(0.1) L0\nerror(0.1) D0 L0\nerror(0.1) D0\n"
2774 d_dem, info_dem = dist_m4ri.compute_dem_distance(
2775 dem_w1, method=2, wmax=2, threads=1, engine="dist_m4ri"
2776 )
2777 assert d_dem == 1 and info_dem[1] == 1
2778 finally:
2780
2781
2783 from types import SimpleNamespace
2784 cache_file = str(tmp_path / "cert_cache.json")
2787 try:
2788 # 1. Exact verified unconditional certificate writes exact distance to cache
2789 fake_exact = SimpleNamespace(
2790 status="exact", distance=5, lower_bound=5, upper_bound=5,
2791 witness_verified=True, lb_conditional=False, ub_conditional=False, method_used="cc"
2792 )
2793 fake_pipeline = SimpleNamespace(
2794 certify=lambda *a, **kw: fake_exact,
2795 certify_dem=lambda *a, **kw: fake_exact,
2796 certify_circuit=lambda *a, **kw: fake_exact,
2797 )
2798 fake_io = SimpleNamespace(
2799 DistanceProblem=lambda *a, **kw: SimpleNamespace(args=a, **kw),
2800 read_mtx=lambda p: None,
2801 binary_csr=lambda m: m,
2802 )
2803 monkeypatch.setattr(dist_m4ri, "_get_qec_cert", lambda: (None, fake_io, fake_pipeline))
2804 monkeypatch.setattr(dist_m4ri, "_has_qec_cert", lambda: True)
2806 S5_H, L=S5_L, engine="cert", return_info=True, cache_file=cache_file
2807 )
2808 assert d == 5 and info == [5, 5, 0]
2809 cached = dist_m4ri.get_cached_distance(H=S5_H, L=S5_L, cache_file=cache_file)
2810 assert cached["dmin"] == 5 and cached["dmax"] == 5
2811
2812 # 2. Unverified upper bound (witness_verified=False) or conditional LB (lb_conditional=True) is NOT cached
2813 dist_m4ri.clear_distance_cache(cache_file, clear_file=True)
2814 fake_unverified = SimpleNamespace(
2815 status="exact", distance=5, lower_bound=5, upper_bound=5,
2816 witness_verified=False, lb_conditional=True, ub_conditional=False, method_used="start_list"
2817 )
2818 fake_pipeline.certify = lambda *a, **kw: fake_unverified
2820 S5_H, L=S5_L, engine="cert", return_info=True, cache_file=cache_file
2821 )
2822 assert d2 == 0 and info2 == [0, 0, 0]
2823 assert dist_m4ri.get_cached_distance(H=S5_H, L=S5_L, cache_file=cache_file) is None
2824
2825 # 2b. Unverified caller dmin > 1 must NEVER pollute _distance_cache when cert returns a smaller c_lb
2826 fake_partial = SimpleNamespace(
2827 status="timeout", distance=None, lower_bound=2, upper_bound=5,
2828 witness_verified=True, lb_conditional=False, ub_conditional=False, method_used="cc"
2829 )
2830 fake_pipeline.certify = lambda *a, **kw: fake_partial
2832 S5_H, L=S5_L, dmin=4, engine="cert", return_info=True, cache_file=cache_file
2833 )
2834 assert d2b == 5 and info2b == [4, 5, 0]
2835 cached_2b = dist_m4ri.get_cached_distance(H=S5_H, L=S5_L, cache_file=cache_file)
2836 assert cached_2b["dmin"] == 2 and cached_2b["dmax"] == 5
2837
2838 # 2c. D1-OBS-1 regression: when cache already holds a certified dmin (p_dmin=3 from wmax=2),
2839 # a subsequent wmax=5 run passes wmin=3 to qec_cert, which marks lb_conditional=True with
2840 # lb_assumed_wmin=3. Because lb_assumed_wmin <= p_dmin and no trust flags or other conditional
2841 # stages exist, the new lower bound (5) is discharged and cached as exact [5, 5, 0].
2842 dist_m4ri.clear_distance_cache(cache_file, clear_file=True)
2843 fake_step1 = SimpleNamespace(
2844 status="bounds", distance=None, lower_bound=3, upper_bound=5,
2845 witness_verified=True, lb_conditional=False, ub_conditional=False, method_used="cc"
2846 )
2847 fake_pipeline.certify = lambda *a, **kw: fake_step1
2848 d_s1, info_s1 = dist_m4ri.compute_quantum_distance(
2849 S5_H, L=S5_L, wmax=2, engine="cert", return_info=True, cache_file=cache_file
2850 )
2851 assert d_s1 == 5 and info_s1 == [3, 5, 0]
2852 cached_s1 = dist_m4ri.get_cached_distance(H=S5_H, L=S5_L, cache_file=cache_file)
2853 assert cached_s1["dmin"] == 3 and cached_s1["dmax"] == 5
2854
2855 # If a certificate with p_dmin=3 also has trust_sym=True (or unverified symmetry), keep rejecting LB
2856 fake_trusted_sym = SimpleNamespace(
2857 status="bounds", distance=None, lower_bound=5, upper_bound=5,
2858 witness_verified=True, lb_conditional=True, lb_assumed_wmin=3,
2859 instance={"lb_conditional": True, "lb_assumed_wmin": 3, "trust_sym": True},
2860 stages=[{"name": "search", "lb_conditional": True, "sym_kind": "orbit", "sym_verified": False}],
2861 reductions=[],
2862 )
2863 fake_pipeline.certify = lambda *a, **kw: fake_trusted_sym
2864 d_ts, info_ts = dist_m4ri.compute_quantum_distance(
2865 S5_H, L=S5_L, wmax=5, engine="cert", return_info=True, cache_file=cache_file
2866 )
2867 assert d_ts == 5 and info_ts == [3, 5, 0]
2868 assert dist_m4ri.get_cached_distance(H=S5_H, L=S5_L, cache_file=cache_file)["dmin"] == 3
2869
2870 # When the ONLY conditional reason is lb_assumed_wmin=3 <= p_dmin=3, accept LB=5 -> exact [5, 5, 0]
2871 seen_wmin = []
2872
2873 def _fake_step2(*a, **kw):
2874 w_in = int(kw.get("wmin", 1))
2875 seen_wmin.append(w_in)
2876 return SimpleNamespace(
2877 status="bounds", distance=None, lower_bound=5, upper_bound=5,
2878 witness_verified=True, lb_conditional=True, lb_assumed_wmin=w_in,
2879 instance={"lb_conditional": True, "lb_assumed_wmin": w_in},
2880 stages=[{"name": "search", "lb_conditional": True, "sym_kind": "orbit", "sym_verified": 2}],
2881 reductions=[{
2882 "name": "cc_mitm_search",
2883 "params": {
2884 "wmin": w_in, "wmax": 4, "sym_kind": "orbit",
2885 "sym_verified": 2, "lb_conditional": True,
2886 },
2887 }],
2888 )
2889
2890 fake_pipeline.certify = _fake_step2
2891 d_s2, info_s2 = dist_m4ri.compute_quantum_distance(
2892 S5_H, L=S5_L, wmax=5, engine="cert", return_info=True, cache_file=cache_file
2893 )
2894 assert seen_wmin == [3]
2895 assert d_s2 == 5 and info_s2 == [5, 5, 0]
2896 cached_s2 = dist_m4ri.get_cached_distance(H=S5_H, L=S5_L, cache_file=cache_file)
2897 assert cached_s2["dmin"] == 5 and cached_s2["dmax"] == 5
2898
2899 # 3. engine='auto' delegates distance-only calls, but falls back to dist_m4ri when do_cws=True
2900 fake_pipeline.certify = lambda *a, **kw: fake_exact
2902 S5_H, L=S5_L, engine="auto", return_info=True, cache_file=cache_file
2903 )
2904 assert d3 == 5 and info3 == [5, 5, 0]
2905 dist_m4ri.clear_distance_cache(cache_file, clear_file=True)
2906 d4, info4, cws4 = dist_m4ri.compute_classical_distance(
2907 S5_H, method=2, wmax=2, threads=2, engine="auto", do_cws=True, return_info=True, cache_file=cache_file
2908 )
2909 assert d4 == 2 and info4 == [2, 2, 0] and len(cws4) > 0
2910 with pytest.raises(ValueError, match="Codeword extraction is not supported with engine='cert'"):
2911 dist_m4ri.compute_classical_distance(S5_H, engine="cert", do_cws=True)
2912 finally:
2914
2915
2917 # Hamming [7, 4, 3] parity-check matrix (rank 3, ker(H) has classical distance 3)
2918 H = np.array([
2919 [1, 0, 1, 0, 1, 0, 1],
2920 [0, 1, 1, 0, 0, 1, 1],
2921 [0, 0, 0, 1, 1, 1, 1],
2922 ], dtype=np.uint8)
2923 G_zero = np.zeros((2, 7), dtype=np.uint8)
2924
2927 try:
2928 d_cl, info_cl, cws_cl = dist_m4ri.compute_classical_distance(
2929 H, method=2, wmax=3, threads=2, do_cws=True, return_info=True
2930 )
2931 for m in (1, 2, 3):
2933 H, G=G_zero, method=m, num_steps=200, wmax=3, threads=2, return_info=True
2934 )
2935 assert dq == d_cl == 3
2936 assert info_q[1] == info_cl[1] == 3
2937 dq2, info_q2, cws_q2 = dist_m4ri.compute_quantum_distance(
2938 H, G=G_zero, method=2, wmax=3, threads=2, do_cws=True, return_info=True
2939 )
2940 assert dq2 == d_cl and info_q2 == info_cl and sorted(cws_q2) == sorted(cws_cl)
2941
2942 # CSS with all-zero Hz: Z-sector (ker(Hx) \ rowspan(Hz)) equals classical distance of Hx; X-sector has d=1
2943 d_css1, dx1, dz1 = dist_m4ri.compute_css_distance(H, G_zero, method=2, wmax=3, threads=2)
2944 assert dz1 == info_cl == [3, 3, 0]
2945 assert dx1[1] == 1 and d_css1 == 1
2946
2947 # CSS with all-zero Hx: X-sector (ker(Hz) \ rowspan(Hx)) equals classical distance of Hz; Z-sector has d=1
2948 d_css2, dx2, dz2 = dist_m4ri.compute_css_distance(G_zero, H, method=2, wmax=3, threads=2)
2949 assert dx2 == info_cl == [3, 3, 0]
2950 assert dz2[1] == 1 and d_css2 == 1
2951 finally:
2953
2954
2955if __name__ == "__main__":
2956 pytest.main([__file__, "-v"])
2957
2958
2959
str find_dist_m4ri_binary(Optional[str] custom_path=None)
Definition dist_m4ri.py:887
int main(Optional[List[str]] argv=None)
str format_bounds_str(List[int] bounds)
Definition dist_m4ri.py:284
List[int] format_bounds_list(int dmin, int dmax, int num_rw)
Definition dist_m4ri.py:263
int parse_debug_value(Union[str, int] val)
Definition dist_m4ri.py:49
Tuple[List[int], Optional[int]] read_drop_file(Union[str, Path] path)
Any strip_noise(Any circuit)
Dict[str, Any] get_distance_cache()
Definition dist_m4ri.py:257
None _warn_cli_conflicts(Dict[str, Any] args)
Tuple[List[int], List[bool], int] _model_detector_groups(Any circuit)
str _matrix_to_file(matrix, str extension=".mtx", str temp_dir="tmp")
None _write_nzlist_file(str filepath, List[List[int]] cws)
Dict[str, Any] parse_cli_args(List[str] argv)
Optional[Dict[str, Any]] rw_infoset_estimate(int n, int rank, int steps, int d, int dmin=1, float step_time=0.0, float target=0.01, int steps_total=0, int pairs_l=0)
Definition dist_m4ri.py:330
Any add_noise(Any circuit, float p=0.001, bool force=False)
List[Tuple[Any,...]] _dem_error_columns(Any dem, float pmin=0.0, bool with_prob=False)
Tuple[Any,...] compute_dem_distance(Optional[Any] dem=None, Optional[Any] circuit=None, Optional[str] dist_m4ri=None, int method=3, Optional[int] threads=None, float timeout=60.0, Optional[int] num_steps=None, int d_exp=0, int d_min=0, int d_max=0, int dmin=0, int dmax=0, int dstop=0, int wmin=1, int wmax=0, Optional[int] smax=None, Optional[Union[int, str, Sequence[int]]] start=None, Optional[int] cbeg=None, Optional[int] cend=None, int noscan=0, int dW=-1, int maxC=0, float pmin=0.0, bool merge=True, bool expand=False, Optional[Union[bool, int, str, Path]] drop=None, Optional[Union[str, Path]] outD=None, Optional[Union[bool, str, Path]] out_reduced=None, Optional[str] finC=None, Optional[str] outC=None, bool do_cws=False, Optional[Union[str, Path]] cache_file=None, str solver="dist_m4ri", str codedistance_method="UndetectableErrorStim", Optional[Dict[str, Any]] codedistance_params=None, int seed=0, int debug=0, bool verbose=False, bool nothrottle=False, int chunk_size=0, int ksub=0, int kwin=0, int win_mode=0, int win_hyb=0, int pairs=-1, Optional[int] min_hits=None, int cov_cws=100, Optional[int] refresh=None, Optional[bool] simple=None, Optional[bool] full=None, Optional[str] basis=None, Optional[int] rounds=None, Optional[Union[str, Path]] out_dir=None, Optional[Union[bool, str, Path]] out_dem=None, Optional[Union[bool, str, Path]] out_stim=None, float p_noise=0.001, bool force_noise=False, bool trust_start=False, bool start_scan=False, Optional[bool] sort_rows=None, Optional[int] cc_prune=None, Optional[str] engine=None, **kwargs)
Dict[str, Any] classify_qubits_thorough(Any circuit, bool verbose=False, Optional[str] basis_arg=None, Optional[Union[str, Path]] filepath=None)
Tuple[Any, Optional[Any], Optional[str]] _dem_from_input(Optional[Any] dem, Optional[Any] circuit, Optional[str] basis=None, Optional[int] rounds=None, float p_noise=0.001, Optional[Union[str, Path]] out_dir=None, Optional[Union[bool, str, Path]] out_dem=None, Optional[Union[bool, str, Path]] out_stim=None, bool circ_verbose=False, Optional[List[int]] drop_out=None, bool force_noise=False)
Tuple[int, int, int] run_dist_m4ri(Optional[str] dist_m4ri_path=None, int method=3, Optional[str] finH=None, Optional[str] finG=None, Optional[str] finL=None, Optional[str] fin=None, Optional[str] finC=None, Optional[str] fdem=None, int dmin=0, int dmax=0, int wmax=0, int wmin=1, int dexp=0, int dest=0, int dstop=0, Optional[int] steps=None, Optional[int] threads=None, float timeout=60.0, Optional[int] smax=None, Optional[Union[int, str, Sequence[int]]] start=None, Optional[int] cbeg=None, Optional[int] cend=None, Optional[int] css=None, int noscan=0, int classical=-1, int dW=-1, int maxC=0, float pmin=0.0, bool merge=True, Optional[str] drop=None, Optional[str] outD=None, Optional[bool] sort_rows=None, Optional[int] cc_prune=None, Optional[str] outC=None, int seed=0, int debug=0, bool nothrottle=False, int chunk_size=0, int ksub=0, int kwin=0, int win_mode=0, int win_hyb=0, int pairs=-1, Optional[int] min_hits=None, int cov_cws=100, Optional[int] refresh=None, bool verbose=False, Optional[threading.Event] stop_event=None, bool warn_start=True, bool warn_ksub=True, bool start_scan=False, bool ortho=True, bool warn_ortho=True)
Optional[Dict[str, Any]] get_cached_distance(Optional[Any] H=None, Optional[Any] G=None, Optional[Any] L=None, Optional[Any] Hx=None, Optional[Any] Hz=None, Optional[Any] Lx=None, Optional[Any] Lz=None, Optional[Any] dem=None, Optional[Any] circuit=None, float pmin=0.0, Optional[Union[str, Path]] cache_file=None, Optional[Union[int, str, Sequence[int]]] start=None, Optional[bool] simple=None, Optional[bool] full=None, Optional[str] basis=None, Optional[int] rounds=None, float p_noise=0.001, bool force_noise=False, bool start_scan=False)
Definition dist_m4ri.py:717
str _start_key_suffix(Optional[List[int]] start_list)
Tuple[Any, bool, bool] _circuit_dem(Any circuit)
str explain_bounds(List[int] bounds, Optional[int] method=None, str label="", Optional[Dict[str, Any]] stats=None, int dstop=0)
Definition dist_m4ri.py:543
None clear_distance_cache(Optional[Union[str, Path]] cache_file=None, bool clear_file=False)
Definition dist_m4ri.py:230
List[List[int]] read_sparse_vectors(str filepath)
Definition dist_m4ri.py:844
Tuple[Any, int, int] strip_minority_detectors(Any circuit, str basis, bool verbose=False, bool thorough=True, Optional[Dict[str, Any]] thorough_res=None, Optional[List[bool]] mask_out=None)
Any compute_classical_distance(Any H, Optional[str] dist_m4ri=None, int method=3, Optional[int] threads=None, float timeout=60.0, Optional[int] num_steps=None, int d_exp=0, int d_min=0, int d_max=0, int dmin=0, int dmax=0, int dstop=0, int wmin=1, int wmax=0, Optional[int] smax=None, Optional[Union[int, str, Sequence[int]]] start=None, Optional[int] cbeg=None, Optional[int] cend=None, int noscan=0, int dW=-1, int maxC=0, Optional[str] finC=None, Optional[str] outC=None, bool do_cws=False, bool return_info=False, Optional[Union[str, Path]] cache_file=None, str solver="dist_m4ri", str codedistance_method="QDistEvol", Optional[Dict[str, Any]] codedistance_params=None, int seed=0, int debug=0, bool verbose=False, bool nothrottle=False, int chunk_size=0, int ksub=0, int kwin=0, int win_mode=0, int win_hyb=0, int pairs=-1, Optional[int] min_hits=None, int cov_cws=100, Optional[int] refresh=None, bool trust_start=False, bool start_scan=False, Optional[bool] sort_rows=None, Optional[int] cc_prune=None, Optional[str] engine=None, **kwargs)
None set_silent(bool silent=True)
Optional[str] check_binary_compatibility(Optional[str] binary_path=None)
Definition dist_m4ri.py:948
Dict[str, Any] _parse_stderr_stats(str stderr)
Definition dist_m4ri.py:392
None enable_distance_cache()
Definition dist_m4ri.py:245
float rw_infoset_find_prob(int n, int rank, int w, int pairs_l=0)
Definition dist_m4ri.py:292
Any compute_quantum_distance(Any H, Optional[Any] G=None, Optional[Any] L=None, Optional[str] dist_m4ri=None, int method=3, Optional[int] threads=None, float timeout=60.0, Optional[int] num_steps=None, int d_exp=0, int d_min=0, int d_max=0, int dmin=0, int dmax=0, int dstop=0, int wmin=1, int wmax=0, Optional[int] smax=None, Optional[Union[int, str, Sequence[int]]] start=None, Optional[int] cbeg=None, Optional[int] cend=None, int noscan=0, int dW=-1, int maxC=0, Optional[str] finC=None, Optional[str] outC=None, bool do_cws=False, bool return_info=False, Optional[Union[str, Path]] cache_file=None, str solver="dist_m4ri", str codedistance_method="QDistEvol", Optional[Dict[str, Any]] codedistance_params=None, int seed=0, int debug=0, bool verbose=False, bool nothrottle=False, int chunk_size=0, int ksub=0, int kwin=0, int win_mode=0, int win_hyb=0, int pairs=-1, Optional[int] min_hits=None, int cov_cws=100, Optional[int] refresh=None, bool trust_start=False, bool start_scan=False, Optional[bool] sort_rows=None, Optional[int] cc_prune=None, bool ortho=True, Optional[str] engine=None, **kwargs)
None save_distance_cache(Optional[Union[str, Path]] filepath=None)
Definition dist_m4ri.py:202
Optional[str] check_finc_outc(Optional[str] finC, Optional[str] outC, bool verbose=False)
Dict[str, Any] load_distance_cache(Optional[Union[str, Path]] filepath=None)
Definition dist_m4ri.py:169
Any compute_model_lower_bound(Any circuit=None, Optional[str] dist_m4ri=None, int method=3, Optional[int] threads=None, float timeout=60.0, Optional[int] num_steps=None, int dstop=0, int wmax=0, bool minority=False, float pmin=0.0, Optional[int] rounds=None, float p_noise=0.001, bool force_noise=False, Optional[Union[str, Path]] out_model=None, Optional[Union[str, Path]] cache_file=None, int seed=0, int debug=0, bool verbose=False, bool nothrottle=False, int pairs=-1, Optional[bool] sort_rows=None, Optional[int] cc_prune=None, bool return_info=False, Optional[Any] dem=None, **kwargs)
Tuple[str, str] write_reduced_dem(Any dem, Sequence[int] dropped, Union[str, Path] prefix, float pmin=0.0, Optional[int] num_detectors=None, Optional[Dict[str, Any]] meta=None)
None disable_distance_cache()
Definition dist_m4ri.py:251
bool has_noise(Any circuit)
Tuple[Any,...] compute_css_distance(Any Hx, Any Hz, Optional[Any] Lx=None, Optional[Any] Lz=None, Optional[str] dist_m4ri=None, int method=3, Optional[int] threads=None, float timeout=60.0, Optional[int] num_steps=None, int d_exp=0, int d_min=0, int d_max=0, int dmin=0, int dmax=0, int dstop=0, int wmin=1, int wmax=0, Optional[int] smax=None, Optional[Union[int, str, Sequence[int]]] start=None, Optional[int] cbeg=None, Optional[int] cend=None, int noscan=0, int dW=-1, int maxC=0, Optional[str] finC=None, Optional[str] outC=None, bool do_cws=False, Optional[Union[str, Path]] cache_file=None, str solver="dist_m4ri", str codedistance_method="QDistEvol", Optional[Dict[str, Any]] codedistance_params=None, int seed=0, int debug=0, bool verbose=False, bool nothrottle=False, int chunk_size=0, int ksub=0, int kwin=0, int win_mode=0, int win_hyb=0, int pairs=-1, Optional[int] min_hits=None, int cov_cws=100, Optional[int] refresh=None, bool trust_start=False, bool start_scan=False, Optional[bool] sort_rows=None, Optional[int] cc_prune=None, bool ortho=True, Optional[str] engine=None, **kwargs)
test_win_hyb_option(capsys)
test_cc_prune_option(tmp_path, capsys)
test_add_noise_classically_controlled()
test_stim_out_dem_out_stim_out_dir(tmp_path)
test_cli_verbose_mode(capsys)
test_dem_merge_equal_columns(tmp_path, capsys)
test_cli_version(capsys)
test_rw_ksub_kwin_min_hits(capsys)
test_dem_out_reduced(tmp_path, capsys)
test_dem_force_noise_and_p_noise(tmp_path, capsys)
test_model_lower_bound(tmp_path, capsys)
test_stim_pauli_channel_circuit(tmp_path, capsys)
test_start_scan_option(tmp_path, capsys)
test_css_user_dmax_and_sector_records(tmp_path, capsys)
test_ortho_option(capsys)
test_pairs_option(capsys)
test_dem_cache_merged_bounds_and_circuit_key(tmp_path, capsys)
test_dem_error_columns_parser(tmp_path)
test_css_distance_requires_both_matrices(capsys)
test_dstop_stop_target(tmp_path, capsys)
test_run_dist_m4ri_stop_event(capsys)
test_cache_newer_version_not_overwritten(tmp_path, capsys)
test_dem_expand_equal_columns(tmp_path, capsys)
test_ignored_inputs_warnings(tmp_path, capsys, monkeypatch)
test_add_noise_and_noiseless_circuit()
test_kwin_negative_turns_windows_off(capsys)
test_binary_compatibility_warning(tmp_path)
test_explain_bounds_model_check_needs_hit_counts()
test_cli_binary_compatibility_silent(capsys)
_hgp_rep(int a, int b)
test_cli_morehelp(capsys)
test_run_dist_m4ri_start_list_and_disabled_options(capsys)
test_cli_morehelp_topic(capsys)
test_cache_versioning(tmp_path)
test_stim_rounds_option(tmp_path, capsys)
test_dem_circuit_outc_without_out_dem_warning(tmp_path, capsys)
test_matrix_writer(tmp_path)
_make_xzzx_circuit(int distance=3, int rounds=3, str memory_basis="Z")
test_start_list_cache_records(tmp_path, monkeypatch, capsys)
test_classify_qubits_checks_measured_in_hadamard_frame()
test_stim_xzzx_rotated_css(tmp_path, capsys)
test_refresh_passed_to_binary(capsys)
test_start_list_trust_after_calculation_css(tmp_path)
test_cc_zero_col_strip_and_even_weight_parity(tmp_path)
test_cli_no_args_error(capsys)
test_model_detector_groups_end_of_circuit_and_pure_dem(tmp_path)
test_css_and_quantum_distance_inputs()
test_css_cache_migration(tmp_path)
test_stim_simple_vs_full_and_basis_tracking(tmp_path, capsys)
test_quantum_distance_single_sided()
test_persistent_json_cache(tmp_path)
test_add_noise_gate_classes_and_strip_noise()
_surface_memory_z(bool x_in_h_frame, bool meas_before_h, int rounds=3)
test_sort_rows_option(tmp_path, capsys)
test_cc_round_stats(capsys)
test_css_outc_sector_files_and_finc(tmp_path, capsys)
test_cert_engine_delegation_and_cache_rules(tmp_path, monkeypatch)
test_cli_start_trust_start_and_disabled_options(capsys)
test_dem_drop_minority_detectors(tmp_path, capsys)
test_cli_outc_keeps_finc_codewords(tmp_path, capsys)
test_debug_bits(capsys)
test_cli_css_dx_dz_bounds(capsys)
test_cli_identical_finc_outc_nonexistent(capsys)
test_cli_conflict_warnings(capsys)
test_cached_outc_keeps_finc_codewords(tmp_path)