2dist_m4ri.py: Python wrapper for the multithreaded dist_m4ri distance calculator.
4Provides high-level APIs for computing:
5- Classical code distance: compute_classical_distance(...)
6- Single-sided quantum code distance: compute_quantum_distance(...)
7- CSS quantum code distance: compute_css_distance(...)
8- Detector Error Model (DEM) distance: compute_dem_distance(...)
9- Certified lower bound on the distance of a Stim memory circuit from its code-sized model:
10 compute_model_lower_bound(...)
12Supports distance caching, codeword export, and fallback/option for the codedistance library.
13Since dist_m4ri natively handles multithreading (via POSIX threads and dynamic bracketing),
14no Python-level threading or subprocess-per-core logic is necessary.
29from pathlib
import Path
30from typing
import List, Tuple, Union, Optional, Dict, Any, Set, Sequence, Callable
32_codedistance_mod =
None
41PY_DBG_COMMANDS = 1 << 16
43PY_DBG_CIRCUITS = 1 << 18
44PY_DBG_KEEP_FILES = 1 << 19
46VERBOSE_DEBUG = 3 | PY_DBG_COMMANDS | PY_DBG_CACHE
51 Parses a debug bitmap value: a non-negative decimal or hexadecimal (0x...) integer, as in the dist_m4ri binary
52 (no octal: leading zeros are ignored).
55 val: The value, e.g., "3" or "0x10004", or an int.
61 ValueError: If the value is not a non-negative integer.
63 msg = f
"invalid debug={val!r}: expected a non-negative decimal or hexadecimal (0x...) integer"
64 if isinstance(val, bool):
66 if isinstance(val, int):
71 value = int(s[2:], 16)
if s.lower().startswith(
"0x")
else int(s, 10)
73 raise ValueError(msg)
from None
80 """Lazily imports the codedistance library only when requested."""
81 global _codedistance_mod
82 if _codedistance_mod
is None:
85 _codedistance_mod = codedistance
87 raise ImportError(
"codedistance library is requested but not installed.")
88 return _codedistance_mod
92 """Lazily imports stim only when requested."""
99 raise ImportError(
"stim library is requested but not installed.")
104 """Lazily imports qec_cert (io, pipeline) only when requested."""
106 if _qec_cert_mod
is None:
109 from qec_cert
import io
as _qec_io, pipeline
as _qec_pipeline
110 _qec_cert_mod = (qec_cert, _qec_io, _qec_pipeline)
112 raise ImportError(
"qec_cert library is requested (engine='cert') but not installed.")
117 """Checks whether qec_cert is importable without raising ImportError."""
126 """Lazily resolves module attributes without loading heavy dependencies at startup."""
127 if name ==
"_HAS_STIM":
133 if name ==
"_HAS_CODEDISTANCE":
139 if name ==
"_HAS_QEC_CERT":
141 raise AttributeError(f
"module '{__name__}' has no attribute '{name}'")
144_distance_cache: Dict[str, Any] = {}
145_use_distance_cache: bool =
True
146_distance_cache_file: Optional[str] =
None
147_newer_cache_files: Set[str] = set()
148_last_run_stats: Dict[str, Any] = {}
149_last_css_stats: Dict[str, Dict[str, Any]] = {
"X": {},
"Z": {}}
152_css_distance_cache = _distance_cache
153_use_css_distance_cache = _use_distance_cache
158 Sets the default JSON file for persistent distance caching.
159 If the file exists, its contents are loaded into memory.
161 global _distance_cache_file
162 if filepath
is not None:
163 _distance_cache_file = str(Path(filepath).resolve())
166 _distance_cache_file =
None
171 Loads distance cache from a JSON file into memory.
172 Inspects cache version silently; a cache file written by a newer version is ignored with a warning, and it is
173 never overwritten by save_distance_cache().
175 global _distance_cache, _distance_cache_file
176 target_file = str(Path(filepath).resolve())
if filepath
is not None else _distance_cache_file
177 if target_file
and os.path.isfile(target_file):
179 with open(target_file,
"r")
as f:
181 if isinstance(data, dict):
182 cache_ver = data.pop(
"__version__",
None)
185 _newer_cache_files.add(target_file)
187 f
"# Warning: Cache file '{target_file}' has newer version {cache_ver} "
188 f
"(current {__version__}); ignoring incompatible cache (the file is not overwritten).\n"
190 return _distance_cache
192 for k, v
in data.items():
193 if isinstance(v, dict)
and k.startswith(
"css:"):
194 if v.get(
"dmax_X", 0) == 0
and v.get(
"dmax_Z", 0) == 0
and "dmax_X" in v:
196 _distance_cache.update(data)
197 except Exception
as e:
198 sys.stderr.write(f
"# Warning: Failed to load distance cache from {target_file}: {e}\n")
199 return _distance_cache
204 Saves the in-memory distance cache to a JSON file.
205 Silently writes "__version__": __version__ into the file.
206 Uses atomic write via a temporary file to prevent corruption.
207 A cache file written by a newer version (see load_distance_cache()) is not overwritten.
209 global _distance_cache, _distance_cache_file
210 target_file = str(Path(filepath).resolve())
if filepath
is not None else _distance_cache_file
211 if not target_file
or target_file
in _newer_cache_files:
214 parent_dir = os.path.dirname(os.path.abspath(target_file))
or "."
215 os.makedirs(parent_dir, exist_ok=
True)
216 fd, temp_path = tempfile.mkstemp(suffix=
".tmp", prefix=
"dist_cache_", dir=parent_dir)
218 cache_data = dict(_distance_cache)
219 cache_data[
"__version__"] = __version__
220 with open(fd,
"w")
as f:
221 json.dump(cache_data, f, indent=2)
222 os.replace(temp_path, target_file)
223 except Exception
as e:
224 if os.path.exists(temp_path):
225 try: os.remove(temp_path)
227 sys.stderr.write(f
"# Warning: Failed to save distance cache to {target_file}: {e}\n")
232 Clears all cached distance calculations from memory, and optionally deletes the persistent JSON file.
234 global _distance_cache, _distance_cache_file
235 _distance_cache.clear()
236 target_file = str(Path(cache_file).resolve())
if cache_file
is not None else _distance_cache_file
237 if clear_file
and target_file
and os.path.isfile(target_file):
239 os.remove(target_file)
240 _newer_cache_files.discard(target_file)
246 """Enables distance caching."""
247 global _use_distance_cache
248 _use_distance_cache =
True
252 """Disables distance caching."""
253 global _use_distance_cache
254 _use_distance_cache =
False
258 """Returns the global distance cache dictionary."""
259 global _distance_cache
260 return _distance_cache
265 Returns [dmin, dmax, num_rw] according to:
266 - [d, d, 0] if known exactly
267 - [dmin, 0, 0] if there is no upper bound (dmax == 0)
268 - [0, dmax, num_rw] if there is no lower bound (dmin <= 1)
269 - [dmin, dmax, num_rw] otherwise
271 eff_dmin = dmin
if dmin > 1
else 0
272 eff_dmax = dmax
if dmax > 0
else 0
273 eff_rw = num_rw
if num_rw > 0
else 0
274 if eff_dmin > 0
and eff_dmin == eff_dmax:
275 return [eff_dmin, eff_dmax, 0]
277 return [eff_dmin, 0, 0]
279 return [0, eff_dmax, eff_rw]
281 return [eff_dmin, eff_dmax, eff_rw]
285 """Formats a bounds list [dmin, dmax, num_rw] as a string, stating '(exact)' if bounds coincide."""
286 dmin, dmax, num_rw = bounds[0], bounds[1], bounds[2]
287 if dmin > 0
and dmin == dmax:
288 return f
"{dmin} {dmax} {num_rw} (exact)"
289 return f
"{dmin} {dmax} {num_rw}"
294 Information-set estimate of the probability that a RW step finds a given codeword of weight w.
296 Model: uniform random information sets (as in the dist_m4ri binary, rw_infoset_find_prob()). A RW step
297 reduces H (rank r) with a random column order; a codeword is found if exactly one of its w positions is among
298 the k = n - r non-pivot columns: P1(w) = w C(n-w, k-1) / C(n, k). Codewords of weight w > r + 1 are never
299 found (P1 = 0). With the RW pairs (pairs_l > 0, the binary's option pairs), also if exactly two of its positions
300 are non-pivot columns and its other w - 2 positions avoid the l pivot rows of the buckets:
301 P2(w) = C(w, 2) C(n-w, k-2) / C(n, k) * C(r - w + 2, l) / C(r, l), and the probability is P1 + P2.
304 n: Block length (number of columns of H).
307 pairs_l: The number l of pivot rows of the buckets of the RW pairs (0: no pairs).
314 if n <= 0
or rank < 0
or k <= 0
or w < 1
or w > n
or w - 1 > rank:
317 def log_binom(a: int, b: int) -> float:
318 return math.lgamma(a + 1) - math.lgamma(b + 1) - math.lgamma(a - b + 1)
320 p = math.exp(math.log(w) + log_binom(n - w, k - 1) - log_binom(n, k))
321 if pairs_l > 0
and w >= 2
and k >= 2
and w - 2 + pairs_l <= rank:
322 p += math.exp(log_binom(w, 2) + log_binom(n - w, k - 2) - log_binom(n, k) + log_binom(rank - w + 2, pairs_l)
323 - log_binom(rank, pairs_l))
328 n: int, rank: int, steps: int, d: int, dmin: int = 1, step_time: float = 0.0, target: float = 0.01,
329 steps_total: int = 0, pairs_l: int = 0
330) -> Optional[Dict[str, Any]]:
332 Information-set estimate of the probability that RW missed a codeword lighter than the upper bound d found.
334 Among the weights max(dmin, 1) <= w < d (not excluded by CC), the weight hardest to find (the smallest P(w),
335 see rw_infoset_find_prob()) is used: a single codeword of this weight is missed in `steps` RW steps with uniform
336 random permutations with probability (1 - P)^steps. The number of RW steps for the target miss probability is
337 given in total, with the fraction steps / steps_total of uniform steps as in the run (localized windows, used in
338 about half of the steps for n >= 500, are not counted). Same as print_rw_infoset_estimate() in the binary.
341 n: Block length (number of columns of H).
343 steps: Number of completed RW steps with uniform random permutations.
344 d: Smallest weight found (the upper bound dmax).
345 dmin: Certified lower bound (smaller weights are excluded).
346 step_time: Wall time per RW step in seconds (0: unknown).
347 target: Target miss probability (default: 0.01).
348 steps_total: Number of all completed RW steps (0: the same as `steps`).
349 pairs_l: The number l of pivot rows of the buckets of the RW pairs (0: no pairs), see rw_infoset_find_prob().
352 Dictionary with the weight `w`, the probability `p_find` = P(w) per step, the miss probability `p_miss`,
353 the total number of RW steps `steps_target` for the target miss probability (None if the codeword cannot be
354 found), and the corresponding wall time `time_target` (None if unknown); or None if there are no weights to
358 w_lo, w_hi = max(dmin, 1), d - 1
359 if w_hi < w_lo
or n <= 0:
362 for w
in range(w_lo, w_hi):
366 steps_target: Optional[int]
368 p_miss, steps_target = 1.0,
None
370 p_miss, steps_target = (0.0
if steps > 0
else 1.0), 1
373 p_miss = math.exp(steps * lq)
if steps > 0
else 1.0
374 steps_target = math.ceil(math.log(target) / lq)
375 if steps_target
is not None and steps > 0
and steps_total > steps:
376 steps_target = math.ceil(steps_target * steps_total / steps)
377 time_target = steps_target * step_time
if (steps_target
is not None and step_time > 0.0)
else None
378 return {
"w": w_hard,
"p_find": p1,
"p_miss": p_miss,
"steps_target": steps_target,
"time_target": time_target}
382 """Formats a duration in seconds (s, min, h, or days), as in the dist_m4ri binary."""
386 return f
"{t / 60.0:.3g} min"
388 return f
"{t / 3600.0:.3g} h"
389 return f
"{t / 86400.0:.3g} days"
394 Parses codeword and hit statistics from dist_m4ri stderr output (debug & 1), and with debug & 8 the CC rounds:
395 "cc_rounds", a list with an entry for each completed CC round, {"w": weight, "work": the CC thread time in s,
396 "predicted": the predicted work (None if not known), "time": the wall time of the round in s, "threads": the CC
397 threads (at the end of the round in method=3), "nodes": the calls of the CC recursion (None from binaries before
398 0.15.0)}, and "cc_nodes", the total of the nodes of these rounds.
401 stats: Dict[str, Any] = {
"extra_weights": [],
"rw_converged":
False,
"hit_warnings": []}
404 hit_warning: Optional[List[str]] =
None
405 skip_estimate =
False
406 for line
in stderr.splitlines():
407 line_s = line.strip()
408 if line_s.startswith(
"# information-set estimate"):
410 elif skip_estimate
and line_s.startswith(
"# "):
413 skip_estimate =
False
415 if hit_warning
is not None:
416 stats[
"hit_warnings"].append(
" ".join(hit_warning))
419 if hit_warning
is not None:
420 if line_s.startswith(
"# "):
421 hit_warning.append(line_s[1:].strip())
423 stats[
"hit_warnings"].append(
" ".join(hit_warning))
425 if line_s.startswith(
"# Warning: non-uniform RW hit counts"):
426 hit_warning = [line_s[len(
"# Warning: "):]]
428 if line_s.startswith(
"# Note: drop=")
and " not used: " in line_s:
429 stats[
"drop_note"] = line_s.split(
" not used: ", 1)[1].replace(
"; using the full DEM",
"")
431 if line_s.startswith(
"# Note: drop=")
and "none of the listed detectors is in the DEM" in line_s:
432 stats[
"drop_none_listed"] =
True
434 if line_s.startswith(
"# RW information sets:"):
436 r"n=(\d+), rank\(H\)=(\d+), steps=(\d+) \(uniform permutations: (\d+)\), ksub=(\d+), "
437 r"(?:pairs=(\d+), )?([0-9.eE+-]+) s/step per thread, (\d+) RW threads", line_s
440 stats[
"rw_n"] = int(m_rw.group(1))
441 stats[
"rw_rank"] = int(m_rw.group(2))
442 stats[
"rw_steps"] = int(m_rw.group(3))
443 stats[
"rw_steps_uniform"] = int(m_rw.group(4))
444 stats[
"rw_ksub"] = int(m_rw.group(5))
445 stats[
"rw_pairs_l"] = int(m_rw.group(6))
if m_rw.group(6)
else 0
446 stats[
"rw_step_time"] = float(m_rw.group(7))
447 stats[
"rw_threads"] = int(m_rw.group(8))
449 if line_s.startswith(
"# RW pairs (p=2"):
450 m_p = re.search(
r"([0-9.eE+-]+) pairs checked per step; .*: (\d+) from pairs, (\d+) from single columns",
453 stats[
"rw_pairs_per_step"] = float(m_p.group(1))
454 stats[
"rw_hits_pairs"] = int(m_p.group(2))
455 stats[
"rw_hits_single"] = int(m_p.group(3))
457 if line_s.startswith(
"# RW step types, w="):
458 m_st = re.search(
r"w=(\d+): uniform steps: cws=(\d+), hits=(\d+); window steps: cws=(\d+), hits=(\d+); "
459 r"both: cws=(\d+) \(of (\d+) cws\)", line_s)
461 v = [int(x)
for x
in m_st.groups()]
462 stats.setdefault(
"rw_step_types", {})[v[0]] = {
463 "uniform_cws": v[1],
"uniform_hits": v[2],
"window_cws": v[3],
"window_hits": v[4],
464 "both_cws": v[5],
"cws": v[6]}
466 if line_s.startswith(
"# rounds of the DEM:"):
467 m_r = re.search(
r"D=(\d+) detectors and L=(\d+) columns per round \(estimated: score ([0-9.]+)\)", line_s)
468 m_n = re.search(
r"best score ([0-9.]+)", line_s)
470 stats[
"rounds"] = {
"D": int(m_r.group(1)),
"L": int(m_r.group(2)),
"score": float(m_r.group(3))}
472 stats[
"rounds"] = {
"D": 0,
"L": 0,
"score": float(m_n.group(1))}
474 if line_s.startswith(
"# CC round w=")
and " work: " in line_s:
475 m_cc = re.match(
r"# CC round w=(\d+) work: ([0-9.eE+-]+) thread-s measured, (unknown|([0-9.eE+-]+) "
476 r"thread-s) predicted \(([0-9.]+)s(?: on (\d+) threads|, (\d+) CC threads at the end)\)"
477 r"(?:, (\d+) nodes)?", line_s)
480 stats.setdefault(
"cc_rounds", []).append({
481 "w": int(g[0]),
"work": float(g[1]),
"predicted": float(g[3])
if g[3]
else None,
482 "time": float(g[4]),
"threads": int(g[5]
or g[6]),
"nodes": int(g[7])
if g[7]
else None})
484 if "RW convergence reached" in line_s:
485 stats[
"rw_converged"] =
True
486 if line_s.startswith(
"# codewords accumulated: total=0"):
487 stats[
"total_cws"] = 0
488 elif line_s.startswith(
"# codewords accumulated:"):
490 r"total=(\d+),\s*min_w=(\d+):\s*cws=(\d+),\s*total_hits=(\d+),\s*"
491 r"hits min=(\d+),\s*max=(\d+),\s*avg=([0-9.]+),\s*stdev=([0-9.]+)",
495 stats[
"total_cws"] = int(m.group(1))
496 stats[
"min_w"] = int(m.group(2))
497 stats[
"cws"] = int(m.group(3))
498 stats[
"total_hits"] = int(m.group(4))
499 stats[
"hits_min"] = int(m.group(5))
500 stats[
"hits_max"] = int(m.group(6))
501 stats[
"hits_avg"] = float(m.group(7))
502 stats[
"hits_stdev"] = float(m.group(8))
504 r"<n>=([0-9.]+) over (\d+) cws of w=(\d+)\.\.(\d+) \(min_hits=(\d+), cov_cws=(-?\d+)\)", line_s
507 stats[
"avg_hits"] = float(m_set.group(1))
508 stats[
"set_cws"] = int(m_set.group(2))
509 stats[
"set_w_lo"] = int(m_set.group(3))
510 stats[
"set_w_hi"] = int(m_set.group(4))
511 stats[
"min_hits"] = int(m_set.group(5))
512 stats[
"cov_cws"] = int(m_set.group(6))
513 elif line_s.startswith(
"# codewords w="):
515 r"w=(\d+):\s*cws=(\d+),\s*total_hits=(\d+),\s*"
516 r"hits min=(\d+),\s*max=(\d+),\s*avg=([0-9.]+),\s*stdev=([0-9.]+)",
520 stats[
"extra_weights"].append({
521 "w": int(m_w.group(1)),
522 "cws": int(m_w.group(2)),
523 "total_hits": int(m_w.group(3)),
524 "hits_min": int(m_w.group(4)),
525 "hits_max": int(m_w.group(5)),
526 "hits_avg": float(m_w.group(6)),
527 "hits_stdev": float(m_w.group(7)),
529 if hit_warning
is not None:
530 stats[
"hit_warnings"].append(
" ".join(hit_warning))
531 cc_rounds = stats.get(
"cc_rounds")
532 if cc_rounds
and all(r[
"nodes"]
is not None for r
in cc_rounds):
533 stats[
"cc_nodes"] = sum(r[
"nodes"]
for r
in cc_rounds)
539 method: Optional[int] =
None,
541 stats: Optional[Dict[str, Any]] =
None,
545 Returns a human-readable explanation of [dmin, dmax, num_rw] following README.md.
548 bounds: [dmin, dmax, rw_steps] list.
549 method: Optional solver method (1=RW, 2=CC, 3=Bracketing).
550 label: Optional prefix/label (e.g. "dX", "dZ", "").
551 stats: Optional dictionary of codeword and hit statistics parsed from dist_m4ri.
552 dstop: Stop target of the search (0: none); once dmin >= dstop, the upper bound is not needed, and the
553 estimates of the probability that RW missed a lighter codeword are skipped.
556 Multi-line formatted explanation string.
558 dmin, dmax, num_rw = bounds[0], bounds[1], bounds[2]
560 prefix = f
"{label} " if label
else ""
561 stopped = dstop > 0
and dmin >= dstop
and not (dmin > 0
and dmin == dmax)
564 if dmin > 0
and dmin == dmax:
565 lines.append(f
" {prefix}Lower bound (dmin = {dmin}): Exact distance certified (dmin == dmax == {dmin}).")
567 lines.append(f
" {prefix}Lower bound (dmin = {dmin}): All cluster weights w <= {dmin - 1} "
568 f
"were exhaustively analyzed by CC without finding any non-trivial codewords.")
570 lines.append(f
" {prefix}Lower bound (dmin = {dmin}): No non-trivial lower bound certified (dmin <= 1).")
574 lines.append(f
" {prefix}Upper bound (dmax = {dmax}): Weight of the smallest non-trivial codeword discovered.")
576 lines.append(f
" {prefix}Upper bound (dmax = {dmax}): No non-trivial codeword discovered yet (dmax = 0).")
578 lines.append(f
" {prefix}Stop target (dstop = {dstop}): reached, the search ended once dmin >= {dstop} was "
579 f
"certified (no codeword of weight < {dstop}); the upper bound is not needed.")
583 lines.append(f
" {prefix}Random window steps (rw_steps = {num_rw}): {num_rw} completed random "
584 f
"information set searches across worker threads.")
586 if dmin > 0
and dmin == dmax:
587 lines.append(f
" {prefix}Random window steps (rw_steps = 0): Set to 0 because the exact distance "
588 f
"d = {dmin} was proven by Connected Cluster search or certified bounds coincided.")
590 lines.append(f
" {prefix}Random window steps (rw_steps = 0): Set to 0 because Method 2 (Connected Cluster) "
591 f
"is an exhaustive search that does not perform random information set (RW) sampling.")
593 lines.append(f
" {prefix}Random window steps (rw_steps = 0): 0 completed random information set steps.")
595 if stats
and "total_cws" in stats:
596 if stats[
"total_cws"] > 0:
598 f
" {prefix}Codewords accumulated: {stats['total_cws']} distinct "
599 f
"(w = {stats['min_w']}: {stats['cws']} codewords, {stats['total_hits']} total hits; "
600 f
"hits min = {stats['hits_min']}, max = {stats['hits_max']}, "
601 f
"avg = {stats['hits_avg']:.2f}, stdev = {stats['hits_stdev']:.2f})."
603 if stats.get(
"min_hits", 0) > 0
and "avg_hits" in stats:
605 conv_str =
"CONVERGED" if stats.get(
"rw_converged")
else "not converged"
607 f
" {prefix}Hit convergence (min_hits = {stats['min_hits']}, "
608 f
"cov_cws = {stats['cov_cws']}): <n> = {stats['avg_hits']:.2f} average hits per codeword "
609 f
"({stats['set_cws']} codewords of weight {stats['set_w_lo']}..{stats['set_w_hi']}), "
610 f
"P_fail ~ exp(-<n>) = {math.exp(-stats['avg_hits']):.2g} ({conv_str})."
612 for ew
in stats.get(
"extra_weights", []):
614 f
" {prefix}Codewords (w = {ew['w']}): {ew['cws']} codewords, "
615 f
"{ew['total_hits']} total hits; hits min = {ew['hits_min']}, "
616 f
"max = {ew['hits_max']}, avg = {ew['hits_avg']:.2f}, "
617 f
"stdev = {ew['hits_stdev']:.2f}."
619 n_all, n_unif = stats.get(
"rw_steps", 0), stats.get(
"rw_steps_uniform", 0)
620 for w, st
in sorted(stats.get(
"rw_step_types", {}).items()):
622 f
" {prefix}Codewords (w = {w}) by RW step type: {st['uniform_cws']} found in the {n_unif} uniform "
623 f
"steps ({st['uniform_hits']} hits), {st['window_cws']} in the {n_all - n_unif} window steps "
624 f
"({st['window_hits']} hits), {st['both_cws']} in both (of {st['cws']} codewords)."
626 if stats.get(
"rw_pairs_l", 0) > 0
and "rw_pairs_per_step" in stats:
628 f
" {prefix}RW pairs (p=2, buckets of l = {stats['rw_pairs_l']} pivot rows): "
629 f
"{stats['rw_pairs_per_step']:.3g} pairs checked per RW step; codewords recorded with weight up to "
630 f
"the upper bound: {stats['rw_hits_pairs']} from pairs, {stats['rw_hits_single']} from single "
633 for hw
in stats.get(
"hit_warnings", []):
634 lines.append(f
" {prefix}WARNING: {hw}")
636 lines.append(f
" {prefix}Codewords accumulated: 0 distinct non-trivial codewords found.")
639 if stats
and num_rw > 0
and dmax > max(dmin, 1)
and not stopped:
641 rw_threads = stats.get(
"rw_threads", 0)
642 step_wall = stats.get(
"rw_step_time", 0.0) / rw_threads
if rw_threads > 0
else 0.0
643 avg = stats.get(
"avg_hits", 0.0)
644 if stats.get(
"min_hits", 0) > 0
and avg > 0.0:
645 p_hit = math.exp(-avg)
647 need = math.ceil(num_rw * math.log(100.0) / avg)
648 t_str = f
", ~{_format_duration((need - num_rw) * step_wall)} more" if step_wall > 0.0
else ""
649 lines.append(f
" {prefix}Hit-based estimate: P_fail ~ exp(-<n>) = {p_hit:.2g}; 1% after about {need} "
650 f
"RW steps in total{t_str}.")
652 lines.append(f
" {prefix}Hit-based estimate: P_fail ~ exp(-<n>) = {p_hit:.2g} (below 1%).")
653 n_unif = stats.get(
"rw_steps_uniform", 0)
654 if stats.get(
"rw_n", 0) > 0
and stats.get(
"rw_ksub", 0) == 0
and n_unif > 0:
655 n_cols, rank = stats[
"rw_n"], stats[
"rw_rank"]
656 pairs_l = stats.get(
"rw_pairs_l", 0)
657 est =
rw_infoset_estimate(n_cols, rank, n_unif, dmax, dmin=max(dmin, 1), step_time=step_wall,
658 steps_total=stats.get(
"rw_steps", n_unif), pairs_l=pairs_l)
659 if est
is not None and est[
"steps_target"]
is None:
660 lines.append(f
" {prefix}Information-set estimate: a codeword of weight {est['w']} cannot be found "
661 f
"by RW (n = {n_cols}, rank(H) = {rank}).")
662 elif est
is not None:
663 t_str = (f
", ~{_format_duration(est['time_target'])}" if est[
"time_target"]
is not None else "")
664 p_str = f
" with pairs, l = {pairs_l}" if pairs_l > 0
else ""
666 f
" {prefix}Information-set estimate (uniform random information sets{p_str}; n = {n_cols}, "
667 f
"rank(H) = {rank}, {n_unif} of {stats.get('rw_steps', n_unif)} RW steps uniform): a single "
668 f
"codeword of weight {est['w']} < {dmax} is found with probability {est['p_find']:.3g} per RW "
669 f
"step and would be missed with probability {est['p_miss']:.2g}; 1% after about "
670 f
"{est['steps_target']} RW steps in total{t_str}."
673 if stats.get(
"min_w") == dmax
and "hits_avg" in stats
and stats.get(
"min_hits", 0) > 0:
674 n_all = stats.get(
"rw_steps", n_unif)
676 st = stats.get(
"rw_step_types", {}).get(dmax)
677 if st
is not None and st[
"cws"] > 0:
678 n_ref, obs, note = n_unif, st[
"uniform_hits"] / st[
"cws"],
" in the uniform steps"
680 n_ref, obs = n_all, stats[
"hits_avg"]
681 note =
"" if n_unif == n_all
else ", including the steps with localized windows"
682 exp_hits = n_ref * p1
683 verdict = (
"the uniform model is conservative here" if obs >= exp_hits
else
684 "codewords are found less often than in the uniform model, the information-set "
685 "estimate may be optimistic")
687 f
" {prefix}Model check: expected hits per codeword of weight {dmax} in {n_ref} uniform RW "
688 f
"steps: {exp_hits:.3g}, observed average {obs:.2f}{note} ({verdict})."
690 elif stats.get(
"rw_n", 0) > 0:
691 lines.append(f
" {prefix}Information-set estimate: not available (ksub > 0 or kwin > 0: no uniform "
692 f
"random information sets).")
694 return "\n".join(lines)
698 H: Optional[Any] =
None,
699 G: Optional[Any] =
None,
700 L: Optional[Any] =
None,
701 Hx: Optional[Any] =
None,
702 Hz: Optional[Any] =
None,
703 Lx: Optional[Any] =
None,
704 Lz: Optional[Any] =
None,
705 dem: Optional[Any] =
None,
706 circuit: Optional[Any] =
None,
708 cache_file: Optional[Union[str, Path]] =
None,
709 start: Optional[Union[int, str, Sequence[int]]] =
None,
710 simple: Optional[bool] =
None,
711 full: Optional[bool] =
None,
712 basis: Optional[str] =
None,
713 rounds: Optional[int] =
None,
714 p_noise: float = 0.001,
715 force_noise: bool =
False,
716 start_scan: bool =
False
717) -> Optional[Dict[str, Any]]:
719 Retrieves the cached distance entry (including bounds and cumulative rw_steps)
720 for a given code matrix, CSS code, or DEM.
722 If the expert CC start-column list `start` is given (see compute_classical_distance), the
723 separate start-list record (cache key suffix ':start=a,b,c', or ':start=a,b,c:scan' with start_scan=True) is
724 returned instead of the main record.
726 For a CSS code (Hx and Hz, optionally Lx / Lz), the entry is assembled from the records of its two sectors,
727 the quantum codes (H=Hx, G=Hz) -> dZ and (H=Hz, G=Hx) -> dX (shared with compute_quantum_distance()): the fields
728 dmin_X, dmax_X, rw_steps_X, dX, cws_X (the same for Z), and dist, dmin, dmax, rw_steps of d = min(dX, dZ).
730 For a Stim circuit (circuit, or dem with a .stim file), the cache key is that of the DEM processed as in
731 compute_dem_distance(), with the same options rounds, p_noise, and force_noise (simple and full are disabled since
732 version 0.13.0: ignored with a warning to stderr; basis does not change the DEM).
735 dict with keys {"dist", "dmin", "dmax", "rw_steps", ...} or None if not cached.
737 global _distance_cache, _distance_cache_file
738 eff_cache_file = str(Path(cache_file).resolve())
if cache_file
is not None else _distance_cache_file
744 entry = _distance_cache.get(
_matrix_cache_key(H, G, L, classical=(G
is None and L
is None)) + sfx)
747 entry[
"d_info"] =
format_bounds_list(entry.get(
"dmin", 0), entry.get(
"dmax", 0), entry.get(
"rw_steps", 0))
749 elif Hx
is not None or Hz
is not None:
751 if Hx
is None or Hz
is None:
755 sec = {s: _distance_cache.get(
_matrix_cache_key(*codes[s]) + sfx)
for s
in (
"X",
"Z")}
756 if not sec[
"X"]
and not sec[
"Z"]:
762 b = (rec.get(
"dmin", 0)
or 0, rec.get(
"dmax", 0)
or 0, rec.get(
"rw_steps", 0)
or 0)
764 entry[f
"dmin_{s}"], entry[f
"dmax_{s}"], entry[f
"rw_steps_{s}"] = b
766 entry[f
"cws_{s}"] = list(rec.get(
"cws", []))
767 dist, d_lo, d_hi, rw =
_css_combine(bounds[
"X"], bounds[
"Z"])
768 entry.update({
"dist": dist,
"dmin": d_lo,
"dmax": d_hi,
"rw_steps": rw})
770 elif dem
is not None or circuit
is not None:
773 dem_obj, _, _ =
_dem_from_input(dem, circuit, basis=basis, rounds=rounds, p_noise=p_noise,
774 force_noise=force_noise)
778 entry[
"d_info"] =
format_bounds_list(entry.get(
"dmin", 0), entry.get(
"dmax", 0), entry.get(
"rw_steps", 0))
784clear_css_distance_cache = clear_distance_cache
785enable_css_distance_cache = enable_distance_cache
786disable_css_distance_cache = disable_distance_cache
790 """Returns a deterministic string representation for JSON-compatible cache keys."""
793 if isinstance(A, (str, Path)):
794 path_str = str(Path(A).resolve())
795 if os.path.isfile(path_str):
797 with open(path_str,
"rb")
as f:
798 content_h = hashlib.sha256(f.read()).hexdigest()
799 return f
"file:{path_str}:{content_h}"
801 return f
"file:{path_str}"
803 if hasattr(A,
'shape')
and hasattr(A,
'dtype')
and hasattr(A,
'tobytes'):
804 h = hashlib.sha256(A.tobytes()).hexdigest()
805 dtype_str = getattr(A.dtype,
'str', str(A.dtype))
806 return f
"ndarray:{A.shape}:{dtype_str}:{h}"
807 if hasattr(A,
'tocsr'):
809 h = hashlib.sha256(csr.data.tobytes() + csr.indices.tobytes() + csr.indptr.tobytes()).hexdigest()
810 return f
"csr:{csr.shape}:{h}"
811 if hasattr(A,
'tobytes'):
812 h = hashlib.sha256(A.tobytes()).hexdigest()
814 h = hashlib.sha256(str(A).encode(
'utf-8')).hexdigest()
815 return f
"str_sha256:{h}"
819 """Creates a unique temporary file path and ensures the parent directory exists."""
821 os.makedirs(directory, exist_ok=
True)
822 fd, path = tempfile.mkstemp(suffix=extension, dir=directory)
824 fd, path = tempfile.mkstemp(suffix=extension)
830 """Removes the temporary files `files` (None entries are skipped); with the debug bit PY_DBG_KEEP_FILES, the
831 files are kept and listed instead."""
833 if not f
or not os.path.exists(f):
835 if debug & PY_DBG_KEEP_FILES:
836 print(f
"[dist_m4ri] Kept temporary file: {f}")
846 Reads a list of sparse vectors from a text file in NZLIST format,
847 converting from 1-based indexing (in the file) to 0-based indexing (in Python).
850 filepath (str): The path to the text file.
853 list of list of int: A list where each element is a 0-based sparse vector.
856 if not os.path.exists(filepath)
or os.path.getsize(filepath) == 0:
857 return sparse_vectors
859 with open(filepath,
'r')
as f:
860 first_line = f.readline().strip()
862 return sparse_vectors
863 if first_line !=
'%% NZLIST':
864 raise ValueError(f
"Invalid file format in {filepath}: Missing '%% NZLIST' header.")
866 for line_num, line
in enumerate(f, start=2):
868 if not line
or line.startswith(
'%'):
871 parts = list(map(int, line.split()))
873 raise ValueError(f
"Non-integer data found on line {line_num}: {line}")
875 stated_length = parts[0]
876 vector_elements = [x - 1
for x
in parts[1:]]
877 if len(vector_elements) != stated_length:
879 f
"Length mismatch on line {line_num}. "
880 f
"Expected {stated_length} elements, but found {len(vector_elements)}."
882 sparse_vectors.append(vector_elements)
884 return sparse_vectors
888 """Finds the dist_m4ri executable."""
890 if os.path.isfile(custom_path)
and os.access(custom_path, os.X_OK):
891 return os.path.abspath(custom_path)
892 raise FileNotFoundError(
893 f
"Specified executable '{custom_path}' not found or not executable."
896 pkg_dir = os.path.dirname(os.path.abspath(__file__))
898 os.path.join(pkg_dir,
"src",
"dist_m4ri"),
899 os.path.join(pkg_dir,
"dist_m4ri"),
900 os.path.join(pkg_dir,
"bin",
"dist_m4ri"),
901 os.path.join(pkg_dir,
"..",
"dist-m4ri",
"src",
"dist_m4ri"),
902 os.path.join(os.getcwd(),
"src",
"dist_m4ri"),
903 os.path.join(os.getcwd(),
"dist_m4ri"),
904 os.path.join(os.getcwd(),
"bin",
"dist_m4ri"),
907 for cand
in candidates:
908 if os.path.isfile(cand)
and os.access(cand, os.X_OK):
909 return os.path.abspath(cand)
911 which_path = shutil.which(
"dist_m4ri")
915 raise FileNotFoundError(
916 "Could not find executable 'dist_m4ri'. Please run 'make -C src' to build it."
922 parts = re.findall(
r"\d+", v_str)
923 return tuple(int(p)
for p
in parts)
if parts
else (0,)
926def _binary_version(binary_path: Optional[str] =
None) -> Tuple[Optional[str], Optional[str], Optional[str]]:
928 The dist_m4ri binary and its version (from `dist_m4ri --version`).
931 (path, version, error): path is None if the binary is not found; version is None if it is unknown (then
932 error is the reason, or None if the binary does not support --version).
936 except (RuntimeError, FileNotFoundError):
937 return None,
None,
None
939 proc = subprocess.run([path,
"--version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=
True,
941 except Exception
as e:
942 return path,
None, str(e)
943 if proc.returncode == 0
and "version" in proc.stdout:
944 return path, proc.stdout.strip().split()[-1],
None
945 return path,
None,
None
950 Checks if the backend dist_m4ri binary exists and is compatible (version >= __version__).
951 Returns a warning message string if missing or older, or None if compatible (silent).
955 return "Warning: backend binary 'dist_m4ri' not found (run 'make -C src' to build it)"
956 if error
is not None:
957 return f
"Warning: failed to check binary '{path}': {error}"
960 f
"Warning: backend binary '{path}' does not support --version "
961 f
"(expected >= {__version__}; run 'make -C src' to rebuild)"
965 f
"Warning: backend binary '{path}' is version {bin_ver} "
966 f
"(expected >= {__version__}; run 'make -C src' to rebuild)"
973 Parses the standard output of dist_m4ri.
974 Expected format on stdout: "dmin dmax rw_steps", "dmin dmax", or a single integer.
977 tuple (dmin, dmax, rw_steps)
979 lines = stdout.strip().split(
'\n')
980 for line
in reversed(lines):
982 if not line
or line.startswith(
'#'):
987 return int(parts[0]), int(parts[1]), int(parts[2])
990 elif len(parts) == 2:
992 return int(parts[0]), int(parts[1]), 0
995 elif len(parts) == 1:
1002 raise RuntimeError(f
"Could not parse dist_m4ri output: {stdout}")
1007 Structured result for code distance calculations containing:
1008 - dmin: lower bound on distance
1009 - dmax: upper bound on distance (minimum non-trivial codeword weight found)
1010 - rw_steps: cumulative number of completed random window information sets
1011 - cws: discovered codewords (if requested)
1018 cws: Optional[List[List[int]]] =
None,
1019 cws_X: Optional[List[List[int]]] =
None,
1020 cws_Z: Optional[List[List[int]]] =
None,
1021 dmin_X: Optional[int] =
None,
1022 dmax_X: Optional[int] =
None,
1023 rw_steps_X: Optional[int] =
None,
1024 dmin_Z: Optional[int] =
None,
1025 dmax_Z: Optional[int] =
None,
1026 rw_steps_Z: Optional[int] =
None,
1056 if isinstance(other, DistanceResult):
1057 return (self.
dmin, self.
dmax, self.
rw_steps) == (other.dmin, other.dmax, other.rw_steps)
1058 if isinstance(other, (tuple, list)):
1059 return self.
_fields() == tuple(other)
1060 if isinstance(other, numbers.Integral):
1065 """The result as a tuple (dmin, dmax, rw_steps[, cws] or [, cws_X, cws_Z])."""
1066 if self.
cws_X is not None or self.
cws_Z is not None:
1068 if self.
cws is not None:
1083 return f
"{self.dmin} {self.dmax} {self.rw_steps} (exact)"
1084 return f
"{self.dmin} {self.dmax} {self.rw_steps}"
1088 return f
"DistanceResult({self.dmin} {self.dmax} {self.rw_steps} (exact))"
1089 return f
"DistanceResult(dmin={self.dmin}, dmax={self.dmax}, rw_steps={self.rw_steps})"
1092def check_finc_outc(finC: Optional[str], outC: Optional[str], verbose: bool =
False) -> Optional[str]:
1094 When finC and outC names are identical, an empty or non-existent file is silently ignored
1095 (with a warning if verbose is True).
1098 The effective finC filepath to use (or None if ignored).
1102 if outC
and (finC == outC
or os.path.abspath(finC) == os.path.abspath(outC)):
1103 if not os.path.exists(finC)
or os.path.getsize(finC) == 0:
1105 print(f
"[dist_m4ri] Warning: finC='{finC}' (identical to outC) is empty or non-existent; "
1106 f
"silently ignoring input codewords.")
1134 Normalizes the expert CC start-column specification `start` into a sorted list of distinct
1135 non-negative (0-based) column indices, or None if unset.
1137 Accepts None, an integer, a comma-separated string (e.g., "0,48"), or a sequence of integers.
1138 A single negative value (e.g., -1, the default of the dist_m4ri binary) means unset (all columns).
1141 ValueError: on malformed input or on negative entries in a list of several columns.
1146 err_msg = f
"invalid start={start!r}: expected a comma-separated list of column indices, e.g., start=0,48"
1147 vals: List[int] = []
1148 if isinstance(start, str):
1153 vals = [int(tok)
for tok
in s.split(
",")]
1155 raise ValueError(err_msg)
from None
1162 if isinstance(v, (bool, str, bytes)):
1163 raise ValueError(err_msg)
1165 vals.append(operator.index(v))
1167 raise ValueError(err_msg)
from None
1170 if len(vals) == 1
and vals[0] < 0:
1172 if any(v < 0
for v
in vals):
1173 raise ValueError(f
"invalid start={start!r}: column indices must be non-negative")
1174 return sorted(set(vals))
1178 """A normalized CC start list (sorted distinct columns, see _normalize_start_list()) with the expert flag `scan`
1179 (start_scan=1 of the binary: a cluster grown from a listed column c only uses the columns > c), which selects
1180 its own cache key (see _start_key_suffix())."""
1185 """The start list with the flag `scan` set if start_scan (None or the list itself otherwise)."""
1186 if not start_list
or not start_scan:
1194 """Returns the cache key suffix ':start=a,b,c' for a normalized start list ('' if unset), with ':scan' for
1198 return ":start=" +
",".join(str(c)
for c
in start_list) + (
":scan" if getattr(start_list,
"scan",
False)
else "")
1202 """Formats a start list for messages, showing at most `max_show` entries (as the dist_m4ri binary)."""
1203 shown =
",".join(str(c)
for c
in start_list[:max_show])
1204 return shown + (
",..." if len(start_list) > max_show
else "")
1207_report_ignored =
True
1212 Turns off (silent=True) or on again (silent=False) the warnings written to stderr for the inputs which are
1213 ignored (the CLI option --silent). The warnings on the validity of a result (e.g., for ksub, ortho=0, or start)
1216 global _report_ignored
1217 _report_ignored =
not silent
1221 """Writes the warning '# Warning: msg' on an input which is ignored to stderr, unless set_silent() is on."""
1223 sys.stderr.write(f
"# Warning: {msg}\n")
1230_CODEDISTANCE_UNUSED: Dict[str, Any] = {
1231 "method": 3,
"threads":
None,
"timeout": 60.0,
"d_exp": 0,
"dmin": 0,
"dmax": 0,
"d_min": 0,
"d_max": 0,
1232 "dstop": 0,
"wmin": 1,
"wmax": 0,
"smax":
None,
"dW": -1,
"maxC": 0,
"finC":
None,
"nothrottle":
False,
1233 "chunk_size": 0,
"ksub": 0,
"kwin": 0,
"win_mode": 0,
"win_hyb": 0,
"pairs": -1,
"sort_rows":
None,
1234 "cc_prune":
None,
"min_hits":
None,
"cov_cws": 100,
"refresh":
None,
"dist_m4ri":
None,
"pmin": 0.0,
1235 "merge":
True,
"drop":
None,
"expand":
False,
1241 With solver="codedistance": a warning (see _warn_ignored()) on the options of the dist_m4ri binary in `values`
1242 (the locals() of a compute_*() function) which are not at their defaults (see _CODEDISTANCE_UNUSED).
1244 names = [k
for k, v
in _CODEDISTANCE_UNUSED.items()
if k
in values
and values[k] != v]
1246 _warn_ignored(f
"{', '.join(k + '=' for k in names)}: not used with solver='codedistance'; ignored")
1251 Issues a warning to stderr for each of the expert options noscan, cbeg, and cend that is set.
1252 These options are disabled in the Python interface: they are accepted for backward compatibility
1253 but ignored, since their results are not valid distance bounds on their own.
1255 for name, val, is_flag
in ((
"noscan", noscan,
True), (
"cbeg", cbeg,
False), (
"cend", cend,
False)):
1260 active = (ival != 0)
if is_flag
else (ival >= 0)
1261 except (TypeError, ValueError):
1264 _warn_ignored(f
"{name}={val} is disabled in the Python interface (ignored); use the dist_m4ri binary "
1269 """Returns the stderr warning for a CC start list (same wording as the dist_m4ri binary)."""
1270 if getattr(start_list,
"scan",
False):
1272 f
"# WARNING: start={_format_start_list(start_list)} start_scan=1 (expert options): CC clusters are grown "
1273 f
"only from {len(start_list)} listed column(s), each with\n"
1274 f
"# the larger columns only; dmin is valid only if a code symmetry maps every minimum-weight codeword\n"
1275 f
"# to one whose smallest column is listed\n"
1278 f
"# WARNING: start={_format_start_list(start_list)} (expert option): CC clusters are grown only from "
1279 f
"{len(start_list)} listed column(s)\n"
1280 f
"# (not limited to larger column indices); dmin is valid only if a code symmetry maps every\n"
1281 f
"# minimum-weight codeword to one containing a listed column\n"
1286 """Returns the stderr warning for the experimental RW option ksub (same wording as the dist_m4ri binary)."""
1288 f
"# WARNING: ksub={ksub} is experimental and should not be used: a RW step reduces the span of ksub rows\n"
1289 f
"# of a fixed basis of ker(H), so that a few codewords are found much more often than others; with\n"
1290 f
"# min_hits, RW may end early, and exp(-<n>) underestimates the probability to miss a lighter codeword\n"
1296 Issues the warning for the experimental RW option ksub > 0 to stderr (with or without min_hits), if RW runs
1297 (method=1 or 3) with the dist_m4ri binary.
1300 ival = int(ksub
or 0)
1301 except (TypeError, ValueError):
1303 if ival > 0
and (int(method) & 1)
and solver !=
"codedistance":
1308 """Returns the stderr warning for the expert option ortho=0 (same wording as the dist_m4ri binary)."""
1310 "# WARNING: ortho=0 (expert option): the rows of H and G are not checked for orthogonality\n"
1311 "# (H G^T = 0); the result is valid only if they are orthogonal\n"
1315def _warn_ortho(ortho: Any, has_g: bool, solver: str =
"dist_m4ri") ->
None:
1317 Issues the warning for the expert option ortho=False to stderr: the dist_m4ri binary does not check that the rows
1318 of H and G are orthogonal (if has_g: a matrix G is given), or the option has no effect (without G, or with the
1319 codedistance solver).
1323 if solver ==
"codedistance":
1324 _warn_ignored(
"ortho=0 is ignored with solver='codedistance'")
1328 _warn_ignored(
"ortho=0 has no effect without G (the check H G^T = 0 needs G)")
1331def _check_kwargs(func: Callable[..., Any], kwargs: Dict[str, Any], aliases: Sequence[str] = (
"win",)) ->
None:
1333 Raises TypeError for the keyword arguments in kwargs (the **kwargs of func) other than the accepted aliases
1334 (`win` for kwin), e.g., misspelled or unsupported options, with the closest parameter names as hints.
1336 unknown = [k
for k
in kwargs
if k
not in aliases]
1341 names = [n
for n, p
in inspect.signature(func).parameters.items()
if p.kind
is not inspect.Parameter.VAR_KEYWORD]
1342 names += list(aliases)
1345 close = difflib.get_close_matches(k, names, n=1)
1346 items.append(f
"'{k}'" + (f
" (did you mean '{close[0]}'?)" if close
else ""))
1348 raise TypeError(f
"{func.__name__}() got an unexpected keyword argument {items[0]}")
1349 raise TypeError(f
"{func.__name__}() got unexpected keyword arguments {', '.join(items)}")
1354 trust_start: bool =
False,
1358 solver: str =
"dist_m4ri",
1359 start_scan: bool =
False
1360) -> Optional[List[int]]:
1362 Validates the expert CC options of a compute_*_distance() call and issues the warnings to stderr.
1365 The normalized start list (with the flag `scan` for start_scan, see _StartList), or None if unset (or not
1366 applicable to the selected solver).
1370 if start_list
is None:
1372 _warn_ignored(
"trust_start=1 has no effect without a start list")
1374 _warn_ignored(
"start_scan=1 has no effect without a start list")
1376 if solver ==
"codedistance":
1377 _warn_ignored(f
"start={_format_start_list(start_list)} is ignored with solver='codedistance'")
1381 if _use_distance_cache
and trust_start:
1382 msg +=
"# trust_start=1: start-list results are accepted as valid and copied to the main cache record\n"
1383 elif _use_distance_cache:
1385 "# results are cached under a separate ':start=...' key; only dmax and codewords update the\n"
1386 "# main cache record (use trust_start=1 to also accept dmin)\n"
1388 sys.stderr.write(msg)
1394 Tightens the bounds (eff_dmin, eff_dmax) with those stored in a cache entry (a CSS code is cached as the
1395 records of its two sectors, see _matrix_cache_key()).
1398 return eff_dmin, eff_dmax
1399 c_dmin = entry.get(
"dmin", 0)
or 0
1400 c_dmax = entry.get(
"dmax", 0)
or 0
1402 eff_dmax = min(eff_dmax, c_dmax)
if eff_dmax > 0
else c_dmax
1404 eff_dmin = max(eff_dmin, c_dmin)
if eff_dmin > 1
else c_dmin
1405 return eff_dmin, eff_dmax
1408def _union_cws(base: Optional[List[List[int]]], extra: Optional[List[List[int]]]) -> List[List[int]]:
1409 """Returns the union of two codeword lists (duplicates removed, sorted by weight)."""
1410 combined = [list(cw)
for cw
in (base
or [])]
1411 seen = {tuple(cw)
for cw
in combined}
1412 for cw
in (extra
or []):
1415 combined.append(list(cw))
1417 combined.sort(key=len)
1422 """The distance reported for the bounds [dmin, dmax]: dmin if exact or without an upper bound, otherwise dmax."""
1423 return dmin
if (dmin == dmax
or dmax == 0)
else dmax
1428 Cache hit test for a classical / quantum / DEM entry: the exact distance, or with the stop target dstop > 0 a
1429 lower bound dmin >= dstop (and codewords if needed).
1431 c_dmin = entry.get(
"dmin", 0)
or 0
1432 if not ((c_dmin > 0
and c_dmin == entry.get(
"dmax"))
or (dstop > 0
and c_dmin >= dstop)):
1434 return (
not need_cws)
or bool(entry.get(
"cws"))
1439 Resolves the qec_cert delegation mode ('off', 'auto', or 'cert') from `solver` and `engine`.
1440 Default ('dist_m4ri', engine=None) returns 'off'.
1442 if engine
is not None:
1443 e = str(engine).strip().lower()
1444 if e
in (
"",
"dist_m4ri",
"m4ri",
"native",
"off",
"0",
"false",
"none"):
1448 if e
in (
"cert",
"qec_cert",
"qec-cert"):
1450 if e ==
"codedistance":
1452 raise ValueError(f
"invalid engine={engine!r}: expected 'dist_m4ri', 'auto', or 'cert'")
1453 s = str(solver
or "dist_m4ri").strip().lower()
1454 if s
in (
"dist_m4ri",
"codedistance"):
1458 if s
in (
"cert",
"qec_cert",
"qec-cert"):
1460 raise ValueError(f
"invalid solver={solver!r}: expected 'dist_m4ri', 'codedistance', 'auto', or 'cert'")
1467 do_cws: bool =
False,
1468 outC: Optional[str] =
None,
1469 finC: Optional[str] =
None,
1470 smax: Optional[int] =
None,
1473 start_list: Optional[List[int]] =
None,
1474 outD: Optional[Any] =
None,
1475 out_reduced: Optional[Any] =
None,
1479 Whether a distance calculation should be delegated to `qec_cert`.
1480 In 'auto' mode, delegates only when safe (no codeword enumeration, finC, smax, start list, outD, out_reduced,
1481 pmin > 0, or single-sided method 1/2, and `qec_cert` is importable).
1487 raise ValueError(
"Codeword extraction is not supported with engine='cert'; use solver='dist_m4ri'.")
1491 method != 3
or finC
or (smax
is not None and smax > 0)
or dW >= 0
or maxC > 0
1492 or start_list
is not None or outD
or out_reduced
or pmin > 0.0
1497 if outD
or out_reduced:
1498 _warn_ignored(
"outD and out_reduced are only used with solver='dist_m4ri'; ignored")
1503 """Converts a matrix (file path, numpy array, or scipy sparse matrix) to a binary CSR matrix for qec_cert."""
1506 if isinstance(M, (str, Path)):
1507 return qec_io.read_mtx(str(M))
1508 return qec_io.binary_csr(M)
1512 """Whether a qec_cert Certificate (or dict) has a conditional lower bound (e.g., unverified symmetry)."""
1513 get = cert.get
if isinstance(cert, dict)
else lambda k, d=
None: getattr(cert, k, d)
1514 if get(
"status") ==
"conditional" or bool(get(
"lb_conditional",
False)):
1516 for s
in get(
"stages", ())
or ():
1517 if isinstance(s, dict)
and s.get(
"lb_conditional"):
1519 for r
in get(
"reductions", ())
or ():
1520 if isinstance(r, dict)
and (r.get(
"conditional")
or (r.get(
"params")
or {}).get(
"lb_conditional")):
1527 Extracts unconditional (dmin, dmax) from a qec_cert Certificate for dist_m4ri reporting and caching:
1528 - An exact result (dmin = dmax = d) only if status == 'exact', witness_verified is True, and not conditional.
1529 - For bounds, an upper bound dmax only if witness_verified is True, and a lower bound dmin only when
1530 status in ('exact', 'bounds', 'timeout') and not lb_conditional.
1532 get = cert.get
if isinstance(cert, dict)
else lambda k, d=
None: getattr(cert, k, d)
1533 status = str(get(
"status",
"")
or "")
1534 wit_ver = get(
"witness_verified")
is True
1536 raw_lb = int(get(
"lower_bound")
or 0)
1537 raw_ub = int(get(
"upper_bound")
or 0)
1540 if status ==
"exact" and wit_ver
and not is_cond
and raw_ub > 0
and raw_lb == raw_ub:
1541 return raw_ub, raw_ub
1542 c_ub = raw_ub
if (wit_ver
and raw_ub > 0)
else 0
1543 c_lb = raw_lb
if (status
in (
"exact",
"bounds",
"timeout")
and not is_cond
and raw_lb > 1)
else 0
1544 if c_ub > 0
and c_lb > c_ub:
1551 True iff the certificate's conditionality is caused solely by an assumed lower bound
1552 lb_assumed_wmin <= certified_dmin (already certified in _distance_cache), with no trust
1553 flags, no precondition violation, and no other conditional stage or reduction.
1555 if certified_dmin <= 1:
1557 get = cert.get
if isinstance(cert, dict)
else lambda k, d=
None: getattr(cert, k, d)
1558 if get(
"status") ==
"conditional" or bool(get(
"precondition_violated",
False)):
1560 if bool(get(
"trust_sym",
False))
or bool(get(
"trust_quot",
False)):
1562 inst = get(
"instance",
None)
1563 inst_d = inst
if isinstance(inst, dict)
else {}
1565 bool(inst_d.get(
"trust_sym",
False))
1566 or bool(inst_d.get(
"trust_quot",
False))
1567 or bool(inst_d.get(
"precondition_violated",
False))
1570 assumed_wmin = int(get(
"lb_assumed_wmin",
None)
or inst_d.get(
"lb_assumed_wmin", 0)
or 0)
1571 if not (1 < assumed_wmin <= certified_dmin):
1573 sectors = inst_d.get(
"sectors")
1574 if isinstance(sectors, dict):
1575 for sec
in sectors.values():
1578 stages = get(
"stages", ())
or ()
1580 isinstance(s, dict)
and s.get(
"name") ==
"polymer_pinning" and s.get(
"shift_verified")
is True
1584 if isinstance(s, dict)
and s.get(
"lb_conditional"):
1585 sname = str(s.get(
"name",
"")
or "")
1586 if bool(s.get(
"trust_sym",
False))
or bool(s.get(
"trust_quot",
False)):
1588 if sname ==
"search":
1589 sym_ok = bool(s.get(
"sym_verified",
True))
or (
1590 s.get(
"sym_kind") ==
"pinned" and pinned_ok
1594 elif sname !=
"p1_projection_lb" and not sname.startswith(
"fold_mod_"):
1596 reductions = get(
"reductions", ())
or ()
1597 pinned_red_ok = pinned_ok
or any(
1599 and r.get(
"name") ==
"polymer_pinning"
1600 and (r.get(
"params")
or {}).get(
"shift_verified")
is True
1603 for r
in reductions:
1604 if not isinstance(r, dict):
1606 rp = r.get(
"params")
or {}
1607 if r.get(
"conditional"):
1609 if rp.get(
"lb_conditional"):
1610 rname = str(r.get(
"name",
"")
or "")
1611 if rname ==
"cc_mitm_search":
1612 sym_ok = bool(rp.get(
"sym_verified",
True))
or (
1613 rp.get(
"sym_kind") ==
"pinned" and pinned_red_ok
1615 wmin_r = int(rp.get(
"wmin", 0)
or 0)
1616 if not sym_ok
or (wmin_r > certified_dmin):
1618 elif rname
not in (
"p1_projection_lb",
"capped_spacetime_folding"):
1624 key_fn: Callable[[], str],
1625 run_cert_fn: Callable[[int, int], Any],
1627 label: str =
"code",
1632 eff_cache_file: Optional[str] =
None,
1634 verbose: bool =
False,
1637 Runs a qec_cert certification callback with dist_m4ri cache lookup and safe cache write-back.
1639 global _distance_cache
1640 code_key: Optional[str] =
None
1641 entry: Optional[Dict[str, Any]] =
None
1642 eff_dmin, eff_dmax = dmin, dmax
1643 if _use_distance_cache:
1647 entry = _distance_cache.get(code_key)
1649 if verbose
or (debug & PY_DBG_CACHE):
1650 c_dmin = entry.get(
"dmin", 0)
or 0
1651 found = (
"exact distance" if c_dmin == entry.get(
"dmax")
else f
"dmin={c_dmin} >= dstop={dstop}")
1652 print(f
"[dist_m4ri] Cache hit for {label} distance ({found} known)!")
1654 "dist": entry[
"dist"],
"dmin": entry[
"dmin"],
"dmax": entry[
"dmax"],
1655 "rw_steps": entry.get(
"rw_steps", 0),
"d_info": list(entry[
"d_info"]),
1656 "cws": list(entry.get(
"cws", [])),
"cached":
True,
1659 w_lo = max(1, eff_dmin)
1660 w_hi = wmax
if wmax > 0
else 40
1662 w_hi = min(w_hi, max(1, eff_dmax - 1))
if wmax > 0
else max(1, eff_dmax - 1)
1664 w_hi = min(w_hi, max(1, dstop - 1))
if (wmax > 0
or eff_dmax > 0)
else max(1, dstop - 1)
1665 cert = run_cert_fn(w_lo, max(w_lo, w_hi))
1667 p_dmin = (entry.get(
"dmin", 0)
or 0)
if entry
else 0
1668 p_dmax = (entry.get(
"dmax", 0)
or 0)
if entry
else 0
1669 p_rw = (entry.get(
"rw_steps", 0)
or 0)
if entry
else 0
1670 p_cws = list(entry.get(
"cws", []))
if entry
else []
1671 cache_dmin = max(p_dmin, c_lb)
1672 cache_dmax = min(p_dmax, c_ub)
if (p_dmax > 0
and c_ub > 0)
else max(p_dmax, c_ub)
1673 if cache_dmax > 0
and cache_dmin > cache_dmax:
1674 cache_dmin = cache_dmax
1675 if _use_distance_cache
and code_key
is not None and (c_lb > 1
or c_ub > 0):
1677 if entry
is not None and "dmin_model" in entry:
1678 record[
"dmin_model"] = entry[
"dmin_model"]
1680 _distance_cache[code_key] = record
1683 dmin_res = max(eff_dmin, cache_dmin)
1684 pos_ub = [x
for x
in (eff_dmax, cache_dmax)
if x > 0]
1685 dmax_res = min(pos_ub)
if pos_ub
else 0
1686 if dmax_res > 0
and dmin_res > dmax_res:
1690 "dist": dist,
"dmin": dmin_res,
"dmax": dmax_res,
"rw_steps": p_rw,
1691 "d_info":
format_bounds_list(dmin_res, dmax_res, p_rw),
"cws": p_cws,
"cached":
False,
1697 src: Dict[str, Any],
1698 trust: bool =
False,
1699 rw_delta: Optional[Dict[str, int]] =
None
1702 Merges the start-list cache record `src` (key plain_key + ':start=...') into the main record `plain_key`.
1704 The upper bound dmax (minimum of the positive values) and the codewords (union) are always valid and
1705 are always merged. Only with `trust` (expert option trust_start) the lower bound dmin is merged as well
1706 (maximum, capped at dmax), and rw_steps are either incremented by the steps of the run, `rw_delta`
1707 (after a calculation), or set to the maximum of the two records (copy of a cached record, rw_delta=None).
1708 (A CSS code is cached as the records of its two sectors, see _matrix_cache_key().)
1711 plain_key: Main cache key.
1712 src: Start-list cache record.
1713 trust: Accept the start-list lower bound as valid.
1714 rw_delta: RW steps of the run, {'': steps}.
1717 True if the main record was created or modified.
1719 global _distance_cache
1720 prev = _distance_cache.get(plain_key)
1721 new: Dict[str, Any] = dict(prev)
if prev
else {}
1722 s_dmin = src.get(
"dmin", 0)
or 0
1723 s_dmax = src.get(
"dmax", 0)
or 0
1724 p_dmin = new.get(
"dmin", 0)
or 0
1725 p_dmax = new.get(
"dmax", 0)
or 0
1726 p_rw = new.get(
"rw_steps", 0)
or 0
1727 dmax = min(p_dmax, s_dmax)
if (p_dmax > 0
and s_dmax > 0)
else (p_dmax
if p_dmax > 0
else s_dmax)
1728 dmin, rw = p_dmin, p_rw
1730 dmin = max(p_dmin, s_dmin)
1731 if rw_delta
is not None:
1732 rw = p_rw + (rw_delta.get(
"", 0)
or 0)
1734 rw = max(p_rw, src.get(
"rw_steps", 0)
or 0)
1735 if dmax > 0
and dmin > dmax:
1737 s_cws = src.get(
"cws", [])
or []
1738 if not prev
and not (trust
or s_dmax > 0
or s_cws):
1740 new[
"cws"] =
_union_cws(new.get(
"cws", []), s_cws)
1742 new[
"dmin"], new[
"dmax"], new[
"rw_steps"] = dmin, dmax, rw
1744 _distance_cache[plain_key] = new
1750 start_list: Optional[List[int]],
1751 is_hit: Callable[[Dict[str, Any]], bool],
1753 merge_copy: Callable[[Dict[str, Any]], bool],
1754 eff_cache_file: Optional[str],
1755 verbose: bool =
False
1756) -> Tuple[str, Optional[Dict[str, Any]], Optional[Dict[str, Any]]]:
1758 Resolves the cache record of a run with an optional expert CC start list.
1760 Without a start list, or if the main record already holds the requested exact result, the main record
1761 is used. Otherwise the separate start-list record (key plain_key + ':start=a,b,c') is used; with
1762 trust_start, an existing exact start-list record is copied to the main record (via `merge_copy`), and
1763 the cache file is saved, even though no calculation is needed.
1766 (code_key, cached_entry, plain_entry): the key and the existing record (or None) for the results of
1767 this run, and the main record (or None) used to seed the bounds of a start-list run.
1769 plain_entry = _distance_cache.get(plain_key)
1771 return plain_key, plain_entry,
None
1772 if plain_entry
is not None and is_hit(plain_entry):
1774 print(f
"[dist_m4ri] Start list not needed: the main record '{plain_key}' has the exact distance")
1775 return plain_key, plain_entry,
None
1777 cached_entry = _distance_cache.get(code_key)
1778 if trust_start
and cached_entry
is not None and is_hit(cached_entry):
1779 if merge_copy(cached_entry)
and eff_cache_file:
1782 print(f
"[dist_m4ri] trust_start: copied the exact start-list record to the main record '{plain_key}'")
1783 return code_key, cached_entry, plain_entry
1787 dist_m4ri_path: Optional[str] =
None,
1789 finH: Optional[str] =
None,
1790 finG: Optional[str] =
None,
1791 finL: Optional[str] =
None,
1792 fin: Optional[str] =
None,
1793 finC: Optional[str] =
None,
1794 fdem: Optional[str] =
None,
1802 steps: Optional[int] =
None,
1803 threads: Optional[int] =
None,
1804 timeout: float = 60.0,
1805 smax: Optional[int] =
None,
1806 start: Optional[Union[int, str, Sequence[int]]] =
None,
1807 cbeg: Optional[int] =
None,
1808 cend: Optional[int] =
None,
1809 css: Optional[int] =
None,
1811 classical: int = -1,
1816 drop: Optional[str] =
None,
1817 outD: Optional[str] =
None,
1818 sort_rows: Optional[bool] =
None,
1819 cc_prune: Optional[int] =
None,
1820 outC: Optional[str] =
None,
1823 nothrottle: bool =
False,
1824 chunk_size: int = 0,
1830 min_hits: Optional[int] =
None,
1832 refresh: Optional[int] =
None,
1833 verbose: bool =
False,
1834 stop_event: Optional[threading.Event] =
None,
1835 warn_start: bool =
True,
1836 warn_ksub: bool =
True,
1837 start_scan: bool =
False,
1839 warn_ortho: bool =
True
1840) -> Tuple[int, int, int]:
1842 Low-level invocation of the multithreaded dist_m4ri binary.
1845 merge: If True (default), the binary merges the equal columns of the DEM fdem (error mechanisms with the
1846 same detectors and observables). Exact: dropping two equal columns of a codeword gives a lighter one,
1847 so the distance does not change. Column indices (finC, outC, start) are those of the DEM; the codewords
1848 found use the first one of each set of equal columns. merge=False passes merge=0 (all columns are
1849 kept, e.g., to enumerate the codewords with each of them). Ignored without fdem.
1850 drop: The detectors of the DEM fdem to drop if this is verified to be exact, passed as drop=...: None (the
1851 binary's default, drop=auto: a search for the largest such set in the DEM), "auto" or "1" (the same), "0"
1852 (keep all detectors), or a file with the detectors to drop (0-based; otherwise, the binary uses the full DEM
1853 and prints a note, see _parse_stderr_stats()). Ignored without fdem.
1854 outD: File to which the binary writes the detectors dropped from the DEM fdem (0-based; only the comment line
1855 '# N of M detectors dropped ...' if none, see read_drop_file()). Ignored without fdem.
1857 Orthogonality check:
1858 ortho: If True (default), the binary checks that the rows of H and G (finG, or fin without finL) are
1859 orthogonal (H G^T = 0) and stops with an error otherwise. False (expert option, since version 0.15.2)
1860 passes ortho=0: the check is skipped, e.g., to screen many codes which are orthogonal by construction; the
1861 result is then valid only if H G^T = 0. No effect without G. The cache key does not depend on it.
1862 warn_ortho: If True (default), issue the warning for ortho=False to stderr (see _warn_ortho()).
1865 sort_rows: Passed as sort_rows=1 (True) or sort_rows=0 (False); None (default): the binary's default, i.e.,
1866 1 for matrices and 0 for a DEM. With sort_rows=1, the binary sorts the rows of H by increasing weight
1867 (stable). With cc_prune=0, this reduces the CC work for matrices with very different row weights (e.g.,
1868 1.7-4x with the hook columns of an extended CSS distance), while the detectors of a DEM are in time
1869 order, which is better for CC (use sort_rows=False also for matrices exported from a DEM); with the
1870 default cc_prune=1, the order of the rows only breaks ties. The distance and RW do not depend on the
1871 order of the rows; CC enumerates the same codewords, possibly in another order (with cc_prune=0, also with
1875 cc_prune: Passed as cc_prune=N if not None (0, 1, or 2; None, the default: the binary's default 1). The CC
1876 search with smax=0: 0, the walk of the versions before 0.15.0 (a cluster is extended by the columns of the
1877 first unsatisfied row of its syndrome); 1, the branch and bound (the unsatisfied row with the fewest
1878 available columns, the columns of the earlier branches forbidden in the later ones, so that each codeword
1879 is found once, and pruning by lower bounds on the number of columns needed: an unsatisfied row without
1880 available columns, a packing of unsatisfied rows with disjoint sets of available columns); 2, also the
1881 coverage and fractional cover bounds. The same codewords and distances (doc/cc_completeness.tex); with
1882 the expert start list (and start_scan=False), cc_prune=1 and 2 forbid the columns listed before the start
1883 column. The cache key does not depend on it.
1886 kwin / win_mode: A window of kwin columns in every RW step (win_mode=0: a Tanner graph ball, 1: an index
1887 window). kwin=0 (default): the automatic hybrid for n >= 500, an index window of win_hyb columns in every
1888 other step and uniform random permutations in the others. kwin=-1 (any negative value): no windows,
1889 uniform random permutations in every step.
1890 win_hyb: Window size of the hybrid RW steps, passed as win_hyb=N if nonzero; 0 (default): 256 columns for a
1891 DEM, 512 for matrices (at most 3n/4). The windows of 192-256 columns find the minimum-weight codewords of
1892 circuit DEMs 2-10 times faster than 512, and windows of one round of columns are much slower. With
1893 debug & 1 (or verbose=True), the binary prints the rounds of a DEM (estimated from the time translation of
1894 its errors: D detectors and L columns per round, also in _last_run_stats["rounds"]) and the codewords found
1895 by the uniform and by the window steps (see _parse_stderr_stats()).
1898 pairs: The RW pairs of non-pivot columns (p=2, as in Lee-Brickell and Stern), passed as pairs=N if not -1: a
1899 RW step also finds the codewords with two non-pivot columns, checking only the pairs which agree on N
1900 random pivot rows (0: off). -1 (default): auto, about one non-pivot column per bucket, with fewer rows
1901 while a codeword of the weight of the upper bound would be kept with probability below 0.8, but enough
1902 rows to keep the pairs of a step within about the work of its elimination (e.g., at least 6 for large
1903 circuit DEMs while the upper bound is far above the distance). The pairs find the minimum-weight
1904 codewords of circuit DEMs 7-26 times faster (per wall time) and lighter codewords of codes with a large
1905 distance. With debug & 1, the binary prints the pairs checked per step and the codewords found by pairs
1906 and by single columns (also in _last_run_stats). The pairs of disjoint codewords are skipped (their sum
1907 is reducible: the binary records only irreducible codewords, its default irred=1).
1909 RW stopping criterion:
1910 min_hits: Passed as min_hits=N if not None and >= 0 (None, the default, or a negative value: the binary's
1911 default 5): RW ends once the average number of hits <n> per codeword reaches N, both for the codewords
1912 of the minimum weight found and for a representative set of cov_cws lowest-weight codewords (the hits of
1913 all RW steps, with or without windows, and those of CC and finC count). 0: off; then, unless the
1914 codewords are collected (outC or maxC), RW records only the codewords lighter than the upper bound, so
1915 that the repeated hits are not counted.
1916 cov_cws: Size of the representative set of lowest-weight codewords for min_hits, passed as cov_cws=N if not
1917 100 (the default, as in the binary; 0: only the minimum weight).
1920 dstop: The run (method=2 or 3) ends once CC has certified dmin >= dstop, i.e., that there is no codeword of
1921 weight < dstop, unless a lighter codeword is found (default: 0 = off). Unlike dmax, dstop is not an
1922 upper bound and is not reported as dmax: the result is (dmin, w, rw_steps), where w is the weight of the
1923 lightest codeword found (0: none).
1926 start: List of CC start columns (int, comma-separated string "a,b,c", or sequence of ints;
1927 None or a single negative value: all columns), passed as start=a,b,c (sorted, distinct).
1928 CC clusters are grown only from the listed columns, and they are not limited to larger
1929 column indices. WARNING: the returned dmin (and an exact result) is valid only if a code
1930 symmetry maps every minimum-weight codeword to one containing a listed column (e.g., one
1931 column per block of a quasi-cyclic code); dmax is always valid.
1932 start_scan: With start (expert option, since version 0.15.1): passed as start_scan=1, so that a cluster
1933 grown from a listed column c only uses the columns > c, as in the scan of all columns (not with fdem).
1934 WARNING: dmin is then valid only if a code symmetry maps every minimum-weight codeword to one whose
1935 smallest column is listed, e.g., if the listed columns are the smallest columns of the orbits of a
1936 group of code automorphisms, one per orbit (start=[0, l] for the two blocks of a two-block
1937 group-algebra code of length 2l over an abelian group). The later start columns then need much less
1938 work, the least if each orbit is a range of consecutive columns.
1939 warn_start: If True (default), issue the start-list warning to stderr.
1940 noscan / cbeg / cend: Disabled in the Python interface: accepted for backward compatibility,
1941 but ignored with a warning to stderr (use the dist_m4ri binary directly).
1943 Experimental RW options:
1944 ksub: Experimental, should not be used: subspace dimension sampled from ker(H) in RW (0: full-matrix RW).
1945 A RW step reduces the span of ksub rows of a fixed basis of ker(H), so that a few codewords are found
1946 much more often than others (min_hits may end RW early).
1947 refresh: RW steps between the basis refreshes of the experimental ksub RW (passed if not None; None, the
1948 default: the binary's 5000 with ksub > 0; 0: no refresh).
1949 warn_ksub: If True (default), issue the warning for ksub > 0 to stderr (with method=1 or 3).
1952 debug: Debug bitmap; the bits in BIN_DBG_MASK are passed to the binary (its diagnostic output on stderr
1953 is printed), and PY_DBG_COMMANDS echoes the command line and the run time. verbose=True implies
1954 the bits VERBOSE_DEBUG.
1957 stop_event: Optional threading.Event; once it is set, the binary is terminated and RuntimeError is raised
1958 (the binary is also terminated on any other exception, e.g., KeyboardInterrupt).
1961 tuple (dmin, dmax, rw_steps)
1963 global _last_run_stats
1968 if start_scan
and not start_list:
1969 _warn_ignored(
"start_scan=1 has no effect without a start list")
1970 if start_scan
and start_list
and fdem:
1971 raise ValueError(
"start_scan=1 is not available with fdem (the equal columns of a DEM are merged)")
1973 if start_list
and warn_start:
1977 has_g =
not fdem
and bool(finG
or (fin
and not finL))
1983 if cc_prune
is not None and cc_prune
not in (0, 1, 2):
1984 raise ValueError(f
"invalid cc_prune={cc_prune!r}: cc_prune should be 0, 1, or 2")
1986 if method == 2
and wmax <= 0:
1989 elif timeout <= 0.0
and dstop <= 0:
1991 "either parameter wmax>0, dmax>0, dstop>0, or timeout>0 should be specified for CC method=2."
1994 eff_debug = (debug | VERBOSE_DEBUG)
if verbose
else debug
1995 bin_debug = eff_debug & BIN_DBG_MASK
1996 cmd = [exec_path, f
"debug={bin_debug}", f
"method={method}"]
1998 if finH: cmd.append(f
"finH={finH}")
1999 if finG: cmd.append(f
"finG={finG}")
2000 if finL: cmd.append(f
"finL={finL}")
2001 if fin: cmd.append(f
"fin={fin}")
2002 if has_g
and not ortho: cmd.append(
"ortho=0")
2003 if finC: cmd.append(f
"finC={finC}")
2004 if fdem: cmd.append(f
"fdem={fdem}")
2005 if dmin > 0: cmd.append(f
"dmin={dmin}")
2006 if dmax > 0: cmd.append(f
"dmax={dmax}")
2007 if dstop > 0: cmd.append(f
"dstop={dstop}")
2008 if wmax > 0: cmd.append(f
"wmax={wmax}")
2009 if wmin
is not None and wmin != 1: cmd.append(f
"wmin={wmin}")
2010 if dexp > 0: cmd.append(f
"dexp={dexp}")
2011 elif dest > 0: cmd.append(f
"dest={dest}")
2012 if steps
is not None and steps >= 0: cmd.append(f
"steps={steps}")
2013 if threads
is not None and threads > 0: cmd.append(f
"threads={threads}")
2014 if timeout
is not None and timeout >= 0: cmd.append(f
"timeout={timeout}")
2015 if smax
is not None: cmd.append(f
"smax={smax}")
2016 if start_list: cmd.append(
"start=" +
",".join(str(c)
for c
in start_list))
2017 if start_list
and start_scan: cmd.append(
"start_scan=1")
2018 if css
is not None: cmd.append(f
"css={css}")
2019 if classical >= 0: cmd.append(f
"classical={classical}")
2020 if dW >= 0: cmd.append(f
"dW={dW}")
2021 if maxC > 0: cmd.append(f
"maxC={maxC}")
2022 if pmin > 0.0: cmd.append(f
"pmin={pmin}")
2023 if fdem
and not merge: cmd.append(
"merge=0")
2024 if fdem
and drop: cmd.append(f
"drop={drop}")
2025 if fdem
and outD: cmd.append(f
"outD={outD}")
2026 if sort_rows
is not None: cmd.append(f
"sort_rows={1 if sort_rows else 0}")
2027 if cc_prune
is not None: cmd.append(f
"cc_prune={int(cc_prune)}")
2028 if outC: cmd.append(f
"outC={outC}")
2029 if seed != 0: cmd.append(f
"seed={seed}")
2030 if nothrottle: cmd.append(
"nothrottle=1")
2031 if chunk_size > 0: cmd.append(f
"chunk_size={chunk_size}")
2032 if ksub > 0: cmd.append(f
"ksub={ksub}")
2033 if kwin != 0: cmd.append(f
"kwin={kwin}")
2034 if win_mode != 0: cmd.append(f
"win_mode={win_mode}")
2035 if win_hyb != 0: cmd.append(f
"win_hyb={win_hyb}")
2036 if pairs != -1: cmd.append(f
"pairs={pairs}")
2037 if min_hits
is not None and min_hits >= 0: cmd.append(f
"min_hits={min_hits}")
2038 if cov_cws != 100: cmd.append(f
"cov_cws={cov_cws}")
2039 if refresh
is not None: cmd.append(f
"refresh={refresh}")
2041 if eff_debug & PY_DBG_COMMANDS:
2042 print(f
"[dist_m4ri] Running: {' '.join(cmd)}")
2044 t_start = time.time()
2045 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=
True)
2047 if stop_event
is None:
2048 stdout, stderr = proc.communicate()
2052 stdout, stderr = proc.communicate(timeout=0.05)
2054 except subprocess.TimeoutExpired:
2055 if stop_event.is_set():
2056 raise RuntimeError(
"dist_m4ri execution cancelled by stop_event")
2057 except BaseException:
2060 proc.communicate(timeout=1.0)
2069 if eff_debug & PY_DBG_COMMANDS:
2070 print(f
"[dist_m4ri] Finished in {time.time() - t_start:.3f} s (exit code {proc.returncode})")
2072 if proc.returncode != 0:
2073 raise RuntimeError(f
"dist_m4ri failed with exit code {proc.returncode}:\n{stderr}")
2076 if (verbose
or bin_debug > 0)
and stderr:
2077 print(stderr.rstrip())
2082_MTX_PY_MAX_NNZ = 1 << 16
2087 The shape (m, n) and the nonzero entries (0-based row and column index arrays, in row-major order) of a binary
2088 matrix given as a numpy array (or an array-like, e.g., a list of lists) or a scipy sparse matrix (or array): an
2089 entry is nonzero iff its value is odd (floats are truncated to integers, and the duplicate entries of a sparse
2090 matrix are summed first), as the dist_m4ri binary reads the integer entries of a MatrixMarket file.
2094 def odd(v: Any) -> Any:
2095 if v.dtype == np.bool_:
2097 if not np.issubdtype(v.dtype, np.integer):
2098 v = v.astype(np.int64)
2100 return v.view(np.bool_)
if v.itemsize == 1
else v != 0
2102 if hasattr(matrix,
"tocsr"):
2103 csr = matrix.tocsr()
2104 if not csr.has_canonical_format:
2105 csr = csr.copy()
if csr
is matrix
else csr
2106 csr.sum_duplicates()
2108 keep = odd(csr.data)
2109 rows = np.repeat(np.arange(m, dtype=np.int64), np.diff(csr.indptr))[keep]
2110 return m, n, rows, csr.indices[keep]
2111 A = np.asarray(matrix)
2113 raise ValueError(f
"a matrix should have two dimensions, got the shape {A.shape}")
2115 if m == 0
or n == 0:
2116 empty = np.zeros(0, dtype=np.int64)
2117 return m, n, empty, empty
2118 rows, cols = np.divmod(np.flatnonzero(odd(A)), n)
2119 return m, n, rows, cols
2124 Writes a binary matrix (numpy array, array-like, or scipy sparse matrix; see _gf2_entries()) to the file `path` in
2125 the MatrixMarket coordinate pattern format (the 1-based indices of the nonzero entries, in row-major order), which
2126 the dist_m4ri binary reads. With up to _MTX_PY_MAX_NNZ nonzero entries, the file is formatted in Python (e.g.,
2127 about 1 ms for 6000 entries); larger matrices are written by scipy.io.mmwrite, which is faster per entry, but has a
2128 fixed cost of about 10 ms per call on a 128-core machine (the thread pool of its parallel writer).
2133 if nnz > _MTX_PY_MAX_NNZ:
2134 from scipy.io
import mmwrite
2135 from scipy.sparse
import coo_matrix
2136 mmwrite(path, coo_matrix((np.ones(nnz, dtype=np.int8), (rows, cols)), shape=(m, n)), field=
"pattern",
2139 rc = np.empty(2 * nnz, dtype=np.int64)
2140 rc[0::2], rc[1::2] = rows, cols
2142 with open(path,
"w")
as f:
2143 f.write(f
"%%MatrixMarket matrix coordinate pattern general\n{m} {n} {nnz}\n")
2144 f.write((
"%d %d\n" * nnz) % tuple(rc.tolist()))
2149 The MatrixMarket file of a matrix for the dist_m4ri binary: a file path is returned as is; a numpy array (or an
2150 array-like) or a scipy sparse matrix is written to a new temporary file in temp_dir with _write_mtx_gf2() (its
2151 nonzero entries over GF(2), i.e., those with an odd value; before version 0.15.2, scipy.io.mmwrite wrote the int8
2152 entries, which took 10-13 ms per matrix with up to 1000 columns on a 128-core machine).
2154 if isinstance(matrix, (str, Path)):
2163 Checks if a Stim circuit has noise: a noise channel (e.g., DEPOLARIZE1, X_ERROR, PAULI_CHANNEL_2, E, or
2164 HERALDED_ERASE), or a measurement with a flip probability (e.g., M(0.001) or MPP(0.01)), i.e., if strip_noise()
2171 if not isinstance(circuit, stim.Circuit):
2173 return circuit.without_noise() != circuit
2178 Removes all noise from a Stim circuit (stim.Circuit or a .stim file), see stim.Circuit.without_noise(): the noise
2179 channels (e.g., DEPOLARIZE1, X_ERROR, PAULI_CHANNEL_2, E) are removed, the measurements lose their flip
2180 probabilities (M(0.001) becomes M), and heralded noise (e.g., HERALDED_ERASE) is replaced by MPAD, so that the
2181 measurement record does not change. E.g., add_noise(strip_noise(circuit), p) (or add_noise(circuit, p,
2182 force=True); CLI: --force-noise) gives circuits with the same noise model.
2185 The noiseless stim.Circuit.
2188 if isinstance(circuit, (str, Path)):
2189 circuit = stim.Circuit.from_file(str(circuit))
2190 return circuit.without_noise()
2194 circuit: Any, p: float, num_qubits: int, active_qubits: Set[int]
2196 """Recursively adds uniform circuit-level noise to a Stim circuit (see add_noise())."""
2199 noisy_circuit = stim.Circuit()
2200 for inst
in circuit:
2201 if isinstance(inst, stim.CircuitRepeatBlock):
2203 inst.body_copy(), p, num_qubits, active_qubits
2205 noisy_circuit.append(
2206 stim.CircuitRepeatBlock(inst.repeat_count, noisy_body)
2211 idle_qubits = set(range(num_qubits)) - active_qubits
2213 noisy_circuit.append(
"DEPOLARIZE1", sorted(idle_qubits), p1)
2214 noisy_circuit.append(inst)
2215 active_qubits.clear()
2217 gd = stim.gate_data(name)
2218 noise_channel = gd.is_noisy_gate
and (
not gd.produces_measurements
or 0
not in gd.num_parens_arguments_range)
2219 if name ==
"MPAD" or noise_channel
or not (gd.is_unitary
or gd.is_reset
or gd.produces_measurements):
2222 noisy_circuit.append(inst)
2224 targets = inst.targets_copy()
2226 qubits = [t.qubit_value
for t
in targets
if t.qubit_value
is not None]
2227 active_qubits.update(qubits)
2228 flip =
"Z_ERROR" if name.endswith(
"X")
else "X_ERROR"
2229 if gd.produces_measurements
and gd.is_single_qubit_gate:
2231 noisy_circuit.append(flip, qubits, p)
2232 noisy_circuit.append(inst)
2233 if gd.is_reset
and qubits:
2234 noisy_circuit.append(flip, qubits, p)
2235 elif gd.is_reset
and not gd.produces_measurements:
2236 noisy_circuit.append(inst)
2238 noisy_circuit.append(flip, qubits, p)
2239 elif gd.is_unitary
and gd.is_single_qubit_gate:
2240 noisy_circuit.append(inst)
2242 noisy_circuit.append(
"DEPOLARIZE1", qubits, p1)
2243 elif gd.is_unitary
and gd.is_two_qubit_gate:
2244 noisy_circuit.append(inst)
2245 pair_qubits: List[int] = []
2246 ctrl_qubits: List[int] = []
2247 for a, b
in zip(targets[::2], targets[1::2]):
2248 if a.qubit_value
is not None and b.qubit_value
is not None:
2249 pair_qubits += [a.qubit_value, b.qubit_value]
2251 ctrl_qubits += [t.qubit_value
for t
in (a, b)
if t.qubit_value
is not None]
2253 noisy_circuit.append(
"DEPOLARIZE2", pair_qubits, p)
2255 noisy_circuit.append(
"DEPOLARIZE1", ctrl_qubits, p1)
2257 noisy_circuit.append(inst)
2258 return noisy_circuit
2261def add_noise(circuit: Any, p: float = 0.001, force: bool =
False) -> Any:
2263 Adds uniform circuit-level noise of strength p to a Stim circuit (the gates are classified by stim.gate_data()):
2264 - DEPOLARIZE2(p) after two-qubit gates (CX, CY, CZ, XCX, XCY, XCZ, YCX, YCY, YCZ, SWAP, ISWAP, ISWAP_DAG, SQRT_XX,
2265 SQRT_YY, SQRT_ZZ and their _DAG versions, CXSWAP, SWAPCX, CZSWAP, II); a classically controlled gate (e.g.,
2266 CX rec[-1] 5 or CZ sweep[0] 5) gets DEPOLARIZE1(p/10) on its qubit;
2267 - DEPOLARIZE1(p/10) after one-qubit gates (I, X, Y, Z, H, H_XY, H_YZ, S, S_DAG, SQRT_X, SQRT_Y and their _DAG
2268 versions, C_XYZ, C_ZYX, and the other variants), and on the qubits idle since the previous TICK (at each TICK,
2269 and at the end of the circuit);
2270 - X (Z) flips with probability p after Z-basis or Y-basis (X-basis) resets and before measurements, both before and
2271 after a measurement with reset (MR, MRX, MRY);
2272 - none for the multi-qubit measurements (MPP, MXX, MYY, MZZ) and the Pauli product gates (SPP, SPP_DAG).
2273 The noise of a noisy circuit is kept and the uniform noise is added to it, unless force=True: then it is replaced
2274 (see strip_noise()).
2277 circuit: stim.Circuit or path to a .stim file.
2278 p: Noise strength (for the distance, only which errors are possible matters); p=0: no noise is added.
2279 force: If True, all noise of the circuit is removed first (with p=0: only that).
2282 The noisy stim.Circuit (a copy).
2285 if isinstance(circuit, (str, Path)):
2286 circuit = stim.Circuit.from_file(str(circuit))
2290 return circuit.copy()
2291 num_qubits = circuit.num_qubits
2292 active_qubits: Set[int] = set()
2295 final_idle_qubits = set(range(num_qubits)) - active_qubits
2296 if final_idle_qubits:
2297 noisy_circuit.append(
"DEPOLARIZE1", sorted(final_idle_qubits), 0.1 * p)
2298 return noisy_circuit
2302 """Removes DETECTOR instructions that have no measurement record targets."""
2304 new_circuit = stim.Circuit()
2306 for inst
in circuit:
2307 if isinstance(inst, stim.CircuitRepeatBlock):
2309 removed += r * inst.repeat_count
2310 new_circuit.append(stim.CircuitRepeatBlock(inst.repeat_count, body))
2311 elif inst.name ==
"DETECTOR":
2313 t
for t
in inst.targets_copy()
2314 if t.is_measurement_record_target
2319 new_circuit.append(inst)
2321 new_circuit.append(inst)
2322 return new_circuit, removed
2326 """Checks if a Stim circuit contains at least one REPEAT block."""
2328 if isinstance(circuit, (str, Path)):
2329 circuit = stim.Circuit.from_file(str(circuit))
2330 if not isinstance(circuit, stim.Circuit):
2332 for inst
in circuit:
2333 if isinstance(inst, stim.CircuitRepeatBlock):
2339 """Sets the repeat_count of CircuitRepeatBlocks in a Stim circuit.
2341 For circuits with a single repeat block, sets its repeat_count to rounds.
2342 For circuits with two repeat blocks (e.g. preamble + periodic body),
2343 adjusts them to achieve total requested rounds.
2346 circuit: A stim.Circuit instance.
2347 rounds: The new repetition count to set for repeat blocks. If <= 0,
2348 repeat blocks are omitted.
2351 A new stim.Circuit instance with modified repeat counts.
2354 if isinstance(circuit, (str, Path)):
2355 circuit = stim.Circuit.from_file(str(circuit))
2356 if not isinstance(circuit, stim.Circuit):
2359 raise ValueError(f
"Invalid rounds={rounds}; must be >= 0.")
2362 inst.repeat_count
for inst
in circuit
2363 if isinstance(inst, stim.CircuitRepeatBlock)
2365 if len(top_blocks) > 1:
2367 f
"# Warning: Circuit has {len(top_blocks)} repeat blocks "
2368 f
"({top_blocks}) which may not be handled correctly by this "
2372 new_circuit = stim.Circuit()
2373 if len(top_blocks) == 2:
2374 preamble = top_blocks[0]
2375 b1_count = min(preamble, rounds)
2376 b2_count = max(0, rounds - preamble)
2378 for inst
in circuit:
2379 if isinstance(inst, stim.CircuitRepeatBlock):
2380 cnt = b1_count
if block_idx == 0
else b2_count
2384 new_circuit.append(stim.CircuitRepeatBlock(cnt, new_body))
2386 new_circuit.append(inst)
2388 for inst
in circuit:
2389 if isinstance(inst, stim.CircuitRepeatBlock):
2393 stim.CircuitRepeatBlock(rounds, new_body)
2396 new_circuit.append(inst)
2401 """Returns total repeat count of all repeat blocks in circuit, or 1 if unrolled."""
2403 if isinstance(circuit, (str, Path)):
2404 circuit = stim.Circuit.from_file(str(circuit))
2405 if not isinstance(circuit, stim.Circuit):
2408 inst.repeat_count
for inst
in circuit
2409 if isinstance(inst, stim.CircuitRepeatBlock)
2413 f
"# Warning: Circuit has {len(blocks)} repeat blocks "
2414 f
"({blocks}) which may not be handled correctly by this "
2417 return sum(blocks)
if blocks
else 1
2422 filepath: Optional[Union[str, Path]] =
None,
2423 basis_arg: Optional[str] =
None
2425 """Determines the primary memory basis ('X' or 'Z') of a Stim circuit."""
2426 if basis_arg
is not None and str(basis_arg).upper()
in [
"X",
"Z"]:
2427 return str(basis_arg).upper()
2429 if filepath
is not None:
2430 stem = Path(filepath).stem.upper()
2431 parts = stem.replace(
"-",
"_").split(
"_")
2432 if "X" in parts
or "HX" in parts
or stem.endswith(
"X"):
2434 if "Z" in parts
or "HZ" in parts
or stem.endswith(
"Z"):
2438 if isinstance(circuit, (str, Path)):
2439 circuit = stim.Circuit.from_file(str(circuit))
2443 for inst
in circuit:
2444 if isinstance(inst, stim.CircuitRepeatBlock):
2446 if inst.name ==
"RX":
2447 rx_count += len(inst.targets_copy())
2448 elif inst.name
in [
"R",
"RZ"]:
2449 rz_count += len(inst.targets_copy())
2450 elif inst.name
in [
"M",
"MX",
"MZ",
"MR",
"MRX",
"MRZ"]:
2452 if rx_count > rz_count:
2454 if rz_count > rx_count:
2459 for inst
in reversed(list(circuit)):
2460 if isinstance(inst, stim.CircuitRepeatBlock):
2462 if inst.name
in [
"MX",
"MRX"]:
2463 mx_count += len(inst.targets_copy())
2464 elif inst.name
in [
"M",
"MZ",
"MR",
"MRZ"]:
2465 mz_count += len(inst.targets_copy())
2466 if mx_count > mz_count:
2472 """Dynamically tracks logical syndrome carriers across the circuit to measurements.
2474 Accounts for physical qubit permutations via SWAP, CXSWAP, CZSWAP, ISWAP,
2475 and ISWAP_DAG gates, as well as CNOT/CZ syndrome handoffs onto fresh ancilla
2479 meas_carriers: List mapping each measurement index to a list of initial
2480 logical qubit IDs whose syndrome or state is carried into that
2484 if isinstance(circuit, (str, Path)):
2485 circuit = stim.Circuit.from_file(str(circuit))
2486 num_qubits = circuit.num_qubits
2487 phys_to_log = {q: q
for q
in range(num_qubits)}
2489 tot_resets: Dict[int, int] = {q: 0
for q
in range(num_qubits)}
2490 tot_meas: Dict[int, int] = {q: 0
for q
in range(num_qubits)}
2492 def pass1(block: Any, p_to_l: Dict[int, int], mult: int = 1) ->
None:
2494 if isinstance(inst, stim.CircuitRepeatBlock):
2495 rep_count = inst.repeat_count
2496 body = inst.body_copy()
2497 map_copy = dict(p_to_l)
2498 for sub_inst
in body:
2499 if not isinstance(sub_inst, stim.CircuitRepeatBlock):
2500 if sub_inst.name
in [
2501 "SWAP",
"CXSWAP",
"CZSWAP",
"ISWAP",
"ISWAP_DAG"
2504 t.value
for t
in sub_inst.targets_copy()
2505 if t.is_qubit_target
2507 for idx
in range(0, len(t_vals), 2):
2508 q1, q2 = t_vals[idx], t_vals[idx + 1]
2509 map_copy[q1], map_copy[q2] = (
2510 map_copy[q2], map_copy[q1]
2512 if map_copy == p_to_l:
2513 pass1(body, p_to_l, mult * rep_count)
2515 for _
in range(rep_count):
2516 pass1(body, p_to_l, mult)
2520 t.value
for t
in inst.targets_copy()
if t.is_qubit_target
2523 "R",
"RX",
"RY",
"RZ",
"MR",
"MRX",
"MRY",
"MRZ"
2526 tot_resets[p_to_l[q]] += mult
2528 "M",
"MX",
"MY",
"MZ",
"MR",
"MRX",
"MRY",
"MRZ"
2531 tot_meas[p_to_l[q]] += mult
2532 if name
in [
"SWAP",
"CXSWAP",
"CZSWAP",
"ISWAP",
"ISWAP_DAG"]:
2533 for idx
in range(0, len(t_vals), 2):
2534 q1, q2 = t_vals[idx], t_vals[idx + 1]
2535 p_to_l[q1], p_to_l[q2] = p_to_l[q2], p_to_l[q1]
2537 pass1(circuit, dict(phys_to_log), 1)
2538 max_resets = max(tot_resets.values())
if tot_resets
else 0
2539 max_meas = max(tot_meas.values())
if tot_meas
else 0
2541 for q
in range(num_qubits):
2543 if tot_resets[q] <= 1:
2546 if tot_meas[q] <= 1:
2549 active_carriers: Dict[int, Set[int]] = {
2550 q: {q}
for q
in range(num_qubits)
2552 fresh_anc: Set[int] = set(range(num_qubits)) - data_log
2553 meas_carriers: List[List[int]] = []
2555 def pass2(block: Any, p_to_l: Dict[int, int]) ->
None:
2557 if isinstance(inst, stim.CircuitRepeatBlock):
2558 rep_count = inst.repeat_count
2559 body = inst.body_copy()
2562 map_copy = dict(p_to_l)
2563 for sub_inst
in body:
2564 if not isinstance(sub_inst, stim.CircuitRepeatBlock):
2565 if sub_inst.name
in [
2566 "SWAP",
"CXSWAP",
"CZSWAP",
"ISWAP",
"ISWAP_DAG"
2569 t.value
for t
in sub_inst.targets_copy()
2570 if t.is_qubit_target
2572 for idx
in range(0, len(t_vals), 2):
2573 q1, q2 = t_vals[idx], t_vals[idx + 1]
2574 map_copy[q1], map_copy[q2] = (
2575 map_copy[q2], map_copy[q1]
2577 if map_copy == p_to_l:
2578 idx0 = len(meas_carriers)
2580 idx1 = len(meas_carriers)
2581 if rep_count > 1
and idx1 > idx0:
2582 body_carriers = meas_carriers[idx0:idx1]
2583 meas_carriers.extend(body_carriers * (rep_count - 1))
2585 for _
in range(rep_count):
2590 t.value
for t
in inst.targets_copy()
if t.is_qubit_target
2592 if name
in [
"R",
"RX",
"RY",
"RZ"]:
2595 active_carriers[lq] = {lq}
2596 if lq
not in data_log:
2598 elif name
in [
"M",
"MX",
"MY",
"MZ"]:
2601 meas_carriers.append(sorted(list(active_carriers[lq])))
2602 elif name
in [
"MR",
"MRX",
"MRY",
"MRZ"]:
2605 meas_carriers.append(sorted(list(active_carriers[lq])))
2606 active_carriers[lq] = {lq}
2607 if lq
not in data_log:
2610 "CX",
"ZCX",
"CY",
"ZCY",
"CZ",
"ZCZ",
2611 "XCX",
"XCY",
"XCZ",
"YCX",
"YCY",
"YCZ"
2613 for idx
in range(0, len(t_vals), 2):
2614 q1, q2 = t_vals[idx], t_vals[idx + 1]
2615 l1, l2 = p_to_l[q1], p_to_l[q2]
2616 if l1
not in data_log
and l2
not in data_log:
2617 if l2
in fresh_anc
and l1
not in fresh_anc:
2618 active_carriers[l2] = set(active_carriers[l1])
2619 fresh_anc.discard(l2)
2620 elif l1
in fresh_anc
and l2
not in fresh_anc:
2621 active_carriers[l1] = set(active_carriers[l2])
2622 fresh_anc.discard(l1)
2624 comb = active_carriers[l1] | active_carriers[l2]
2625 active_carriers[l1] = set(comb)
2626 active_carriers[l2] = set(comb)
2627 fresh_anc.discard(l1)
2628 fresh_anc.discard(l2)
2629 elif l1
in data_log
and l2
not in data_log:
2630 fresh_anc.discard(l2)
2631 elif l2
in data_log
and l1
not in data_log:
2632 fresh_anc.discard(l1)
2633 elif name ==
"SWAP":
2634 for idx
in range(0, len(t_vals), 2):
2635 q1, q2 = t_vals[idx], t_vals[idx + 1]
2636 p_to_l[q1], p_to_l[q2] = p_to_l[q2], p_to_l[q1]
2637 elif name
in [
"CXSWAP",
"CZSWAP",
"ISWAP",
"ISWAP_DAG"]:
2638 for idx
in range(0, len(t_vals), 2):
2639 q1, q2 = t_vals[idx], t_vals[idx + 1]
2640 l1, l2 = p_to_l[q1], p_to_l[q2]
2641 if l1
in data_log
and l2
not in data_log:
2642 fresh_anc.discard(l2)
2643 elif l2
in data_log
and l1
not in data_log:
2644 fresh_anc.discard(l1)
2645 p_to_l[q1], p_to_l[q2] = p_to_l[q2], p_to_l[q1]
2647 pass2(circuit, dict(phys_to_log))
2648 return meas_carriers
2653 phys_to_log: Dict[int, int],
2654 active_gates_count: Dict[int, int]
2656 """Rewrites a Stim circuit by tracking SWAPs virtually."""
2658 new_circ = stim.Circuit()
2659 non_active_instructions = {
2660 "QUBIT_COORDS",
"SHIFT_COORDS",
"TICK",
2661 "DETECTOR",
"OBSERVABLE_INCLUDE",
"MPAD"
2665 if isinstance(inst, stim.CircuitRepeatBlock):
2666 rep_count = inst.repeat_count
2667 body = inst.body_copy()
2668 map_copy = dict(phys_to_log)
2669 for sub_inst
in body:
2670 if not isinstance(sub_inst, stim.CircuitRepeatBlock):
2671 if sub_inst.name
in [
2672 "SWAP",
"CXSWAP",
"CZSWAP",
"ISWAP",
"ISWAP_DAG"
2674 targets = sub_inst.targets_copy()
2675 for idx
in range(0, len(targets), 2):
2676 q1 = targets[idx].value
2677 q2 = targets[idx + 1].value
2678 map_copy[q1], map_copy[q2] = (
2679 map_copy[q2], map_copy[q1]
2681 if map_copy == phys_to_log:
2683 body, phys_to_log, active_gates_count
2685 new_circ.append(stim.CircuitRepeatBlock(rep_count, new_body))
2687 for _
in range(rep_count):
2689 body, phys_to_log, active_gates_count
2691 new_circ += unrolled_body
2695 targets = inst.targets_copy()
2696 args = inst.gate_args_copy()
2699 for idx
in range(0, len(targets), 2):
2700 q1 = targets[idx].value
2701 q2 = targets[idx + 1].value
2702 phys_to_log[q1], phys_to_log[q2] = (
2703 phys_to_log[q2], phys_to_log[q1]
2705 elif name ==
"CXSWAP":
2707 for idx
in range(0, len(targets), 2):
2708 q1 = targets[idx].value
2709 q2 = targets[idx + 1].value
2710 l1 = phys_to_log[q1]
2711 l2 = phys_to_log[q2]
2712 cx_targets.extend([l1, l2])
2713 active_gates_count[l1] = active_gates_count.get(l1, 0) + 1
2714 active_gates_count[l2] = active_gates_count.get(l2, 0) + 1
2715 phys_to_log[q1], phys_to_log[q2] = (
2716 phys_to_log[q2], phys_to_log[q1]
2718 new_circ.append(
"CX", cx_targets, args)
2719 elif name ==
"CZSWAP":
2721 for idx
in range(0, len(targets), 2):
2722 q1 = targets[idx].value
2723 q2 = targets[idx + 1].value
2724 l1 = phys_to_log[q1]
2725 l2 = phys_to_log[q2]
2726 cz_targets.extend([l1, l2])
2727 active_gates_count[l1] = active_gates_count.get(l1, 0) + 1
2728 active_gates_count[l2] = active_gates_count.get(l2, 0) + 1
2729 phys_to_log[q1], phys_to_log[q2] = (
2730 phys_to_log[q2], phys_to_log[q1]
2732 new_circ.append(
"CZ", cz_targets, args)
2733 elif name
in [
"ISWAP",
"ISWAP_DAG"]:
2736 for idx
in range(0, len(targets), 2):
2737 q1 = targets[idx].value
2738 q2 = targets[idx + 1].value
2739 l1 = phys_to_log[q1]
2740 l2 = phys_to_log[q2]
2741 cz_targets.extend([l1, l2])
2742 s_targets.extend([l1, l2])
2743 active_gates_count[l1] = active_gates_count.get(l1, 0) + 1
2744 active_gates_count[l2] = active_gates_count.get(l2, 0) + 1
2745 phys_to_log[q1], phys_to_log[q2] = (
2746 phys_to_log[q2], phys_to_log[q1]
2748 new_circ.append(
"CZ", cz_targets)
2749 s_gate =
"S" if name ==
"ISWAP" else "S_DAG"
2750 new_circ.append(s_gate, s_targets)
2754 if t.is_qubit_target:
2755 l_q = phys_to_log[t.value]
2756 if name
not in non_active_instructions:
2757 active_gates_count[l_q] = (
2758 active_gates_count.get(l_q, 0) + 1
2760 if t.is_inverted_result_target:
2761 new_targets.append(stim.target_inv(l_q))
2763 new_targets.append(l_q)
2765 l_q = phys_to_log[t.value]
2766 if name
not in non_active_instructions:
2767 active_gates_count[l_q] = (
2768 active_gates_count.get(l_q, 0) + 1
2770 new_targets.append(stim.target_x(l_q))
2772 l_q = phys_to_log[t.value]
2773 if name
not in non_active_instructions:
2774 active_gates_count[l_q] = (
2775 active_gates_count.get(l_q, 0) + 1
2777 new_targets.append(stim.target_y(l_q))
2779 l_q = phys_to_log[t.value]
2780 if name
not in non_active_instructions:
2781 active_gates_count[l_q] = (
2782 active_gates_count.get(l_q, 0) + 1
2784 new_targets.append(stim.target_z(l_q))
2786 new_targets.append(t)
2787 new_circ.append(name, new_targets, args)
2793 verbose: bool =
False,
2794 basis_arg: Optional[str] =
None,
2795 filepath: Optional[Union[str, Path]] =
None
2797 """Classifies qubits and checks via virtual SWAP rewriting and Pauli basis tracking.
2799 Tracks both ancilla stabilizer evolution and data qubit initial basis
2800 evolution through 1-qubit and 2-qubit Clifford gates. Supports standard
2801 CSS codes as well as locally basis-rotated CSS codes (such as XZZX codes)
2802 via 2-coloring of the check-data Pauli compatibility graph.
2805 Dictionary with keys:
2806 - 'x_ancillas': Sorted list of X-type (or X-sector) ancilla qubit IDs.
2807 - 'z_ancillas': Sorted list of Z-type (or Z-sector) ancilla qubit IDs.
2808 - 'data_qubits': Sorted list of data qubit IDs.
2809 - 'routing_qubits': Sorted list of idle/routing qubit IDs.
2810 - 'rotated_data_qubits': Sorted list of data qubits with rotated local basis.
2811 - 'is_css': True if the circuit implements a (possibly locally rotated) CSS code.
2812 - 'is_rotated_css': True if CSS only after local data basis rotations (e.g. XZZX).
2813 - 'basis': Primary memory basis ('X' or 'Z').
2814 - 'rewritten_circuit': Circuit with SWAPs virtually eliminated.
2817 if isinstance(circuit, (str, Path)):
2818 if filepath
is None:
2820 circuit = stim.Circuit.from_file(str(circuit))
2822 all_qubits_set: Set[int] = set()
2824 def collect_qubits(blk: Any) ->
None:
2826 if isinstance(inst, stim.CircuitRepeatBlock):
2827 collect_qubits(inst.body_copy())
2829 for t
in inst.targets_copy():
2831 t.is_qubit_target
or t.is_x_target
2832 or t.is_y_target
or t.is_z_target
2834 all_qubits_set.add(t.value)
2836 collect_qubits(circuit)
2837 max_q = max(all_qubits_set)
if all_qubits_set
else -1
2838 phys_to_log = {q: q
for q
in range(max_q + 1)}
2839 active_gates_count = {q: 0
for q
in all_qubits_set}
2841 def cap_repeats(blk: Any) -> Any:
2842 out = stim.Circuit()
2844 if isinstance(inst, stim.CircuitRepeatBlock):
2846 stim.CircuitRepeatBlock(
2847 min(inst.repeat_count, 2),
2848 cap_repeats(inst.body_copy())
2855 capped_circuit = cap_repeats(circuit)
2857 capped_circuit, phys_to_log, active_gates_count
2860 q
for q, count
in active_gates_count.items()
if count == 0
2862 active_qubits = all_qubits_set - routing_qubits
2864 flat_circ = rewritten_circ.flattened()
2866 tot_resets: Dict[int, int] = {q: 0
for q
in active_qubits}
2867 tot_meas: Dict[int, int] = {q: 0
for q
in active_qubits}
2868 meas_records: List[int] = []
2869 used_in_detector: Set[int] = set()
2870 used_in_observable: Set[int] = set()
2871 first_detector_seen =
False
2872 meas_before_first_det: Set[int] = set()
2873 r1_single_det_anc: Set[int] = set()
2874 multi_det_groups: List[Set[int]] = []
2876 for inst
in flat_circ:
2878 targets = inst.targets_copy()
2879 if name
in [
"R",
"RX",
"RY",
"RZ"]:
2881 if t.is_qubit_target
and t.value
in tot_resets:
2882 tot_resets[t.value] += 1
2883 elif name
in [
"M",
"MX",
"MY",
"MZ"]:
2885 if t.is_qubit_target:
2886 if t.value
in tot_meas:
2887 tot_meas[t.value] += 1
2888 meas_records.append(t.value)
2889 if not first_detector_seen:
2890 meas_before_first_det.add(t.value)
2891 elif name
in [
"MR",
"MRX",
"MRY",
"MRZ"]:
2893 if t.is_qubit_target:
2894 if t.value
in tot_resets:
2895 tot_resets[t.value] += 1
2896 if t.value
in tot_meas:
2897 tot_meas[t.value] += 1
2898 meas_records.append(t.value)
2899 if not first_detector_seen:
2900 meas_before_first_det.add(t.value)
2901 elif name ==
"DETECTOR":
2902 first_detector_seen =
True
2904 meas_records[len(meas_records) + t.value]
2906 if t.is_measurement_record_target
2907 and 0 <= len(meas_records) + t.value < len(meas_records)
2910 used_in_detector.add(q)
2912 r1_single_det_anc.add(recs[0])
2914 multi_det_groups.append(set(recs))
2915 elif name ==
"OBSERVABLE_INCLUDE":
2917 if t.is_measurement_record_target:
2918 idx = len(meas_records) + t.value
2919 if 0 <= idx < len(meas_records):
2920 used_in_observable.add(meas_records[idx])
2922 max_resets = max(tot_resets.values())
if tot_resets
else 0
2923 max_meas = max(tot_meas.values())
if tot_meas
else 0
2925 data_qubits: Set[int] = set()
2926 anc_candidates: Set[int] = set()
2928 for q
in active_qubits:
2930 is_data = (tot_resets[q] <= 1)
2932 is_data = (tot_meas[q] <= 1)
2934 if q
not in used_in_detector:
2938 (q
not in meas_before_first_det)
2939 or (q
in used_in_observable)
2944 anc_candidates.add(q)
2947 reset_basis: Dict[int, str] = {}
2948 meas_basis: Dict[int, str] = {}
2949 first_round_insts: List[Tuple[str, List[int]]] = []
2950 measured_anc: Set[int] = set()
2951 has_any_anc_meas =
False
2953 for inst
in flat_circ:
2955 t_vals = [t.value
for t
in inst.targets_copy()
if t.is_qubit_target]
2956 if name
in [
"R",
"RZ",
"RX",
"RY"]:
2957 if has_any_anc_meas
and any(
2958 q
in anc_candidates
and q
in measured_anc
for q
in t_vals
2961 b =
"X" if name ==
"RX" else (
"Y" if name ==
"RY" else "Z")
2963 if q
not in reset_basis:
2965 first_round_insts.append((name, t_vals))
2966 elif name
in [
"M",
"MZ",
"MX",
"MY",
"MR",
"MRZ",
"MRX",
"MRY"]:
2967 if any(q
in data_qubits
for q
in t_vals)
and has_any_anc_meas:
2969 if any(q
in anc_candidates
and q
in measured_anc
for q
in t_vals):
2972 "X" if name
in [
"MX",
"MRX"]
2973 else (
"Y" if name
in [
"MY",
"MRY"]
else "Z")
2976 if q
in anc_candidates:
2979 has_any_anc_meas =
True
2980 if q
not in reset_basis
and name
in [
2981 "MR",
"MRZ",
"MRX",
"MRY"
2984 first_round_insts.append((name, t_vals))
2986 "H",
"H_XZ",
"H_XY",
"H_YZ",
"S",
"S_DAG",
2987 "SQRT_Z",
"SQRT_Z_DAG",
"SQRT_X",
"SQRT_X_DAG",
2988 "SQRT_Y",
"SQRT_Y_DAG",
2989 "CX",
"ZCX",
"CZ",
"ZCZ",
"CY",
"ZCY",
2990 "XCX",
"XCY",
"XCZ",
"YCX",
"YCY",
"YCZ"
2992 first_round_insts.append((name, t_vals))
2994 def conj_1q(gate: str, p: str) -> str:
2997 if gate
in [
"H",
"H_XZ"]:
2998 return "Z" if p ==
"X" else (
"X" if p ==
"Z" else "Y")
2999 if gate
in [
"S",
"S_DAG",
"SQRT_Z",
"SQRT_Z_DAG",
"H_XY"]:
3000 return "Y" if p ==
"X" else (
"X" if p ==
"Y" else "Z")
3001 if gate
in [
"SQRT_X",
"SQRT_X_DAG",
"H_YZ"]:
3002 return "Z" if p ==
"Y" else (
"Y" if p ==
"Z" else "X")
3003 if gate
in [
"SQRT_Y",
"SQRT_Y_DAG"]:
3004 return "Z" if p ==
"X" else (
"X" if p ==
"Z" else "Y")
3008 (
"I",
"I"):
"I", (
"I",
"X"):
"X", (
"I",
"Y"):
"Y", (
"I",
"Z"):
"Z",
3009 (
"X",
"I"):
"X", (
"X",
"X"):
"I", (
"X",
"Y"):
"Z", (
"X",
"Z"):
"Y",
3010 (
"Y",
"I"):
"Y", (
"Y",
"X"):
"Z", (
"Y",
"Y"):
"I", (
"Y",
"Z"):
"X",
3011 (
"Z",
"I"):
"Z", (
"Z",
"X"):
"Y", (
"Z",
"Y"):
"X", (
"Z",
"Z"):
"I",
3014 def conj_2q(gate: str, p1: str, p2: str) -> Tuple[str, str]:
3015 if p1 ==
"I" and p2 ==
"I":
3017 if gate
in [
"CX",
"ZCX"]:
3019 elif gate
in [
"CZ",
"ZCZ"]:
3021 elif gate
in [
"CY",
"ZCY"]:
3037 anti1 = (p1 !=
"I" and p1 != a)
3038 anti2 = (p2 !=
"I" and p2 != b)
3039 np1 = mul_tab[(p1, a)]
if anti2
else p1
3040 np2 = mul_tab[(p2, b)]
if anti1
else p2
3045 anc_list = sorted(list(anc_candidates))
3046 anc_to_bit = {anc: (1 << i)
for i, anc
in enumerate(anc_list)}
3047 all_anc_mask = (1 << len(anc_list)) - 1
3049 qx: Dict[int, int] = {q: 0
for q
in all_qubits_set}
3050 qz: Dict[int, int] = {q: 0
for q
in all_qubits_set}
3052 for anc
in anc_list:
3053 b = reset_basis.get(anc, meas_basis.get(anc,
"Z"))
3054 bit = anc_to_bit[anc]
3059 if anc
not in reset_basis:
3060 active_anc_mask |= bit
3062 data_init_basis: Dict[int, str] = {q:
"Z" for q
in data_qubits}
3064 for name, t_vals
in first_round_insts:
3065 if name
in [
"R",
"RZ",
"RX",
"RY"]:
3066 b =
"X" if name ==
"RX" else (
"Y" if name ==
"RY" else "Z")
3068 if q
in data_qubits:
3069 data_init_basis[q] = b
3072 if not (active_anc_mask & bit):
3073 active_anc_mask |= bit
3082 elif name
in [
"M",
"MZ",
"MX",
"MY",
"MR",
"MRZ",
"MRX",
"MRY"]:
3085 active_anc_mask &= ~anc_to_bit[q]
3087 "H",
"H_XZ",
"H_XY",
"H_YZ",
"S",
"S_DAG",
3088 "SQRT_Z",
"SQRT_Z_DAG",
"SQRT_X",
"SQRT_X_DAG",
3089 "SQRT_Y",
"SQRT_Y_DAG"
3092 if q
in data_qubits:
3093 data_init_basis[q] = conj_1q(name, data_init_basis[q])
3100 mask = active_anc_mask
if q
in anc_candidates
else all_anc_mask
3103 if name
in [
"H",
"H_XZ",
"SQRT_Y",
"SQRT_Y_DAG"]:
3104 nx_m, nz_m = z_m, x_m
3106 "S",
"S_DAG",
"SQRT_Z",
"SQRT_Z_DAG",
"H_XY"
3108 nx_m, nz_m = x_m, x_m ^ z_m
3109 elif name
in [
"SQRT_X",
"SQRT_X_DAG",
"H_YZ"]:
3110 nx_m, nz_m = x_m ^ z_m, z_m
3112 nx_m, nz_m = x_m, z_m
3113 qx[q] = (qx[q] & ~mask) | nx_m
3114 qz[q] = (qz[q] & ~mask) | nz_m
3116 "CX",
"ZCX",
"CZ",
"ZCZ",
"CY",
"ZCY",
3117 "XCX",
"XCY",
"XCZ",
"YCX",
"YCY",
"YCZ"
3119 if name
in [
"CX",
"ZCX"]:
3120 ax, az, bx, bz = 0, all_anc_mask, all_anc_mask, 0
3121 elif name
in [
"CZ",
"ZCZ"]:
3122 ax, az, bx, bz = 0, all_anc_mask, 0, all_anc_mask
3123 elif name
in [
"CY",
"ZCY"]:
3124 ax, az, bx, bz = 0, all_anc_mask, all_anc_mask, all_anc_mask
3126 ax, az, bx, bz = all_anc_mask, 0, all_anc_mask, 0
3128 ax, az, bx, bz = all_anc_mask, 0, all_anc_mask, all_anc_mask
3130 ax, az, bx, bz = all_anc_mask, 0, 0, all_anc_mask
3132 ax, az, bx, bz = all_anc_mask, all_anc_mask, all_anc_mask, 0
3135 all_anc_mask, all_anc_mask, all_anc_mask, all_anc_mask
3138 ax, az, bx, bz = all_anc_mask, all_anc_mask, 0, all_anc_mask
3142 for idx
in range(0, len(t_vals), 2):
3143 q1, q2 = t_vals[idx], t_vals[idx + 1]
3144 if q1
not in qx
or q2
not in qx:
3146 mask = active_anc_mask
3147 if q1
in anc_to_bit
and not (active_anc_mask & anc_to_bit[q1]):
3148 mask &= ~anc_to_bit[q1]
3149 if q2
in anc_to_bit
and not (active_anc_mask & anc_to_bit[q2]):
3150 mask &= ~anc_to_bit[q2]
3155 anti1 = (x1 & (az & mask)) ^ (z1 & (ax & mask))
3156 anti2 = (x2 & (bz & mask)) ^ (z2 & (bx & mask))
3157 nx1 = x1 ^ (anti2 & ax)
3158 nz1 = z1 ^ (anti2 & az)
3159 nx2 = x2 ^ (anti1 & bx)
3160 nz2 = z2 ^ (anti1 & bz)
3161 qx[q1] = (qx[q1] & ~mask) | nx1
3162 qz[q1] = (qz[q1] & ~mask) | nz1
3163 qx[q2] = (qx[q2] & ~mask) | nx2
3164 qz[q2] = (qz[q2] & ~mask) | nz2
3166 anc_data_paulis: Dict[int, Dict[int, str]] = {
3167 anc: {}
for anc
in anc_list
3169 for q
in data_qubits:
3174 lsb = active_m & -active_m
3175 anc_idx = lsb.bit_length() - 1
3176 anc = anc_list[anc_idx]
3177 has_x = bool(xm & lsb)
3178 has_z = bool(zm & lsb)
3180 "Y" if (has_x
and has_z)
3181 else (
"X" if has_x
else "Z")
3183 anc_data_paulis[anc][q] = p_str
3186 active_ancillas = [a
for a
in anc_list
if len(anc_data_paulis[a]) > 0]
3187 idle_ancillas = [a
for a
in anc_list
if len(anc_data_paulis[a]) == 0]
3188 routing_qubits.update(idle_ancillas)
3191 checks_commute =
True
3192 for i
in range(len(active_ancillas)):
3193 a1 = active_ancillas[i]
3194 s1 = anc_data_paulis[a1]
3195 for j
in range(i + 1, len(active_ancillas)):
3196 a2 = active_ancillas[j]
3197 s2 = anc_data_paulis[a2]
3199 for q, p1
in s1.items():
3201 if p2
is not None and p1 != p2:
3204 checks_commute =
False
3206 if not checks_commute:
3210 adj: Dict[int, Dict[int, int]] = {a: {}
for a
in active_ancillas}
3211 bipartite_paulis =
True
3212 for q
in data_qubits:
3214 (a, anc_data_paulis[a][q])
3215 for a
in active_ancillas
3216 if q
in anc_data_paulis[a]
3220 pauli_set = {p
for _, p
in touching}
3221 if len(pauli_set) > 2:
3222 bipartite_paulis =
False
3224 a0, p0 = touching[0]
3225 for ak, pk
in touching[1:]:
3226 rel = 0
if pk == p0
else 1
3227 if ak
in adj[a0]
and adj[a0][ak] != rel:
3228 bipartite_paulis =
False
3232 if not bipartite_paulis:
3236 color: Dict[int, int] = {}
3237 components: List[Tuple[List[int], List[int]]] = []
3238 if bipartite_paulis:
3239 for a
in active_ancillas:
3247 while idx_q < len(queue):
3254 for v, rel
in adj[u].items():
3255 expected = color[u] ^ rel
3259 elif color[v] != expected:
3260 bipartite_paulis =
False
3262 if not bipartite_paulis:
3264 if not bipartite_paulis:
3266 components.append((c0, c1))
3270 for a
in active_ancillas:
3271 ps = set(anc_data_paulis[a].values())
3272 if not (ps == {
"X"}
or ps == {
"Z"}):
3278 circuit, filepath=filepath, basis_arg=basis_arg
3280 final_det_anc: Set[int] = set()
3281 for grp
in multi_det_groups:
3282 if grp & data_qubits:
3283 final_det_anc.update(grp & set(active_ancillas))
3285 x_ancillas: Set[int] = set()
3286 z_ancillas: Set[int] = set()
3287 rotated_data_qubits: Set[int] = set()
3289 is_rotated_css =
False
3291 if checks_commute
and bipartite_paulis
and active_ancillas:
3293 for a
in active_ancillas:
3294 ps = set(anc_data_paulis[a].values())
3300 frozenset(anc_data_paulis[a].keys())
for a
in x_ancillas
3303 frozenset(anc_data_paulis[a].keys())
for a
in z_ancillas
3306 len(x_supps) > 0
and x_supps == z_supps
3307 and len(x_ancillas) == len(z_ancillas)
3309 is_css =
not is_color_code
3310 is_rotated_css =
False
3313 is_rotated_css =
True
3314 for c0, c1
in components:
3316 def score_primary(sector: List[int]) -> Tuple[int, int, int]:
3317 r1_cnt = sum(1
for a
in sector
if a
in r1_single_det_anc)
3318 fin_cnt = sum(1
for a
in sector
if a
in final_det_anc)
3322 anc_data_paulis[a][q] == data_init_basis.get(q,
"Z")
3323 for q
in anc_data_paulis[a]
3326 return (r1_cnt, fin_cnt, init_cnt)
3328 sc0 = score_primary(c0)
3329 sc1 = score_primary(c1)
3331 primary_sec, minority_sec = c0, c1
3333 primary_sec, minority_sec = c1, c0
3335 if native_basis ==
"Z":
3336 z_ancillas.update(primary_sec)
3337 x_ancillas.update(minority_sec)
3339 x_ancillas.update(primary_sec)
3340 z_ancillas.update(minority_sec)
3343 for q
in data_qubits:
3344 for a
in active_ancillas:
3345 if q
in anc_data_paulis[a]:
3346 sector_label =
"X" if a
in x_ancillas
else "Z"
3347 if anc_data_paulis[a][q] != sector_label:
3348 rotated_data_qubits.add(q)
3353 is_rotated_css =
False
3354 for a
in active_ancillas:
3355 s = anc_data_paulis[a]
3356 nx = sum(1
for p
in s.values()
if p ==
"X")
3357 nz = sum(1
for p
in s.values()
if p ==
"Z")
3364 "x_ancillas": sorted(list(x_ancillas)),
3365 "z_ancillas": sorted(list(z_ancillas)),
3366 "data_qubits": sorted(list(data_qubits)),
3367 "routing_qubits": sorted(list(routing_qubits)),
3368 "rotated_data_qubits": sorted(list(rotated_data_qubits)),
3370 "is_rotated_css": is_rotated_css,
3371 "basis": native_basis,
3372 "rewritten_circuit": rewritten_circ,
3378 verbose: bool =
False,
3379 basis_arg: Optional[str] =
None,
3380 filepath: Optional[Union[str, Path]] =
None
3382 """Verifies qubit classification and returns basis tracking metadata."""
3384 circuit, verbose=verbose, basis_arg=basis_arg, filepath=filepath
3391 verbose: bool =
False,
3392 thorough: bool =
True,
3393 thorough_res: Optional[Dict[str, Any]] =
None,
3394 mask_out: Optional[List[bool]] =
None
3395) -> Tuple[Any, int, int]:
3396 """Strips detectors corresponding to the minority basis from a Stim circuit.
3398 Always uses ancilla and data basis tracking (and dynamic carrier tracking
3399 across SWAPs and syndrome handoffs) to identify each detector's basis.
3402 circuit: A stim.Circuit object or path to a .stim file.
3403 basis: Primary memory basis ('X' or 'Z'). Detectors of the opposite
3405 verbose: Whether to print warnings/statistics.
3406 thorough: Kept for API compatibility (basis tracking is always enabled).
3407 thorough_res: Optional precomputed result from classify_qubits_thorough.
3408 mask_out: Optional list, extended by one entry per detector of the circuit (in the order of the detectors of
3409 its DEM, REPEAT blocks unrolled): True if the detector is stripped, False if it is kept.
3412 Tuple of (simplified_circuit, stripped_count, kept_count).
3415 if isinstance(circuit, (str, Path)):
3416 circuit = stim.Circuit.from_file(str(circuit))
3420 if thorough_res
is not None
3423 x_anc = set(t_res[
"x_ancillas"])
3424 z_anc = set(t_res[
"z_ancillas"])
3425 minority_basis =
"Z" if basis.upper() ==
"X" else "X"
3430 qubit_coords: Dict[int, Tuple[float, ...]] = {}
3432 def get_coords(blk: Any) ->
None:
3434 if isinstance(inst, stim.CircuitRepeatBlock):
3435 get_coords(inst.body_copy())
3436 elif inst.name ==
"QUBIT_COORDS":
3437 args = inst.gate_args_copy()
3438 for t
in inst.targets_copy():
3439 if t.is_qubit_target:
3440 qubit_coords[t.value] = tuple(float(x)
for x
in args)
3446 if q
in qubit_coords
and len(qubit_coords[q]) >= 2
3451 if q
in qubit_coords
and len(qubit_coords[q]) >= 2
3453 layout_well_defined = (
3454 not t_res.get(
"is_rotated_css",
False)
3455 and len(x_coords) > 0
3456 and len(z_coords) > 0
3457 and x_coords.isdisjoint(z_coords)
3460 meas_history: List[int] = []
3461 det_mask: List[bool] = []
3464 contradictions_c_count = 0
3465 floquet_suspected =
False
3467 def process_block(block: Any) -> Any:
3468 nonlocal stripped_count, kept_count
3469 nonlocal contradictions_c_count, floquet_suspected
3470 new_block = stim.Circuit()
3472 if isinstance(inst, stim.CircuitRepeatBlock):
3473 rep_count = inst.repeat_count
3476 body_copy = inst.body_copy()
3477 meas_before = len(meas_history)
3478 mask_before = len(det_mask)
3479 strip_before = stripped_count
3480 kept_before = kept_count
3481 new_body = process_block(body_copy)
3482 meas_after = len(meas_history)
3483 meas_per_rep = meas_after - meas_before
3485 stripped_count += (stripped_count - strip_before) * (
3488 kept_count += (kept_count - kept_before) * (rep_count - 1)
3489 det_mask.extend(det_mask[mask_before:] * (rep_count - 1))
3490 if meas_per_rep > 0:
3491 meas_history.extend(
3492 meas_history[meas_before:meas_after]
3495 new_block.append(stim.CircuitRepeatBlock(rep_count, new_body))
3497 "M",
"MX",
"MY",
"MZ",
"MR",
"MRX",
"MRY",
"MRZ"
3499 if inst.name
in [
"MY",
"MRY"]:
3500 floquet_suspected =
True
3501 for t
in inst.targets_copy():
3502 if t.is_qubit_target:
3503 meas_history.append(t.value)
3504 new_block.append(inst)
3505 elif inst.name ==
"DETECTOR":
3506 cur_meas = len(meas_history)
3508 t
for t
in inst.targets_copy()
3509 if t.is_measurement_record_target
3513 det_mask.append(
True)
3517 meas_history[cur_meas + t.value]
3518 for t
in rec_targets
3519 if 0 <= cur_meas + t.value < cur_meas
3521 is_x_qubit = any(q
in x_anc
for q
in qubits)
3522 is_z_qubit = any(q
in z_anc
for q
in qubits)
3524 "Both" if (is_x_qubit
and is_z_qubit)
3525 else "X" if is_x_qubit
3526 else "Z" if is_z_qubit
3530 args = inst.gate_args_copy()
3532 if layout_well_defined
and args
and len(args) >= 2:
3533 det_xy = (float(args[0]), float(args[1]))
3534 if det_xy
in x_coords:
3536 elif det_xy
in z_coords:
3543 for t
in rec_targets
3544 if 0 <= cur_meas + t.value < len(meas_carriers)
3545 for c
in meas_carriers[cur_meas + t.value]
3547 is_x_c = any(c
in x_anc
for c
in carriers)
3548 is_z_c = any(c
in z_anc
for c
in carriers)
3550 "Both" if (is_x_c
and is_z_c)
3557 if class_c !=
"Unknown":
3558 if class_c ==
"Both":
3559 contradictions_c_count += 1
3560 floquet_suspected =
True
3561 elif class_b !=
"Unknown" and class_c != class_b:
3562 contradictions_c_count += 1
3563 elif len(rec_targets) == 1
and class_c == minority_basis:
3564 contradictions_c_count += 1
3565 is_minority = (class_c == minority_basis)
3566 elif layout_well_defined
and class_b !=
"Unknown":
3567 is_minority = (class_b == minority_basis)
3568 elif len(rec_targets) == 1:
3571 if class_qubit ==
"Both":
3572 floquet_suspected =
True
3573 is_minority = (class_qubit == minority_basis)
3579 new_block.append(inst)
3580 det_mask.append(is_minority)
3582 new_block.append(inst)
3585 simp_circuit = process_block(circuit)
3586 if mask_out
is not None:
3587 mask_out.extend(det_mask)
3589 if contradictions_c_count > 0
and verbose:
3591 f
"# Warning: {contradictions_c_count} detector contradiction(s) "
3592 "detected during dynamic carrier tracking.\n"
3594 if floquet_suspected
and verbose:
3596 "# Warning: Floquet or dynamic measurement structure suspected; "
3597 "simplified circuit distance may be invalid.\n"
3600 return simp_circuit, stripped_count, kept_count
3603def _matrix_cache_key(H: Any, G: Optional[Any] =
None, L: Optional[Any] =
None, classical: bool =
False) -> str:
3605 Cache key of a classical code (H), or of a quantum code given by H with G (or with L). The two sectors of a CSS
3606 code are stored as the quantum codes (H=Hx, G=Hz) -> dZ and (H=Hz, G=Hx) -> dX (with L=Lx / L=Lz if given), so
3607 that compute_css_distance() and compute_quantum_distance() share their cache records.
3611 return f
"classical:{h_state}"
3613 return f
"quantum:H={h_state}:G={get_sparse_array_state(G)}"
3614 return f
"quantum:H={h_state}:L={get_sparse_array_state(L)}"
3618 """Cache key of a DEM (a stim.DetectorErrorModel or a .dem file) with the probability cutoff pmin."""
3620 return f
"dem:{dem_state}" if pmin <= 0.0
else f
"dem:{dem_state}:pmin={pmin}"
3624 """Writes a stim.DetectorErrorModel (flattened) or the text of a DEM to the file `path`."""
3625 if hasattr(dem,
'flattened'):
3626 dem.flattened().to_file(path)
3627 elif hasattr(dem,
'to_file'):
3630 with open(path,
'w')
as f:
3636 The columns of a DEM (a stim.DetectorErrorModel, or a .dem file) as read by the dist_m4ri binary: the detector and
3637 observable sets of its error mechanisms, in their order in the flattened DEM (repeat blocks unrolled, with
3638 shift_detectors), after the probability cutoff pmin (an error with probability < pmin is skipped). The components
3639 of a decomposed error (separated by '^') are summed mod 2, so that a target listed twice cancels. With
3640 with_prob=True, each column also has the probability of the error, as written: (detectors, observables, p).
3642 if isinstance(dem, (str, Path)):
3643 with open(str(dem))
as f:
3644 lines = f.read().splitlines()
3646 lines = str(dem.flattened()
if hasattr(dem,
'flattened')
else dem).splitlines()
3647 cols: List[Tuple[Any, ...]] = []
3649 def block(i: int, shift: int) -> Tuple[int, int]:
3651 while i < len(lines):
3652 s = lines[i].split(
'#', 1)[0].strip()
3656 if s.startswith(
'}'):
3658 if s.startswith(
'repeat'):
3659 count = int(s.split()[1])
3661 for _
in range(count):
3662 end, shift = block(body, shift)
3665 while end < len(lines)
and depth > 0:
3666 t = lines[end].split(
'#', 1)[0].strip()
3668 depth += t.startswith(
'repeat') - t.startswith(
'}')
3670 elif s.startswith(
'shift_detectors'):
3671 shift += int(s.rsplit(
None, 1)[-1])
3672 elif s.startswith(
'error'):
3673 p_text = s[s.index(
'(') + 1:s.index(
')')].strip()
3674 if float(p_text) < pmin:
3676 dets: Set[int] = set()
3677 obs: Set[int] = set()
3678 for tok
in s[s.index(
')') + 1:].split():
3680 dets ^= {int(tok[1:]) + shift}
3682 obs ^= {int(tok[1:])}
3683 col = (frozenset(dets), frozenset(obs))
3684 cols.append(col + (p_text,)
if with_prob
else col)
3693 The drop option of compute_dem_distance(): "auto" (for None, True, 1, "1", or "auto"), "0" (False, 0, or "0"),
3694 "css", or the name of an existing file (ValueError otherwise).
3696 if drop
is None or drop
is True:
3700 if isinstance(drop, numbers.Integral):
3702 return "auto" if drop == 1
else "0"
3703 raise ValueError(f
"invalid drop={drop}: use 0, 1, 'auto', 'css', or a file with the detectors to drop")
3704 s = str(drop).strip()
3705 if s.lower()
in (
"1",
"auto"):
3709 if s.lower() ==
"css":
3711 if not os.path.isfile(s):
3712 raise ValueError(f
"drop={s}: no such file (use 0, 1, auto, css, or a file with the detectors to drop)")
3716def _reduced_out_prefix(spec: Union[bool, str, Path], src: str, out_dir: Optional[Union[str, Path]] =
None) -> str:
3718 The prefix PREFIX of the files PREFIX_red.dem and PREFIX_red.json of compute_dem_distance(out_reduced=spec) for the
3719 input src (a file name, or e.g. 'stim.Circuit'): for True (also '', '1', 'true', 'yes', 'auto'), the basename of
3720 the input file (without a suffix _full or _simp; 'dem' for an object); a bare name goes to out_dir (default: the
3721 directory of the input file, as for --out-dem), and a path is used as given.
3723 src_path = Path(src)
if os.path.exists(src)
else None
3724 eff_dir = Path(out_dir)
if out_dir
is not None else (src_path.parent
if src_path
is not None else Path(
"."))
3725 if spec
is True or (isinstance(spec, str)
and spec.strip().lower()
in (
"",
"1",
"true",
"yes",
"auto")):
3726 stem = src_path.stem
if src_path
is not None else "dem"
3727 if stem.endswith((
"_full",
"_simp")):
3729 return str(eff_dir / stem)
3731 return str(eff_dir / spec_s)
if (
not os.path.isabs(spec_s)
and os.path.dirname(spec_s) ==
"")
else spec_s
3736 Reads a drop file: the 0-based detector indices (k for Dk) separated by white space or commas, where lines starting
3737 with '#' or '%' are comments, e.g., the detectors dropped from a DEM written by the binary (outD=FILE).
3740 (detectors, num_detectors): the sorted distinct detectors, and the number of detectors of the DEM from the
3741 comment line '# N of M detectors dropped ...' written by the binary (None if there is none).
3744 dets: Set[int] = set()
3745 num: Optional[int] =
None
3746 with open(str(path))
as f:
3749 if s.startswith(
'#')
or s.startswith(
'%'):
3750 mm = re.match(
r"#\s*\d+ of (\d+) detectors dropped", s)
3751 if mm
and num
is None:
3752 num = int(mm.group(1))
3754 dets.update(int(tok)
for tok
in s.replace(
',',
' ').split())
3755 return sorted(dets), num
3760 dropped: Sequence[int],
3761 prefix: Union[str, Path],
3763 num_detectors: Optional[int] =
None,
3764 meta: Optional[Dict[str, Any]] =
None
3765) -> Tuple[str, str]:
3767 Writes the DEM (a stim.DetectorErrorModel or a .dem file) without the detectors `dropped` (0-based, e.g., those
3768 dropped by the binary, see compute_dem_distance(outD=..., out_reduced=...)) to PREFIX_red.dem: the errors which
3769 touch none of them (after the cutoff pmin, in the order of the flattened DEM; a decomposed error as the sum of its
3770 components), with the other detectors numbered consecutively, and the observables unchanged. The maps go to
3771 PREFIX_red.json: "detectors" (the original index of each detector of the reduced DEM, i.e., the complement of
3772 "dropped"), "errors" (the original index of each error of the reduced DEM, 0-based in the order of the flattened DEM
3773 after the cutoff pmin, as the columns of the codewords in outC), "dropped", "num_detectors", "num_errors", "pmin",
3774 and the entries of meta. If dropping the detectors is exact (as verified by the binary), the reduced DEM has the
3775 same distance, and its codewords map to codewords of the DEM with "errors".
3778 (dem_path, json_path): the files written.
3781 drop_set = set(int(r)
for r
in dropped)
3782 m = 1 + max([d
for S, _, _
in cols
for d
in S] + list(drop_set), default=-1)
3783 if num_detectors
is not None:
3784 m = max(m, int(num_detectors))
3785 k = 1 + max((o
for _, O, _
in cols
for o
in O), default=-1)
3786 kept = [r
for r
in range(m)
if r
not in drop_set]
3787 new_index = {r: i
for i, r
in enumerate(kept)}
3788 dem_path, json_path = f
"{prefix}_red.dem", f
"{prefix}_red.json"
3789 errors: List[int] = []
3790 lines = [f
"# reduced DEM: {len(drop_set)} of {m} detectors dropped (dist_m4ri.py {__version__}); the maps to the "
3791 f
"original DEM are in {os.path.basename(json_path)}"]
3792 max_det = max_obs = -1
3793 for j, (S, O, p)
in enumerate(cols):
3796 dets = sorted(new_index[d]
for d
in S)
3799 max_det = max(max_det, dets[-1])
3801 max_obs = max(max_obs, obs[-1])
3802 lines.append(f
"error({p})" +
"".join(f
" D{d}" for d
in dets) +
"".join(f
" L{o}" for o
in obs))
3804 if max_det < len(kept) - 1:
3805 lines.append(f
"detector D{len(kept) - 1}")
3807 lines.append(f
"logical_observable L{k - 1}")
3808 parent_dir = os.path.dirname(os.path.abspath(dem_path))
3809 os.makedirs(parent_dir, exist_ok=
True)
3810 with open(dem_path,
"w")
as f:
3811 f.write(
"\n".join(lines) +
"\n")
3812 info: Dict[str, Any] = {
"__version__": __version__}
3813 info.update(meta
or {})
3814 info.update({
"pmin": pmin,
"num_detectors": m,
"num_errors": len(cols),
"dropped": sorted(drop_set),
3815 "detectors": kept,
"errors": errors})
3816 with open(json_path,
"w")
as f:
3817 json.dump(info, f, separators=(
",",
":"))
3819 return dem_path, json_path
3823 cws: List[List[int]],
3827 verbose: bool =
False
3828) -> List[List[int]]:
3830 The expanded list of the DEM codewords cws (0-based columns of the DEM, see _dem_error_columns()): every version
3831 over the choices among equal columns (the same detectors and observables), which the binary merges (merge=1).
3832 Each codeword is mapped to the first columns of its sets of equal columns (two equal columns cancel), and every
3833 distinct result c is expanded into the prod_{j in c} |set(j)| codewords with one column from each set (sorted, in
3834 the order of cws). With maxC > 0, at most maxC codewords are returned, with a warning to stderr if some are
3840 sets: Dict[Tuple[frozenset, frozenset], List[int]] = {}
3841 for j, c
in enumerate(cols):
3842 sets.setdefault(c, []).append(j)
3843 members = [sets[c]
for c
in cols]
3844 keys: List[Tuple[int, ...]] = []
3845 seen: Set[Tuple[int, ...]] = set()
3847 if any(j < 0
or j >= len(members)
for j
in cw):
3848 raise ValueError(f
"expand: codeword column out of range (the DEM has {len(members)} columns after the "
3849 f
"pmin={pmin} cutoff)")
3850 odd: Set[int] = set()
3852 odd ^= {members[j][0]}
3853 key = tuple(sorted(odd))
3854 if key
and key
not in seen:
3861 num *= len(members[r])
3863 limit = total
if maxC <= 0
else min(total, maxC)
3864 out: List[List[int]] = []
3866 if len(out) >= limit:
3868 for choice
in itertools.product(*(members[r]
for r
in key)):
3869 out.append(sorted(choice))
3870 if len(out) >= limit:
3873 sys.stderr.write(f
"# Warning: expand: the expanded list has {total} codewords ({len(keys)} up to equal "
3874 f
"columns); only the first maxC={maxC} are written (the list is incomplete)\n")
3876 print(f
"[dist_m4ri] expand: {len(keys)} codewords up to equal columns -> {total} codewords")
3880def _css_sector_codes(Hx: Any, Hz: Any, Lx: Optional[Any], Lz: Optional[Any]) -> Dict[str, Tuple[Any, Any, Any]]:
3881 """The quantum codes (H, G, L) of the two sectors of a CSS code: Z -> (Hx, Hz or Lx), X -> (Hz, Hx or Lz)."""
3883 "Z": (Hx, Hz
if Lx
is None else None, Lx),
3884 "X": (Hz, Hx
if Lz
is None else None, Lz),
3889 x: Tuple[int, int, int], z: Tuple[int, int, int], dmax: int = 0
3890) -> Tuple[int, int, int, int]:
3892 Bounds of the CSS distance d = min(dX, dZ) from the sector bounds x, z = (dmin, dmax, rw_steps), and an upper
3893 bound `dmax` on d supplied by the user (0: none).
3896 (dist, dmin, dmax, rw_steps): dist is the smaller sector distance (see _dist_from_bounds(); the larger one if
3897 a sector distance is 0), at most the user's dmax; dmin = min(dmin_X, dmin_Z); dmax is the smallest of the
3898 positive upper bounds (0: none).
3900 d_lo = min(x[0], z[0])
3901 uppers = [v
for v
in (x[1], z[1], dmax)
if v > 0]
3902 d_hi = min(uppers)
if uppers
else 0
3903 if d_hi > 0
and d_lo > d_hi:
3906 dist = min(dist_x, dist_z)
if (dist_x > 0
and dist_z > 0)
else max(dist_x, dist_z)
3907 if dmax > 0
and dist > dmax:
3909 if d_lo > 0
and d_lo == d_hi:
3911 return dist, d_lo, d_hi, x[2] + z[2]
3914def _sector_record(dmin: int, dmax: int, rw_steps: int, cws: List[List[int]]) -> Dict[str, Any]:
3915 """A cache record of a classical or quantum code (or of a CSS sector) with the bounds [dmin, dmax]."""
3916 if dmax > 0
and dmin > dmax:
3922 "rw_steps": rw_steps,
3929 Hx: Any, Hz: Any, Lx: Optional[Any] =
None, Lz: Optional[Any] =
None, eff_cache_file: Optional[str] =
None
3932 Converts the CSS cache records written before version 0.12.0 (key 'css:X=<Hx>:Z=<Hz>[:Lx=<Lx>:Lz=<Lz>]', also
3933 with a ':start=...' suffix) into the records of the two sectors (see _matrix_cache_key()), and removes them.
3935 The lower bounds dmin_X and dmin_Z are kept. An upper bound dmax_X or dmax_Z is kept only if a stored codeword
3936 confirms it, or if it is smaller than the upper bound of the other sector: earlier versions passed an upper bound
3937 dmax given to compute_css_distance() (a bound on min(dX, dZ)) to both sectors, where it was reported back as the
3938 upper bound of the sector, which is not valid for the sector with the larger distance. Otherwise, the weight of
3939 the lightest stored codeword (if any) is used.
3942 True if a record was converted (the cache file, if any, is then saved).
3944 global _distance_cache
3945 old = f
"css:X={get_sparse_array_state(Hx)}:Z={get_sparse_array_state(Hz)}"
3946 if Lx
is not None or Lz
is not None:
3947 old = f
"{old}:Lx={get_sparse_array_state(Lx)}:Lz={get_sparse_array_state(Lz)}"
3948 old_keys = [k
for k
in list(_distance_cache)
if k == old
or k.startswith(old +
":start=")]
3953 for key
in old_keys:
3954 rec = _distance_cache.pop(key)
3955 if not isinstance(rec, dict):
3957 sfx = key[len(old):]
3958 for s, other
in ((
"X",
"Z"), (
"Z",
"X")):
3959 dmin = int(rec.get(f
"dmin_{s}", rec.get(
"dmin", 0))
or 0)
3960 dmax = int(rec.get(f
"dmax_{s}", 0)
or 0)
3961 other_dmax = int(rec.get(f
"dmax_{other}", 0)
or 0)
3962 cws = [list(cw)
for cw
in (rec.get(f
"cws_{s}")
or [])]
3963 w_cw = min((len(cw)
for cw
in cws), default=0)
3964 if dmax > 0
and w_cw != dmax
and not (dmax < other_dmax):
3967 dmax = min(dmax, w_cw)
if dmax > 0
else w_cw
3968 rw = int(rec.get(f
"rw_steps_{s}", 0)
or 0)
3969 skey = base[s] + sfx
3970 prev = _distance_cache.get(skey)
3972 p_dmax = prev.get(
"dmax", 0)
or 0
3973 dmax = min(p_dmax, dmax)
if (p_dmax > 0
and dmax > 0)
else max(p_dmax, dmax)
3974 dmin = max(prev.get(
"dmin", 0)
or 0, dmin)
3975 rw = max(prev.get(
"rw_steps", 0)
or 0, rw)
3989 dist_m4ri: Optional[str],
3991 threads: Optional[int],
3993 num_steps: Optional[int],
4000 smax: Optional[int],
4001 start_list: Optional[List[int]],
4004 finC: Optional[str],
4005 outC: Optional[str],
4007 eff_cache_file: Optional[str],
4016 min_hits: Optional[int],
4018 refresh: Optional[int],
4020 fdem: Optional[Any] =
None,
4023 key_fn: Optional[Callable[[], str]] =
None,
4024 cws_out_fn: Optional[Callable[[List[List[int]]], List[List[int]]]] =
None,
4025 fdrop: Optional[Union[str, Path, List[int]]] =
None,
4026 outD: Optional[str] =
None,
4027 sort_rows: Optional[bool] =
None,
4030 cc_prune: Optional[int] =
None,
4034 Distance of a classical code (H), of a quantum code (H with G or L), or of a DEM (fdem: a stim DEM object or a .dem
4035 file, with the probability cutoff pmin, merge, the detectors to drop fdrop ("auto", "0", a file, or a list), and
4036 the file outD for the detectors dropped: see run_dist_m4ri()) with the dist_m4ri binary, using the distance cache
4037 shared by compute_classical_distance(), compute_quantum_distance(), the two sectors of compute_css_distance(), and
4038 compute_dem_distance() (the warnings for the expert options are issued by the callers). The row order sort_rows
4039 and the CC search cc_prune (see run_dist_m4ri()) do not change the result, nor the cache key; nor do the RW window
4040 size win_hyb, the RW pairs (pairs), and the expert option ortho (False: the binary does not check H G^T = 0) change
4043 A cache record (key: key_fn() if given, otherwise see _matrix_cache_key()) answers the request if it holds the
4044 exact distance, or with the stop target dstop > 0 a lower bound dmin >= dstop (and the codewords, if needed).
4045 Otherwise, the binary is run, starting from the cached bounds, and the merged bounds are stored. The returned
4046 codewords (those of the run merged with the cached ones, or the cached ones) are written to outC; with finC
4047 identical to outC, the codewords already in the file are kept. If given, cws_out_fn transforms the codewords
4048 returned and written to outC (e.g., the expansion over equal DEM columns, see _expand_equal_columns()); the cache
4049 keeps the codewords as found.
4052 dict with the distance "dist" (see _dist_from_bounds()), the bounds "dmin", "dmax", "rw_steps", "d_info"
4053 (see format_bounds_list()), the codewords "cws" (empty unless need_cws), and "cached" (True if no binary run).
4055 global _distance_cache, _last_run_stats
4056 _last_run_stats = {}
4057 same_file = bool(finC
and outC)
and os.path.abspath(str(finC)) == os.path.abspath(str(outC))
4060 def write_out(cws_res: List[List[int]]) -> List[List[int]]:
4063 if cws_out_fn
is not None and cws_res:
4064 cws_res = cws_out_fn(cws_res)
4065 if outC
and cws_res:
4067 if same_file
and os.path.isfile(outC):
4072 code_key = cached_entry = plain_key = plain_entry =
None
4073 hit: Optional[Dict[str, Any]] =
None
4074 eff_dmin, eff_dmax = dmin, dmax
4075 if _use_distance_cache:
4079 plain_key = key_fn()
if key_fn
is not None else _matrix_cache_key(H, G, L, classical)
4085 if cached_entry
is not None:
4086 c_dmin, c_dmax = cached_entry.get(
"dmin", 0)
or 0, cached_entry.get(
"dmax", 0)
or 0
4088 c_rw = cached_entry.get(
"rw_steps", 0)
or 0
4090 found =
"exact distance" if (c_dmin > 0
and c_dmin == c_dmax)
else f
"dmin={c_dmin} >= dstop={dstop}"
4092 print(f
"[dist_m4ri] Cache retrieval: SUCCESS (found cached {found} for '{code_key}')")
4093 print(f
"[dist_m4ri] Cached result: dist={_dist_from_bounds(c_dmin, c_dmax)}, "
4094 f
"bounds={format_bounds_str(d_info)}")
4095 elif debug & PY_DBG_CACHE:
4096 print(f
"[dist_m4ri] Cache hit for {kind} distance ({found} known)!")
4097 cws_res = [list(cw)
for cw
in cached_entry.get(
"cws", [])]
if need_cws
else []
4098 hit = {
"dist":
_dist_from_bounds(c_dmin, c_dmax),
"dmin": c_dmin,
"dmax": c_dmax,
4099 "rw_steps": c_rw,
"d_info": d_info,
"cws": cws_res,
"cached":
True}
4102 print(f
"[dist_m4ri] Cache retrieval: PARTIAL (cached bounds: dmin={c_dmin}, dmax={c_dmax}, "
4103 f
"rw_steps={cached_entry.get('rw_steps', 0)}; continuing search)")
4106 print(f
"[dist_m4ri] Cache retrieval: MISS (no entry for '{code_key}')")
4108 code_key = cached_entry = plain_key = plain_entry = hit =
None
4110 print(
"[dist_m4ri] Cache retrieval: DISABLED (cache is turned off)")
4112 hit[
"cws"] = write_out(hit[
"cws"])
4115 if wmax > 0
and eff_dmin > max(wmax, dmin):
4116 src = cached_entry
or plain_entry
4117 if not (method & 1)
and src
is not None:
4118 c_dmin, c_dmax, c_rw = src.get(
"dmin", 0)
or 0, src.get(
"dmax", 0)
or 0, src.get(
"rw_steps", 0)
or 0
4120 print(f
"[dist_m4ri] Cached lower bound dmin={c_dmin} > wmax={wmax}: no CC round needed")
4121 cws_res = write_out([list(cw)
for cw
in src.get(
"cws", [])]
if need_cws
else [])
4122 return {
"dist":
_dist_from_bounds(c_dmin, c_dmax),
"dmin": c_dmin,
"dmax": c_dmax,
"rw_steps": c_rw,
4124 eff_dmin = max(wmax, dmin)
4126 temp_files: List[str] = []
4128 def input_file(M: Any, ext: str) -> Optional[str]:
4131 if isinstance(M, (str, Path))
and os.path.exists(str(M)):
4134 temp_files.append(path)
4138 if fdem
is not None:
4139 if isinstance(fdem, (str, Path))
and os.path.exists(str(fdem)):
4140 file_dem = str(fdem)
4143 temp_files.append(file_dem)
4145 file_H, file_G, file_L = input_file(H,
"_H.mtx"), input_file(G,
"_G.mtx"), input_file(L,
"_L.mtx")
4147 if fdem
is not None and fdrop
is not None:
4148 if isinstance(fdrop, (str, Path)):
4149 file_drop = str(fdrop)
4150 elif len(fdrop) > 0:
4152 temp_files.append(file_drop)
4153 with open(file_drop,
"w")
as f:
4154 f.write(
" ".join(str(r)
for r
in fdrop) +
"\n")
4158 temp_files.append(outC_file)
4161 dist_m4ri_path=dist_m4ri,
4168 classical=-1
if fdem
is not None else (1
if classical
else 0),
4176 start_scan=bool(getattr(start_list,
"scan",
False)),
4188 outD=outD
if fdem
is not None else None,
4189 sort_rows=sort_rows,
4196 nothrottle=nothrottle,
4197 chunk_size=chunk_size,
4209 cws: List[List[int]] = []
4210 if need_cws
and outC_file
and os.path.exists(outC_file):
4214 if _use_distance_cache
and code_key
is not None:
4215 prev = cached_entry
or {}
4216 p_dmax = prev.get(
"dmax", 0)
or 0
4217 best_dmax = min(p_dmax, dmax_res)
if (p_dmax > 0
and dmax_res > 0)
else max(p_dmax, dmax_res)
4218 best_dmin = max(prev.get(
"dmin", 0)
or 0, dmin_res)
4219 total_rw = (prev.get(
"rw_steps", 0)
or 0) + rw_steps
4221 if "dmin_model" in prev:
4222 record[
"dmin_model"] = prev[
"dmin_model"]
4223 _distance_cache[code_key] = record
4224 if plain_key
is not None and code_key != plain_key:
4229 res = {
"dist": record[
"dist"],
"dmin": record[
"dmin"],
"dmax": record[
"dmax"],
"rw_steps": total_rw,
4230 "d_info": record[
"d_info"],
"cws": record[
"cws"]
if need_cws
else [],
"cached":
False}
4232 res = {
"dist":
_dist_from_bounds(dmin_res, dmax_res),
"dmin": dmin_res,
"dmax": dmax_res,
4233 "rw_steps": rw_steps,
"d_info":
format_bounds_list(dmin_res, dmax_res, rw_steps),
"cws": cws,
4235 res[
"cws"] = write_out(res[
"cws"])
4243 dist_m4ri: Optional[str] =
None,
4245 threads: Optional[int] =
None,
4246 timeout: float = 60.0,
4247 num_steps: Optional[int] =
None,
4256 smax: Optional[int] =
None,
4257 start: Optional[Union[int, str, Sequence[int]]] =
None,
4258 cbeg: Optional[int] =
None,
4259 cend: Optional[int] =
None,
4263 finC: Optional[str] =
None,
4264 outC: Optional[str] =
None,
4265 do_cws: bool =
False,
4266 return_info: bool =
False,
4267 cache_file: Optional[Union[str, Path]] =
None,
4268 solver: str =
"dist_m4ri",
4269 codedistance_method: str =
"QDistEvol",
4270 codedistance_params: Optional[Dict[str, Any]] =
None,
4273 verbose: bool =
False,
4274 nothrottle: bool =
False,
4275 chunk_size: int = 0,
4281 min_hits: Optional[int] =
None,
4283 refresh: Optional[int] =
None,
4284 trust_start: bool =
False,
4285 start_scan: bool =
False,
4286 sort_rows: Optional[bool] =
None,
4287 cc_prune: Optional[int] =
None,
4288 engine: Optional[str] =
None,
4292 Computes the minimum distance of a classical linear code given parity check matrix H.
4295 H: Parity check matrix (numpy array, scipy sparse matrix, or file path).
4296 dist_m4ri: Path to dist_m4ri executable (optional).
4297 method: Solver method (1=RW, 2=CC, 3=Bracketing default).
4298 threads: Maximum number of worker threads (default: all logical CPUs available; subject to throttling).
4299 timeout: Execution timeout in seconds (default: 60.0; 0: no limit).
4300 nothrottle: If True, the automatic thread throttling of the binary is disabled (default: False).
4301 chunk_size: Maximum RW batch claimed at once by a thread (default: 0: adaptive, 25-500 steps).
4302 num_steps: Maximum RW steps.
4303 min_hits: RW stops once the average number of hits per codeword reaches min_hits, both for the codewords of
4304 the minimum weight found and for a representative set of cov_cws lowest-weight codewords (see
4305 run_dist_m4ri()); passed if not None and >= 0 (None, the default: the binary's 5; 0: off, and then the
4306 repeated hits are not counted, unless codewords are collected with do_cws, outC, or maxC).
4307 cov_cws: Size of the representative set of lowest-weight codewords for min_hits (default: 100; 0: only the
4309 kwin / win_mode: A window of kwin columns in every RW step (alias: win; win_mode=0, the default: a Tanner
4310 graph ball, 1: an index window); kwin=0 (default): the automatic hybrid windows for n >= 500, see
4311 win_hyb and run_dist_m4ri(); kwin=-1: no windows (uniform random permutations in every step).
4312 refresh: RW steps between the basis refreshes of the experimental ksub RW (passed if not None; None,
4313 the default: the binary's 5000 with ksub > 0; 0: no refresh).
4314 d_exp: Expected distance estimate.
4315 d_min / dmin: Known lower bound on distance.
4316 d_max / dmax: Known upper bound on distance.
4317 dstop: Stop target for the lower bound (default: 0 = off): the search ends once CC has certified
4318 dmin >= dstop (method=2 or 3), unless a lighter codeword is found. Unlike dmax, dstop is not an upper
4319 bound, and it is not reported as dmax: d_info = [dmin, w, rw_steps] with dmin >= dstop and the weight w
4320 of the lightest codeword found (0: none). A cached lower bound dmin >= dstop answers the request without
4322 wmin: Minimum distance of interest (terminate early if cw of weight <= wmin is found in RW or CC, default: 1).
4323 wmax: Maximum weight to search in CC.
4324 smax: Maximum syndrome weight for CC confinement profile.
4325 start: Expert option: list of CC start columns (int, comma-separated string "a,b,c", or
4326 sequence of ints; None or -1: all columns). CC clusters are grown only from the listed
4327 columns, and they are not limited to larger column indices. WARNING: the lower bound
4328 (and an exact result) is valid only if a code symmetry maps every minimum-weight codeword
4329 to one containing a listed column (e.g., one column per block of a quasi-cyclic code);
4330 the upper bound is always valid. Results are cached under the separate key
4331 '<key>:start=a,b,c'; only dmax and codewords update the main cache record.
4332 trust_start: Expert option: accept the start-list results as valid, i.e., also merge dmin and
4333 rw_steps into the main cache record, and copy an existing exact start-list record to the
4334 main record even if no calculation is needed (default: False).
4335 start_scan: Expert option, with start (default: False): a cluster grown from a listed column c only
4336 uses the columns > c (see run_dist_m4ri()), e.g., start=[0, l] for the two blocks of a two-block
4337 group-algebra code over an abelian group; cached under the key suffix ':start=a,b,c:scan' (not for a
4338 DEM: ignored with a warning).
4339 cbeg / cend / noscan: Disabled in the Python interface: accepted for backward compatibility,
4340 but ignored with a warning to stderr (use the dist_m4ri binary directly).
4341 sort_rows: Row order of H in the binary (see run_dist_m4ri()): True sorts the rows by increasing weight
4342 (faster CC for very different row weights with cc_prune=0), False keeps their order, and None (default)
4343 leaves the binary's default (sorted for matrices; the detectors of a DEM are kept in time order). The
4344 result and the cache key do not depend on it.
4345 cc_prune: The CC search of the binary (see run_dist_m4ri()): 0, the walk of the versions before 0.15.0; 1,
4346 the branch and bound (each codeword found once); 2, also the coverage and fractional cover bounds; None
4347 (default): the binary's default 1. The result and the cache key do not depend on it.
4348 win_hyb: Window size of the automatic hybrid RW steps (kwin=0, n >= 500: a window on every other step; see
4349 run_dist_m4ri()); 0 (default): 256 columns for a DEM, 512 for matrices. The cache key does not depend on
4351 pairs: The RW pairs of non-pivot columns (p=2, see run_dist_m4ri()): the number of random pivot rows of
4352 their buckets (0: off; -1, the default: auto). The cache key does not depend on it.
4353 ksub: Experimental, should not be used (a warning is printed to stderr for ksub > 0 with method=1 or 3):
4354 subspace dimension sampled from ker(H) in RW (default: 0, full-matrix RW).
4355 dW: Extra weight window above dmin to collect codewords (only irreducible ones: not the union of a
4356 codeword and a disjoint nonzero vector with zero syndrome; the binary's default irred=1).
4357 maxC: Maximum number of codewords to collect.
4358 finC: Input file with initial codewords.
4359 outC: Output file to save codewords (NZLIST format).
4360 do_cws: Whether to return extracted codewords.
4361 return_info: If True, return (dist, d_info) or (dist, d_info, cws) where d_info is [dmin, dmax, num_rw].
4362 cache_file: Optional JSON file path for persistent distance caching.
4363 solver: "dist_m4ri" or "codedistance".
4364 codedistance_method: Method if using codedistance library.
4365 codedistance_params: Extra parameters for codedistance library.
4367 debug: Debug bitmap: the bits in BIN_DBG_MASK for the dist_m4ri binary, and the PY_DBG_* bits.
4368 verbose: Verbose reporting flag.
4371 dist or (dist, cws) if do_cws is True (or (dist, d_info) / (dist, d_info, cws) if return_info=True)
4374 TypeError: for an unknown keyword argument (e.g., a misspelled option).
4375 ValueError: for an invalid option value (e.g., start, or cc_prune other than 0, 1, or 2), for method=2 with
4376 timeout <= 0 but without wmax, dmax, or dstop, or for do_cws / outC with solver="codedistance".
4377 FileNotFoundError: if the dist_m4ri binary is not found (see find_dist_m4ri_binary()).
4378 RuntimeError: if the binary exits with an error (e.g., for a missing input file).
4379 ImportError: if solver="codedistance" but the codedistance package is not installed.
4381 eff_dmin = dmin
if dmin > 0
else d_min
4382 eff_dmax = dmax
if dmax > 0
else d_max
4385 start_list =
_prepare_start_option(start, trust_start, noscan, cbeg, cend, solver, start_scan=start_scan)
4388 if solver ==
"codedistance" or str(engine
or "").strip().lower() ==
"codedistance":
4390 raise ValueError(
"Codeword extraction is not supported with codedistance solver; use solver='dist_m4ri'.")
4394 params = dict(codedistance_params
or {})
4395 if num_steps
is not None and "iterCount" not in params:
4396 params[
"iterCount"] = num_steps
4398 H_mat = H.toarray()
if hasattr(H,
'toarray')
else (
4399 np.asarray(H, dtype=np.int8)
if isinstance(H, (np.ndarray, list))
else None
4401 res = codedistance.codeDistance(
4402 H_mat,
None, tB=1, method=codedistance_method, params=params,
4403 seed=seed
if seed != 0
else None
4405 d = res.get(
"d", -1)
4411 eff_cache_file = str(Path(cache_file).resolve())
if cache_file
is not None else _distance_cache_file
4413 cert_mode, method=method, do_cws=do_cws, outC=outC, finC=finC, smax=smax, dW=dW, maxC=maxC,
4414 start_list=start_list,
4417 mode_arg =
"ub" if method == 1
else "auto"
4420 lambda w_lo, w_hi: qec_pipeline.certify(
4421 qec_io.DistanceProblem(
_cert_to_csr(H, qec_io),
None,
None),
4422 wmin=w_lo, wmax=w_hi, threads=threads
or (os.cpu_count()
or 8),
4423 seed=seed
if seed != 0
else 1,
4424 time_limit=float(timeout)
if timeout
and timeout > 0
else 0.0,
4427 label=
"classical", dmin=eff_dmin, dmax=eff_dmax, dstop=dstop, wmax=wmax,
4428 eff_cache_file=eff_cache_file, debug=debug, verbose=verbose,
4430 dist, d_info = res[
"dist"], res[
"d_info"]
4431 return (dist, d_info)
if return_info
else dist
4435 H,
None,
None,
True,
"classical", dist_m4ri=dist_m4ri, method=method, threads=threads, timeout=timeout,
4436 num_steps=num_steps, d_exp=d_exp, dmin=eff_dmin, dmax=eff_dmax, dstop=dstop, wmin=wmin, wmax=wmax,
4437 smax=smax, start_list=start_list, dW=dW, maxC=maxC, finC=finC, outC=outC, need_cws=bool(do_cws
or outC),
4438 eff_cache_file=eff_cache_file, seed=seed, debug=debug, verbose=verbose, nothrottle=nothrottle,
4439 chunk_size=chunk_size, ksub=ksub, kwin=kwin
if kwin != 0
else int(kwargs.get(
"win", 0)
or 0),
4440 win_mode=win_mode, min_hits=min_hits, cov_cws=cov_cws, refresh=refresh, trust_start=trust_start,
4441 sort_rows=sort_rows, win_hyb=win_hyb, pairs=pairs, cc_prune=cc_prune
4443 dist, d_info, cws = res[
"dist"], res[
"d_info"], res[
"cws"]
4445 return (dist, d_info, cws)
if do_cws
else (dist, d_info)
4446 return (dist, cws)
if do_cws
else dist
4451 G: Optional[Any] =
None,
4452 L: Optional[Any] =
None,
4453 dist_m4ri: Optional[str] =
None,
4455 threads: Optional[int] =
None,
4456 timeout: float = 60.0,
4457 num_steps: Optional[int] =
None,
4466 smax: Optional[int] =
None,
4467 start: Optional[Union[int, str, Sequence[int]]] =
None,
4468 cbeg: Optional[int] =
None,
4469 cend: Optional[int] =
None,
4473 finC: Optional[str] =
None,
4474 outC: Optional[str] =
None,
4475 do_cws: bool =
False,
4476 return_info: bool =
False,
4477 cache_file: Optional[Union[str, Path]] =
None,
4478 solver: str =
"dist_m4ri",
4479 codedistance_method: str =
"QDistEvol",
4480 codedistance_params: Optional[Dict[str, Any]] =
None,
4483 verbose: bool =
False,
4484 nothrottle: bool =
False,
4485 chunk_size: int = 0,
4491 min_hits: Optional[int] =
None,
4493 refresh: Optional[int] =
None,
4494 trust_start: bool =
False,
4495 start_scan: bool =
False,
4496 sort_rows: Optional[bool] =
None,
4497 cc_prune: Optional[int] =
None,
4499 engine: Optional[str] =
None,
4503 Computes the minimum distance of a single-sided quantum code given parity check matrix H
4504 and degeneracy generator G (or logical operator matrix L).
4507 H: Parity check matrix (numpy array, scipy sparse matrix, or file path).
4508 G: Degeneracy generator matrix (numpy array, scipy sparse matrix, or file path); the binary constructs the
4509 logical operators from H and G. Either G or L is required, not both (ValueError).
4510 L: Logical operator matrix (numpy array, scipy sparse matrix, or file path), instead of G.
4511 ortho: Expert option (default: True; since version 0.15.2): False skips the check of the binary that the rows
4512 of H and G are orthogonal (H G^T = 0; True: an error otherwise), e.g., to screen many codes which are
4513 orthogonal by construction; a warning is printed to stderr, and the result is valid only if H G^T = 0. No
4514 effect without G. The cache key does not depend on it.
4515 dist_m4ri: Path to dist_m4ri executable (optional).
4516 method: Solver method (1=RW, 2=CC, 3=Bracketing default).
4517 threads: Maximum number of worker threads (default: all logical CPUs available; subject to throttling).
4518 timeout: Execution timeout in seconds (default: 60.0; 0: no limit).
4519 nothrottle: If True, the automatic thread throttling of the binary is disabled (default: False).
4520 chunk_size: Maximum RW batch claimed at once by a thread (default: 0: adaptive, 25-500 steps).
4521 num_steps: Maximum RW steps.
4522 min_hits: RW stops once the average number of hits per codeword reaches min_hits, both for the codewords of
4523 the minimum weight found and for a representative set of cov_cws lowest-weight codewords (see
4524 run_dist_m4ri()); passed if not None and >= 0 (None, the default: the binary's 5; 0: off, and then the
4525 repeated hits are not counted, unless codewords are collected with do_cws, outC, or maxC).
4526 cov_cws: Size of the representative set of lowest-weight codewords for min_hits (default: 100; 0: only the
4528 kwin / win_mode: A window of kwin columns in every RW step (alias: win; win_mode=0, the default: a Tanner
4529 graph ball, 1: an index window); kwin=0 (default): the automatic hybrid windows for n >= 500, see
4530 win_hyb and run_dist_m4ri(); kwin=-1: no windows (uniform random permutations in every step).
4531 refresh: RW steps between the basis refreshes of the experimental ksub RW (passed if not None; None,
4532 the default: the binary's 5000 with ksub > 0; 0: no refresh).
4533 d_exp: Expected distance estimate.
4534 d_min / dmin: Known lower bound on distance.
4535 d_max / dmax: Known upper bound on distance.
4536 dstop: Stop target for the lower bound (default: 0 = off): the search ends once CC has certified
4537 dmin >= dstop (method=2 or 3), unless a lighter codeword is found. Unlike dmax, dstop is not an upper
4538 bound, and it is not reported as dmax: d_info = [dmin, w, rw_steps] with dmin >= dstop and the weight w
4539 of the lightest codeword found (0: none). A cached lower bound dmin >= dstop answers the request without
4541 wmin: Minimum distance of interest (terminate early if cw of weight <= wmin is found in RW or CC, default: 1).
4542 wmax: Maximum weight to search in CC.
4543 smax: Maximum syndrome weight for CC confinement profile.
4544 start: Expert option: list of CC start columns (int, comma-separated string "a,b,c", or
4545 sequence of ints; None or -1: all columns). CC clusters are grown only from the listed
4546 columns, and they are not limited to larger column indices. WARNING: the lower bound
4547 (and an exact result) is valid only if a code symmetry maps every minimum-weight codeword
4548 to one containing a listed column (e.g., one column per block of a quasi-cyclic code);
4549 the upper bound is always valid. Results are cached under the separate key
4550 '<key>:start=a,b,c'; only dmax and codewords update the main cache record.
4551 trust_start: Expert option: accept the start-list results as valid, i.e., also merge dmin and
4552 rw_steps into the main cache record, and copy an existing exact start-list record to the
4553 main record even if no calculation is needed (default: False).
4554 start_scan: Expert option, with start (default: False): a cluster grown from a listed column c only
4555 uses the columns > c (see run_dist_m4ri()), e.g., start=[0, l] for the two blocks of a two-block
4556 group-algebra code over an abelian group; cached under the key suffix ':start=a,b,c:scan' (not for a
4557 DEM: ignored with a warning).
4558 cbeg / cend / noscan: Disabled in the Python interface: accepted for backward compatibility,
4559 but ignored with a warning to stderr (use the dist_m4ri binary directly).
4560 sort_rows: Row order of H in the binary (see run_dist_m4ri()): True sorts the rows by increasing weight
4561 (faster CC for very different row weights with cc_prune=0), False keeps their order, and None (default)
4562 leaves the binary's default (sorted for matrices; the detectors of a DEM are kept in time order). The
4563 result and the cache key do not depend on it.
4564 cc_prune: The CC search of the binary (see run_dist_m4ri()): 0, the walk of the versions before 0.15.0; 1,
4565 the branch and bound (each codeword found once); 2, also the coverage and fractional cover bounds; None
4566 (default): the binary's default 1. The result and the cache key do not depend on it.
4567 win_hyb: Window size of the automatic hybrid RW steps (kwin=0, n >= 500: a window on every other step; see
4568 run_dist_m4ri()); 0 (default): 256 columns for a DEM, 512 for matrices. The cache key does not depend on
4570 pairs: The RW pairs of non-pivot columns (p=2, see run_dist_m4ri()): the number of random pivot rows of
4571 their buckets (0: off; -1, the default: auto). The cache key does not depend on it.
4572 ksub: Experimental, should not be used (a warning is printed to stderr for ksub > 0 with method=1 or 3):
4573 subspace dimension sampled from ker(H) in RW (default: 0, full-matrix RW).
4574 dW: Extra weight window above dmin to collect codewords (only irreducible ones: not the union of a
4575 codeword and a disjoint nonzero vector with zero syndrome; the binary's default irred=1).
4576 maxC: Maximum number of codewords to collect.
4577 finC: Input file with initial codewords.
4578 outC: Output file to save codewords (NZLIST format).
4579 do_cws: Whether to return extracted codewords.
4580 return_info: If True, return (dist, d_info) or (dist, d_info, cws) where d_info is [dmin, dmax, num_rw].
4581 cache_file: Optional JSON file path for persistent distance caching.
4582 solver: "dist_m4ri" or "codedistance".
4583 codedistance_method: Method if using codedistance library.
4584 codedistance_params: Extra parameters for codedistance library.
4586 debug: Debug bitmap: the bits in BIN_DBG_MASK for the dist_m4ri binary, and the PY_DBG_* bits.
4587 verbose: Verbose reporting flag.
4590 dist or (dist, cws) if do_cws is True (or (dist, d_info) / (dist, d_info, cws) if return_info=True)
4593 TypeError: for an unknown keyword argument (e.g., a misspelled option).
4594 ValueError: if neither or both of G and L are given (or G is not given, with solver="codedistance"), for an
4595 invalid option value (e.g., start, or cc_prune other than 0, 1, or 2), for method=2 with timeout <= 0
4596 but without wmax, dmax, or dstop, or for do_cws / outC with solver="codedistance".
4597 FileNotFoundError: if the dist_m4ri binary is not found (see find_dist_m4ri_binary()).
4598 RuntimeError: if the binary exits with an error (e.g., for a missing input file, or with ortho=True for rows
4599 of H and G which are not orthogonal).
4600 ImportError: if solver="codedistance" but the codedistance package is not installed.
4602 eff_dmin = dmin
if dmin > 0
else d_min
4603 eff_dmax = dmax
if dmax > 0
else d_max
4606 start_list =
_prepare_start_option(start, trust_start, noscan, cbeg, cend, solver, start_scan=start_scan)
4609 if G
is None and L
is None:
4611 "Either G (dual generator matrix) or L (logical operator matrix) "
4612 "must be specified for quantum distance."
4614 if G
is not None and L
is not None:
4615 raise ValueError(
"Either G or L should be specified for quantum distance, not both.")
4618 if solver ==
"codedistance" or str(engine
or "").strip().lower() ==
"codedistance":
4620 raise ValueError(
"Codeword extraction is not supported with codedistance solver; use solver='dist_m4ri'.")
4624 params = dict(codedistance_params
or {})
4625 if num_steps
is not None and "iterCount" not in params:
4626 params[
"iterCount"] = num_steps
4628 H_mat = H.toarray()
if hasattr(H,
'toarray')
else (
4629 np.asarray(H, dtype=np.int8)
if isinstance(H, (np.ndarray, list))
else None
4631 if G
is None and L
is not None:
4633 "The 'codedistance' solver requires the stabilizer generator matrix G, "
4634 "not the logical operator matrix L. Use solver='dist_m4ri' with L."
4636 dual_mat = G
if G
is not None else L
4637 dual_arr = dual_mat.toarray()
if hasattr(dual_mat,
'toarray')
else (
4638 np.asarray(dual_mat, dtype=np.int8)
if isinstance(dual_mat, (np.ndarray, list))
else None
4641 res = codedistance.codeDistance(
4642 H_mat, dual_arr, tB=1, method=codedistance_method, params=params,
4643 seed=seed
if seed != 0
else None
4645 d = res.get(
"d", -1)
4651 eff_cache_file = str(Path(cache_file).resolve())
if cache_file
is not None else _distance_cache_file
4653 cert_mode, method=method, do_cws=do_cws, outC=outC, finC=finC, smax=smax, dW=dW, maxC=maxC,
4654 start_list=start_list,
4657 mode_arg =
"ub" if method == 1
else "auto"
4660 lambda w_lo, w_hi: qec_pipeline.certify(
4661 qec_io.DistanceProblem(
4664 wmin=w_lo, wmax=w_hi, threads=threads
or (os.cpu_count()
or 8),
4665 seed=seed
if seed != 0
else 1,
4666 time_limit=float(timeout)
if timeout
and timeout > 0
else 0.0,
4669 label=
"quantum", dmin=eff_dmin, dmax=eff_dmax, dstop=dstop, wmax=wmax,
4670 eff_cache_file=eff_cache_file, debug=debug, verbose=verbose,
4672 dist, d_info = res[
"dist"], res[
"d_info"]
4673 return (dist, d_info)
if return_info
else dist
4677 H, G, L,
False,
"quantum", dist_m4ri=dist_m4ri, method=method, threads=threads, timeout=timeout,
4678 num_steps=num_steps, d_exp=d_exp, dmin=eff_dmin, dmax=eff_dmax, dstop=dstop, wmin=wmin, wmax=wmax,
4679 smax=smax, start_list=start_list, dW=dW, maxC=maxC, finC=finC, outC=outC, need_cws=bool(do_cws
or outC),
4680 eff_cache_file=eff_cache_file, seed=seed, debug=debug, verbose=verbose, nothrottle=nothrottle,
4681 chunk_size=chunk_size, ksub=ksub, kwin=kwin
if kwin != 0
else int(kwargs.get(
"win", 0)
or 0),
4682 win_mode=win_mode, min_hits=min_hits, cov_cws=cov_cws, refresh=refresh, trust_start=trust_start,
4683 sort_rows=sort_rows, win_hyb=win_hyb, pairs=pairs, cc_prune=cc_prune, ortho=ortho
4685 dist, d_info, cws = res[
"dist"], res[
"d_info"], res[
"cws"]
4687 return (dist, d_info, cws)
if do_cws
else (dist, d_info)
4688 return (dist, cws)
if do_cws
else dist
4692 """Generates sector-suffixed filename for CSS codewords (e.g. 'cws.nz' -> 'cws_X.nz')."""
4695 base, ext = os.path.splitext(filepath)
4698 if base.endswith(f
"_{sector}"):
4699 return f
"{base}{ext}"
4700 elif base.endswith(
"_X")
or base.endswith(
"_Z"):
4702 return f
"{base}_{sector}{ext}"
4705def _css_sector_finc(finC: Optional[str], outC: Optional[str]) -> Tuple[Optional[str], Optional[str]]:
4707 Input codeword files (finC_X, finC_Z) of the two CSS sectors: the sector files (e.g. 'cws.nz' -> 'cws_X.nz' and
4708 'cws_Z.nz') which exist and are not empty; if there are none, finC itself for both sectors (codewords which are
4709 not valid in a sector are skipped by the binary), unless finC is identical to outC and does not exist or is
4710 empty (see check_finc_outc()).
4715 files = [f
if (os.path.isfile(f)
and os.path.getsize(f) > 0)
else None for f
in files]
4716 if files[0]
or files[1]:
4717 return files[0], files[1]
4725 Lx: Optional[Any] =
None,
4726 Lz: Optional[Any] =
None,
4727 dist_m4ri: Optional[str] =
None,
4729 threads: Optional[int] =
None,
4730 timeout: float = 60.0,
4731 num_steps: Optional[int] =
None,
4740 smax: Optional[int] =
None,
4741 start: Optional[Union[int, str, Sequence[int]]] =
None,
4742 cbeg: Optional[int] =
None,
4743 cend: Optional[int] =
None,
4747 finC: Optional[str] =
None,
4748 outC: Optional[str] =
None,
4749 do_cws: bool =
False,
4750 cache_file: Optional[Union[str, Path]] =
None,
4751 solver: str =
"dist_m4ri",
4752 codedistance_method: str =
"QDistEvol",
4753 codedistance_params: Optional[Dict[str, Any]] =
None,
4756 verbose: bool =
False,
4757 nothrottle: bool =
False,
4758 chunk_size: int = 0,
4764 min_hits: Optional[int] =
None,
4766 refresh: Optional[int] =
None,
4767 trust_start: bool =
False,
4768 start_scan: bool =
False,
4769 sort_rows: Optional[bool] =
None,
4770 cc_prune: Optional[int] =
None,
4772 engine: Optional[str] =
None,
4774) -> Tuple[Any, ...]:
4776 Computes CSS quantum code distance d = min(d_X, d_Z).
4779 Hx: X-stabilizer parity check matrix (required, at least one row).
4780 Hz: Z-stabilizer parity check matrix (required, at least one row).
4781 Lx: Optional X-logical operator matrix (alternative to Hz as finG).
4782 Lz: Optional Z-logical operator matrix (alternative to Hx as finG).
4783 ortho: Expert option (default: True; since version 0.15.2): False skips the checks of the binary that the
4784 rows of Hx and Hz are orthogonal (Hx Hz^T = 0, in each sector with G; True: an error otherwise), e.g., to
4785 screen many codes which are orthogonal by construction; a warning is printed to stderr, and the result is
4786 valid only if Hx Hz^T = 0. No effect with both Lx and Lz. The cache key does not depend on it.
4787 dist_m4ri: Path to dist_m4ri executable (optional).
4788 method: Solver method (1=RW, 2=CC, 3=Bracketing default).
4789 threads: Maximum number of worker threads (default: all logical CPUs available; subject to throttling).
4790 timeout: Execution timeout in seconds (default: 60.0; 0: no limit).
4791 nothrottle: If True, the automatic thread throttling of the binary is disabled (default: False).
4792 chunk_size: Maximum RW batch claimed at once by a thread (default: 0: adaptive, 25-500 steps).
4793 num_steps: Maximum RW steps.
4794 min_hits: RW stops once the average number of hits per codeword reaches min_hits, both for the codewords of
4795 the minimum weight found and for a representative set of cov_cws lowest-weight codewords (see
4796 run_dist_m4ri()); passed if not None and >= 0 (None, the default: the binary's 5; 0: off, and then the
4797 repeated hits are not counted, unless codewords are collected with do_cws, outC, or maxC).
4798 cov_cws: Size of the representative set of lowest-weight codewords for min_hits (default: 100; 0: only the
4800 kwin / win_mode: A window of kwin columns in every RW step (alias: win; win_mode=0, the default: a Tanner
4801 graph ball, 1: an index window); kwin=0 (default): the automatic hybrid windows for n >= 500, see
4802 win_hyb and run_dist_m4ri(); kwin=-1: no windows (uniform random permutations in every step).
4803 refresh: RW steps between the basis refreshes of the experimental ksub RW (passed if not None; None,
4804 the default: the binary's 5000 with ksub > 0; 0: no refresh).
4805 d_exp: Expected distance estimate.
4806 d_min / dmin: Known lower bound on d = min(dX, dZ), inclusive (a lower bound of both sectors).
4807 d_max / dmax: Known upper bound on d = min(dX, dZ), inclusive. As it is not an upper bound of either
4808 sector, it is passed to both sector runs as the stop target dstop (each sector is needed only up to this
4809 weight), and it is not stored in the sector cache records; the returned d is at most dmax.
4810 dstop: Stop target for the lower bound of d (default: 0 = off): both sector runs end once CC has certified
4811 dmin >= dstop, unless a lighter codeword is found (see compute_classical_distance()).
4812 wmin: Minimum distance of interest (terminate early if cw of weight <= wmin is found in RW or CC, default: 1).
4813 wmax: Maximum weight to search in CC.
4814 smax: Maximum syndrome weight for CC confinement profile.
4815 start: Expert option: list of CC start columns (int, comma-separated string "a,b,c", or
4816 sequence of ints; None or -1: all columns). CC clusters are grown only from the listed
4817 columns, and they are not limited to larger column indices. WARNING: the lower bound
4818 (and an exact result) is valid only if a code symmetry maps every minimum-weight codeword
4819 to one containing a listed column (e.g., one column per block of a quasi-cyclic code);
4820 the upper bound is always valid. Results are cached under the separate key
4821 '<key>:start=a,b,c'; only dmax and codewords update the main cache record.
4822 trust_start: Expert option: accept the start-list results as valid, i.e., also merge dmin and
4823 rw_steps into the main cache record, and copy an existing exact start-list record to the
4824 main record even if no calculation is needed (default: False).
4825 start_scan: Expert option, with start (default: False): a cluster grown from a listed column c only
4826 uses the columns > c (see run_dist_m4ri()), e.g., start=[0, l] for the two blocks of a two-block
4827 group-algebra code over an abelian group; cached under the key suffix ':start=a,b,c:scan' (not for a
4828 DEM: ignored with a warning).
4829 cbeg / cend / noscan: Disabled in the Python interface: accepted for backward compatibility,
4830 but ignored with a warning to stderr (use the dist_m4ri binary directly).
4831 sort_rows: Row order of H in the binary (see run_dist_m4ri()): True sorts the rows by increasing weight
4832 (faster CC for very different row weights with cc_prune=0), False keeps their order, and None (default)
4833 leaves the binary's default (sorted for matrices; the detectors of a DEM are kept in time order). The
4834 result and the cache key do not depend on it.
4835 cc_prune: The CC search of the binary (see run_dist_m4ri()): 0, the walk of the versions before 0.15.0; 1,
4836 the branch and bound (each codeword found once); 2, also the coverage and fractional cover bounds; None
4837 (default): the binary's default 1. The result and the cache key do not depend on it.
4838 win_hyb: Window size of the automatic hybrid RW steps (kwin=0, n >= 500: a window on every other step; see
4839 run_dist_m4ri()); 0 (default): 256 columns for a DEM, 512 for matrices. The cache key does not depend on
4841 pairs: The RW pairs of non-pivot columns (p=2, see run_dist_m4ri()): the number of random pivot rows of
4842 their buckets (0: off; -1, the default: auto). The cache key does not depend on it.
4843 ksub: Experimental, should not be used (a warning is printed to stderr for ksub > 0 with method=1 or 3):
4844 subspace dimension sampled from ker(H) in RW (default: 0, full-matrix RW).
4845 dW: Extra weight window above dmin to collect codewords (only irreducible ones: not the union of a
4846 codeword and a disjoint nonzero vector with zero syndrome; the binary's default irred=1).
4847 maxC: Maximum number of codewords to collect.
4848 finC: Input file with initial codewords.
4849 outC: Output file to save codewords (NZLIST format).
4850 do_cws: Whether to return extracted X and Z codewords.
4851 cache_file: Optional JSON file path for persistent distance caching. The two sectors are cached as the
4852 quantum codes (H=Hx, G=Hz) -> dZ and (H=Hz, G=Hx) -> dX (with L=Lx / L=Lz if given), i.e., they share
4853 the records of compute_quantum_distance(); CSS records written before version 0.12.0 are converted.
4854 solver: "dist_m4ri" or "codedistance".
4855 codedistance_method: Method if using codedistance library.
4856 codedistance_params: Extra parameters for codedistance library.
4858 debug: Debug bitmap: the bits in BIN_DBG_MASK for the dist_m4ri binary, and the PY_DBG_* bits.
4859 verbose: Verbose reporting flag.
4862 tuple (dist, dX_info, dZ_info, cws_X, cws_Z) if do_cws
4863 else (dist, dX_info, dZ_info)
4866 ValueError: if Hx or Hz is missing or empty (use compute_quantum_distance() for one sector, or
4867 compute_classical_distance() for a classical code), for an invalid option value (e.g., start, or
4868 cc_prune other than 0, 1, or 2), for method=2 with timeout <= 0 but without wmax, dmax, or dstop, or
4869 for do_cws / outC with solver="codedistance".
4870 TypeError: for an unknown keyword argument (e.g., a misspelled option).
4871 FileNotFoundError: if the dist_m4ri binary is not found (see find_dist_m4ri_binary()).
4872 RuntimeError: if the binary exits with an error (e.g., for a missing input file, or with ortho=True for
4873 rows of Hx and Hz which are not orthogonal).
4874 ImportError: if solver="codedistance" but the codedistance package is not installed.
4876 def _as_matrix(M: Any) -> Any:
4877 if M
is None or isinstance(M, (str, Path))
or hasattr(M,
'shape'):
4880 return np.asarray(M)
4882 Hx, Hz, Lx, Lz = (_as_matrix(M)
for M
in (Hx, Hz, Lx, Lz))
4884 def _nonempty(M: Any) -> bool:
4885 return M
is not None and (isinstance(M, (str, Path))
or (hasattr(M,
'shape')
and M.shape[0] > 0))
4887 if not (_nonempty(Hx)
and _nonempty(Hz)):
4889 "compute_css_distance() requires both Hx and Hz (file names, or matrices with at least one row); "
4890 "use compute_quantum_distance() for one sector, or compute_classical_distance() for a classical code."
4893 eff_dmin = dmin
if dmin > 0
else d_min
4894 eff_dmax = dmax
if dmax > 0
else d_max
4897 start_list =
_prepare_start_option(start, trust_start, noscan, cbeg, cend, solver, start_scan=start_scan)
4899 _warn_ortho(ortho, Lx
is None or Lz
is None, solver)
4901 if solver ==
"codedistance" or str(engine
or "").strip().lower() ==
"codedistance":
4903 raise ValueError(
"Codeword extraction is not supported with codedistance solver; use solver='dist_m4ri'.")
4907 params = dict(codedistance_params
or {})
4908 if num_steps
is not None and "iterCount" not in params:
4909 params[
"iterCount"] = num_steps
4911 Hx_mat = Hx.toarray()
if hasattr(Hx,
'toarray')
else (
4912 np.asarray(Hx, dtype=np.int8)
if isinstance(Hx, (np.ndarray, list))
else None
4914 Hz_mat = Hz.toarray()
if hasattr(Hz,
'toarray')
else (
4915 np.asarray(Hz, dtype=np.int8)
if isinstance(Hz, (np.ndarray, list))
else None
4917 sector_d: Dict[str, int] = {}
4918 sector_info: Dict[str, List[int]] = {}
4919 for s
in (
"Z",
"X"):
4920 res_s = codedistance.CSScodeDistance(
4921 Hx_mat, Hz_mat, method=codedistance_method, params=params.copy(),
4922 component=s, seed=seed
if seed != 0
else None
4924 sector_d[s] = res_s.get(
"d", -1)
4925 sector_info[s] =
format_bounds_list(sector_d[s], sector_d[s], 0)
if sector_d[s] > 0
else [0, 0, 0]
4926 d_z, d_x = sector_d[
"Z"], sector_d[
"X"]
4927 dist = min(d_z, d_x)
if (d_z > 0
and d_x > 0)
else max(d_z, d_x)
4928 return (dist, sector_info[
"X"], sector_info[
"Z"])
4933 eff_cache_file = str(Path(cache_file).resolve())
if cache_file
is not None else _distance_cache_file
4934 stops = [v
for v
in (dstop, eff_dmax)
if v > 0]
4935 sector_stop = min(stops)
if stops
else 0
4936 need_cws = bool(do_cws
or outC)
4940 if verbose
and finC
and not (finC_X
or finC_Z):
4941 print(f
"[dist_m4ri] Warning: finC='{finC}' (identical to outC) and its sector files are empty or "
4942 f
"non-existent; silently ignoring input codewords.")
4943 if _use_distance_cache:
4948 print(
"[dist_m4ri] Converted the CSS cache records written before version 0.12.0 into sector records")
4953 res: Dict[str, Dict[str, Any]] = {}
4954 for s, finC_s
in ((
"Z", finC_Z), (
"X", finC_X)):
4955 H_s, G_s, L_s = codes[s]
4957 dual = (
"Hx" if s ==
"X" else "Hz")
if G_s
is not None else (
"Lz" if s ==
"X" else "Lx")
4958 print(f
"[dist_m4ri] CSS sector d{s}: H={'Hz' if s == 'X' else 'Hx'} with "
4959 f
"{'G' if G_s is not None else 'L'}={dual}")
4961 cert_mode, method=method, do_cws=do_cws, outC=outC, finC=finC_s, smax=smax,
4962 dW=dW, maxC=maxC, start_list=start_list,
4965 mode_arg =
"ub" if method == 1
else "auto"
4968 lambda w_lo, w_hi, H_s=H_s, G_s=G_s, L_s=L_s: qec_pipeline.certify(
4969 qec_io.DistanceProblem(
4972 wmin=w_lo, wmax=w_hi, threads=threads
or (os.cpu_count()
or 8),
4973 seed=seed
if seed != 0
else 1,
4974 time_limit=float(timeout)
if timeout
and timeout > 0
else 0.0,
4977 label=f
"CSS {s}-sector", dmin=eff_dmin, dmax=0, dstop=sector_stop, wmax=wmax,
4978 eff_cache_file=eff_cache_file, debug=debug, verbose=verbose,
4982 H_s, G_s, L_s,
False, f
"CSS {s}-sector", dist_m4ri=dist_m4ri, method=method, threads=threads,
4983 timeout=timeout, num_steps=num_steps, d_exp=d_exp, dmin=eff_dmin, dmax=0, dstop=sector_stop, wmin=wmin,
4984 wmax=wmax, smax=smax, start_list=start_list, dW=dW, maxC=maxC, finC=finC_s,
4985 outC=
_split_css_filename(outC, s)
if outC
else None, need_cws=need_cws, eff_cache_file=eff_cache_file,
4986 seed=seed, debug=debug, verbose=verbose, nothrottle=nothrottle, chunk_size=chunk_size, ksub=ksub,
4987 kwin=kwin
if kwin != 0
else int(kwargs.get(
"win", 0)
or 0), win_mode=win_mode, min_hits=min_hits,
4988 cov_cws=cov_cws, refresh=refresh, trust_start=trust_start, sort_rows=sort_rows, win_hyb=win_hyb,
4989 pairs=pairs, cc_prune=cc_prune, ortho=ortho
4991 _last_css_stats[s] = dict(_last_run_stats)
4993 rx, rz = res[
"X"], res[
"Z"]
4994 dist, d_lo, d_hi, _ =
_css_combine((rx[
"dmin"], rx[
"dmax"], rx[
"rw_steps"]),
4995 (rz[
"dmin"], rz[
"dmax"], rz[
"rw_steps"]), eff_dmax)
4996 if rx[
"cached"]
and rz[
"cached"]
and (debug & PY_DBG_CACHE)
and not verbose:
4997 found = (
"exact distance" if (d_lo > 0
and d_lo == d_hi)
else
4998 f
"dmin={d_lo} >= dstop={sector_stop}" if (sector_stop > 0
and d_lo >= sector_stop)
else
4999 f
"bounds dmin={d_lo}, dmax={d_hi}")
5000 print(f
"[dist_m4ri] Cache hit for CSS distance ({found} known)!")
5002 return (dist, rx[
"d_info"], rz[
"d_info"], rx[
"cws"], rz[
"cws"])
5003 return (dist, rx[
"d_info"], rz[
"d_info"])
5007 out_spec: Optional[Union[bool, str, Path]],
5011 out_dir: Optional[Union[str, Path]] =
None
5013 """Resolves output filepath for --out-dem or --out-stim."""
5014 if out_spec
is None or out_spec
is False:
5016 if isinstance(out_spec, str)
and out_spec.strip().lower()
in (
5017 "0",
"false",
"no",
"off",
"none"
5023 if (circuit_src
and circuit_src !=
"stim.Circuit")
5028 if out_dir
is not None
5029 else (src_path.parent
if src_path
is not None else Path(
"."))
5032 is_auto = (out_spec
is True)
or (
5033 isinstance(out_spec, str)
5034 and out_spec.strip().lower()
in (
"",
"1",
"true",
"yes",
"auto")
5037 if src_path
is not None:
5038 stem = src_path.stem
5039 if stem.endswith(
"_full")
or stem.endswith(
"_simp"):
5043 out_path = eff_out_dir / f
"{stem}{mode_tag}{ext}"
5045 spec_str = str(out_spec)
5046 spec_path = Path(spec_str)
5047 if not spec_path.is_absolute()
and os.path.dirname(spec_str) ==
"":
5048 out_path = eff_out_dir / spec_path
5050 out_path = spec_path
5052 parent_dir = os.path.dirname(os.path.abspath(str(out_path)))
5054 os.makedirs(parent_dir, exist_ok=
True)
5055 return str(out_path)
5060 The DEM of a Stim circuit: with decompose_errors=True if stim can decompose its errors (otherwise without). An
5061 error channel with disjoint cases which stim cannot write as independent error mechanisms (e.g., a PAULI_CHANNEL_1
5062 with X and Z but no Y errors, PAULI_CHANNEL_2, or ELSE_CORRELATED_ERROR) needs approximate_disjoint_errors=True,
5063 which is used only if the conversion fails without it: it only changes the probabilities of the error mechanisms,
5064 not which errors are possible (so neither the distance, unless pmin > 0, nor the codewords).
5067 (dem, decompose_errors, approximate_disjoint_errors): the DEM, and the options used.
5069 error: Optional[Exception] =
None
5070 for approx
in (
False,
True):
5071 for decomp
in (
True,
False):
5073 return circuit.detector_error_model(decompose_errors=decomp, approximate_disjoint_errors=approx), \
5075 except Exception
as e:
5077 assert error
is not None
5083 Writes a warning to stderr if the options simple or full (CLI: --simple, --full) are given: they are disabled since
5084 version 0.13.0 (accepted for backward compatibility, but ignored). The DEM of a Stim circuit always has all
5085 detectors, and the binary drops detectors if this is exact (drop=auto, the default; e.g., the minority-basis
5086 detectors of a CSS circuit, which the simple mode stripped): as fast, with the distance of the full DEM (the
5087 simplified DEM only gives a lower bound when dropping them is not exact).
5089 names = [f
"--{n}" if cli
else f
"{n}=" for n, v
in ((
"simple", simple), (
"full", full))
if v
is not None]
5091 _warn_ignored(f
"{', '.join(names)}: disabled since version 0.13.0, ignored: the DEM of a Stim circuit has all "
5092 f
"detectors, and the binary drops detectors if this is exact (drop=auto, the default, e.g., the "
5093 f
"minority-basis detectors of a CSS circuit; drop=0 keeps them)")
5098 circuit: Optional[Any],
5099 basis: Optional[str] =
None,
5100 rounds: Optional[int] =
None,
5101 p_noise: float = 0.001,
5102 out_dir: Optional[Union[str, Path]] =
None,
5103 out_dem: Optional[Union[bool, str, Path]] =
None,
5104 out_stim: Optional[Union[bool, str, Path]] =
None,
5105 circ_verbose: bool =
False,
5106 drop_out: Optional[List[int]] =
None,
5107 force_noise: bool =
False
5108) -> Tuple[Any, Optional[Any], Optional[str]]:
5110 The DEM of compute_dem_distance() (also used by get_cached_distance() for the cache key). A DEM
5111 (stim.DetectorErrorModel or .dem file) is used as given. A Stim circuit (stim.Circuit or .stim file, also when
5112 given as `dem`) is processed: the empty detectors are removed, the qubits are classified, the REPEAT count is set
5113 (rounds), and circuit-level noise of strength p_noise is added to a noiseless circuit (see add_noise(); none for
5114 p_noise=0), or with force_noise=True replaces the noise of the circuit (see strip_noise(); with p_noise=0, all noise
5115 is removed); then it is converted to a DEM (with all detectors). The processed circuit and the DEM are saved if
5116 requested (out_stim, out_dem, out_dir; see compute_dem_distance()). For a CSS circuit, the list drop_out (if given)
5117 is extended by the minority-basis detectors (0-based detectors of the DEM, see strip_minority_detectors()), which
5118 the binary drops if this is exact (drop=css, passed as drop=FILE). The qubits are only classified for drop_out and
5122 (dem, circuit, saved_dem_path): the DEM (object or file name), the processed circuit (None for a DEM input),
5123 and the file to which the DEM was saved (None if it was not saved).
5125 if dem
is not None and circuit
is not None:
5126 _warn_ignored(
"both 'dem' and 'circuit' were given; using 'dem' (the circuit is ignored).")
5128 if dem
is not None and isinstance(dem, (str, Path))
and str(dem).endswith(
".stim"):
5132 if rounds
is not None and circuit
is None:
5134 "--rounds option is only supported for Stim circuit (.stim) inputs."
5137 saved_dem_path: Optional[str] =
None
5138 if dem
is None and circuit
is not None:
5139 circuit_src = str(circuit)
if isinstance(circuit, (str, Path))
else "stim.Circuit"
5140 if isinstance(circuit, (str, Path)):
5142 circuit = stim.Circuit.from_file(str(circuit))
5143 if not hasattr(circuit,
'detector_error_model'):
5144 raise ValueError(
"Provided circuit object does not have detector_error_model() method.")
5146 if rounds
is not None:
5148 raise ValueError(f
"Invalid rounds={rounds}; must be >= 0.")
5152 f
"# Warning: Circuit '{circuit_src}' does not contain a "
5153 f
"REPEAT block; ignoring rounds={rounds}.\n"
5157 if empty_removed > 0
and circ_verbose:
5158 print(f
"[dist_m4ri] Removed {empty_removed} empty DETECTOR(s) from '{circuit_src}'")
5160 filepath_hint = circuit_src
if circuit_src !=
"stim.Circuit" else None
5161 t_res: Optional[Dict[str, Any]] =
None
5162 if circ_verbose
or drop_out
is not None:
5165 if rounds
is not None and has_rep:
5169 if t_res
is not None and t_res[
"is_css"]
and drop_out
is not None:
5171 mask: List[bool] = []
5174 if len(mask) == circuit.num_detectors:
5175 drop_out.extend(i
for i, s
in enumerate(mask)
if s)
5176 n_minority = sum(mask)
5178 print(f
"[dist_m4ri] Warning: {len(mask)} detectors classified, but the circuit has "
5179 f
"{circuit.num_detectors}; no detectors to drop")
5183 if circ_verbose
and t_res
is not None:
5184 is_css_circ, is_rot_css = t_res[
"is_css"], t_res.get(
"is_rotated_css",
False)
5185 css_type =
"rotated-CSS (e.g. XZZX)" if is_rot_css
else (
"CSS" if is_css_circ
else "non-CSS")
5186 rot_info = f
", rotated_data={len(t_res.get('rotated_data_qubits', []))}" if is_rot_css
else ""
5189 f
"[dist_m4ri] Circuit basis tracking: type={css_type}, "
5190 f
"basis={t_res['basis']}, data={len(t_res['data_qubits'])}, "
5191 f
"X_anc={len(t_res['x_ancillas'])}, "
5192 f
"Z_anc={len(t_res['z_ancillas'])}, "
5193 f
"routing={len(t_res['routing_qubits'])}{rot_info}, "
5196 minority = (f
", minority-basis={n_minority} (drop=css: the binary drops them if this is exact)"
5197 if n_minority
is not None else "")
5198 print(f
"[dist_m4ri] Circuit detectors: {circuit.num_detectors}{minority}")
5201 noise_msg = (f
"replaced the noise by circuit-level noise (p={p_noise})" if p_noise > 0.0
else
5202 "removed all noise (p_noise=0)")
5203 circuit =
add_noise(circuit, p=p_noise, force=
True)
5205 noise_msg =
"existing noise detected"
5207 noise_msg = f
"added circuit-level noise (p={p_noise})"
5210 noise_msg =
"no noise added (p_noise=0)"
5213 if stim_out_path
is not None:
5214 circuit.to_file(stim_out_path)
5216 print(f
"[dist_m4ri] Saved Stim circuit to '{stim_out_path}'")
5220 approx_msg =
", approximate_disjoint_errors=True" if approx_used
else ""
5221 print(f
"[dist_m4ri] Converted '{circuit_src}' to DEM ({noise_msg}, decompose_errors={decomp_used}"
5223 if all(hasattr(dem, a)
for a
in (
"num_detectors",
"num_observables",
"num_errors")):
5225 f
"[dist_m4ri] DEM size: {dem.num_detectors} detectors, {dem.num_observables} observables, "
5226 f
"{dem.num_errors} error mechanisms"
5230 if dem_out_path
is not None:
5232 saved_dem_path = dem_out_path
5234 print(f
"[dist_m4ri] Saved DEM to '{dem_out_path}'")
5235 elif dem
is not None and out_dem
is not None and out_dem
is not False:
5236 dem_src = str(dem)
if isinstance(dem, (str, Path))
else "stim.Circuit"
5238 if dem_out_path
is not None:
5239 if isinstance(dem, (str, Path))
and os.path.exists(str(dem)):
5240 if os.path.abspath(str(dem)) != os.path.abspath(dem_out_path):
5241 shutil.copyfile(str(dem), dem_out_path)
5244 saved_dem_path = dem_out_path
5246 print(f
"[dist_m4ri] Saved DEM to '{dem_out_path}'")
5249 raise ValueError(
"Either 'dem' or 'circuit' must be provided.")
5250 return dem, circuit, saved_dem_path
5254 dem: Optional[Any] =
None,
5255 circuit: Optional[Any] =
None,
5256 dist_m4ri: Optional[str] =
None,
5258 threads: Optional[int] =
None,
5259 timeout: float = 60.0,
5260 num_steps: Optional[int] =
None,
5269 smax: Optional[int] =
None,
5270 start: Optional[Union[int, str, Sequence[int]]] =
None,
5271 cbeg: Optional[int] =
None,
5272 cend: Optional[int] =
None,
5278 expand: bool =
False,
5279 drop: Optional[Union[bool, int, str, Path]] =
None,
5280 outD: Optional[Union[str, Path]] =
None,
5281 out_reduced: Optional[Union[bool, str, Path]] =
None,
5282 finC: Optional[str] =
None,
5283 outC: Optional[str] =
None,
5284 do_cws: bool =
False,
5285 cache_file: Optional[Union[str, Path]] =
None,
5286 solver: str =
"dist_m4ri",
5287 codedistance_method: str =
"UndetectableErrorStim",
5288 codedistance_params: Optional[Dict[str, Any]] =
None,
5291 verbose: bool =
False,
5292 nothrottle: bool =
False,
5293 chunk_size: int = 0,
5299 min_hits: Optional[int] =
None,
5301 refresh: Optional[int] =
None,
5302 simple: Optional[bool] =
None,
5303 full: Optional[bool] =
None,
5304 basis: Optional[str] =
None,
5305 rounds: Optional[int] =
None,
5306 out_dir: Optional[Union[str, Path]] =
None,
5307 out_dem: Optional[Union[bool, str, Path]] =
None,
5308 out_stim: Optional[Union[bool, str, Path]] =
None,
5309 p_noise: float = 0.001,
5310 force_noise: bool =
False,
5311 trust_start: bool =
False,
5312 start_scan: bool =
False,
5313 sort_rows: Optional[bool] =
None,
5314 cc_prune: Optional[int] =
None,
5315 engine: Optional[str] =
None,
5317) -> Tuple[Any, ...]:
5319 Computes minimum graph/hypergraph distance of a Stim Detector Error Model (DEM).
5322 dem: stim.DetectorErrorModel object or path to .dem / .stim file.
5323 circuit: stim.Circuit object or path to .stim file (converted to DEM; if stim cannot write an error channel with
5324 disjoint cases, e.g., a PAULI_CHANNEL_1 without Y errors, as independent error mechanisms, with
5325 approximate_disjoint_errors=True, which only changes the probabilities of the error mechanisms).
5326 dist_m4ri: Path to dist_m4ri executable (optional).
5327 method: Solver method (1=RW, 2=CC, 3=Bracketing default).
5328 threads: Maximum number of worker threads (default: all logical CPUs available; subject to throttling).
5329 timeout: Execution timeout in seconds (default: 60.0; 0: no limit).
5330 nothrottle: If True, the automatic thread throttling of the binary is disabled (default: False).
5331 chunk_size: Maximum RW batch claimed at once by a thread (default: 0: adaptive, 25-500 steps).
5332 num_steps: Maximum RW steps.
5333 min_hits: RW stops once the average number of hits per codeword reaches min_hits, both for the codewords of
5334 the minimum weight found and for a representative set of cov_cws lowest-weight codewords (see
5335 run_dist_m4ri()); passed if not None and >= 0 (None, the default: the binary's 5; 0: off, and then the
5336 repeated hits are not counted, unless codewords are collected with do_cws, outC, or maxC).
5337 cov_cws: Size of the representative set of lowest-weight codewords for min_hits (default: 100; 0: only the
5339 kwin / win_mode: A window of kwin columns in every RW step (alias: win; win_mode=0, the default: a Tanner
5340 graph ball, 1: an index window); kwin=0 (default): the automatic hybrid windows for n >= 500, see
5341 win_hyb and run_dist_m4ri(); kwin=-1: no windows (uniform random permutations in every step).
5342 refresh: RW steps between the basis refreshes of the experimental ksub RW (passed if not None; None,
5343 the default: the binary's 5000 with ksub > 0; 0: no refresh).
5344 d_exp: Expected distance estimate.
5345 d_min / dmin: Known lower bound on distance, inclusive.
5346 d_max / dmax: Known upper bound on distance, inclusive.
5347 dstop: Stop target for the lower bound (see compute_classical_distance(); default: 0 = off).
5348 wmin: Minimum distance of interest (terminate early if cw of weight <= wmin is found in RW or CC, default: 1).
5349 wmax: Maximum weight to search in CC.
5350 smax: Maximum syndrome weight for CC confinement profile.
5351 start: Expert option: list of CC start columns (int, comma-separated string "a,b,c", or
5352 sequence of ints; None or -1: all columns). CC clusters are grown only from the listed
5353 columns, and they are not limited to larger column indices. WARNING: the lower bound
5354 (and an exact result) is valid only if a code symmetry maps every minimum-weight codeword
5355 to one containing a listed column (e.g., one column per block of a quasi-cyclic code);
5356 the upper bound is always valid. Results are cached under the separate key
5357 '<key>:start=a,b,c'; only dmax and codewords update the main cache record.
5358 trust_start: Expert option: accept the start-list results as valid, i.e., also merge dmin and
5359 rw_steps into the main cache record, and copy an existing exact start-list record to the
5360 main record even if no calculation is needed (default: False).
5361 start_scan: Expert option, with start (default: False): a cluster grown from a listed column c only
5362 uses the columns > c (see run_dist_m4ri()), e.g., start=[0, l] for the two blocks of a two-block
5363 group-algebra code over an abelian group; cached under the key suffix ':start=a,b,c:scan' (not for a
5364 DEM: ignored with a warning).
5365 cbeg / cend / noscan: Disabled in the Python interface: accepted for backward compatibility,
5366 but ignored with a warning to stderr (use the dist_m4ri binary directly).
5367 sort_rows: Row order of H in the binary (see run_dist_m4ri()): True sorts the rows by increasing weight
5368 (faster CC for very different row weights with cc_prune=0), False keeps their order, and None (default)
5369 leaves the binary's default (sorted for matrices; the detectors of a DEM are kept in time order). The
5370 result and the cache key do not depend on it.
5371 cc_prune: The CC search of the binary (see run_dist_m4ri()): 0, the walk of the versions before 0.15.0; 1,
5372 the branch and bound (each codeword found once); 2, also the coverage and fractional cover bounds; None
5373 (default): the binary's default 1. The result and the cache key do not depend on it.
5374 win_hyb: Window size of the automatic hybrid RW steps (kwin=0, n >= 500: a window on every other step; see
5375 run_dist_m4ri()); 0 (default): 256 columns for a DEM, 512 for matrices. The cache key does not depend on
5377 pairs: The RW pairs of non-pivot columns (p=2, see run_dist_m4ri()): the number of random pivot rows of
5378 their buckets (0: off; -1, the default: auto). The cache key does not depend on it.
5379 ksub: Experimental, should not be used (a warning is printed to stderr for ksub > 0 with method=1 or 3):
5380 subspace dimension sampled from ker(H) in RW (default: 0, full-matrix RW).
5381 dW: Extra weight window above dmin to collect codewords (only irreducible ones: not the union of a
5382 codeword and a disjoint nonzero vector with zero syndrome; the binary's default irred=1).
5383 maxC: Maximum number of codewords to collect (with expand=True, also the maximum number of codewords of the
5384 expanded list returned and written to outC; a warning is printed to stderr if some are dropped).
5385 pmin: Probability cutoff for error mechanisms in DEM.
5386 Note: CC start columns (start) and the codewords (finC, outC, do_cws) index the error mechanisms in
5387 their order of appearance in the (flattened) DEM, after the pmin cutoff: with another pmin, the columns
5388 (and the codewords) change.
5389 merge: Merge the equal columns of the DEM (error mechanisms with the same detectors and observables) in the
5390 binary (default: True). Exact: dropping two equal columns of a codeword gives a lighter one, so the
5391 distance does not change (and the cache key does not depend on merge). Column indices (finC, outC,
5392 start, the returned codewords) are those of the DEM; the codewords found use the first one of each set
5393 of equal columns (merge=False: all columns are kept, e.g., to enumerate the codewords with each of them;
5394 or expand=True to expand the list found).
5395 expand: If True, the codewords returned and written to outC are expanded in Python: each codeword is listed
5396 once for every choice among equal columns (default: False; see _expand_equal_columns()). The binary
5397 and the cache keep the merged list. With dW <= 1, the expanded list is that of merge=False (with
5398 dW >= 2, merge=False also lists lighter codewords with two equal columns added). The expanded list can
5399 be much longer (it grows quickly with the weight); with maxC > 0, it is cut at maxC codewords.
5400 drop: The detectors which the binary drops if this is verified to be exact, so that the distance (and every
5401 codeword found) is that of the DEM, found from the DEM without these detectors and the errors touching
5402 them (fewer detectors and errors: as fast as the DEM with only the primary-basis detectors of a CSS
5403 circuit). None, True, 1, or "auto" (default): the binary searches for the largest such set in the DEM alone
5404 (drop=auto, see the README), e.g., the minority-basis detectors of a CSS circuit (with the usual noise).
5405 "css": for a Stim circuit classified as CSS, its minority-basis detectors (see strip_minority_detectors()),
5406 passed as a file (otherwise, e.g., for a DEM, drop=auto is used, with a warning). False or 0: no detectors
5407 are dropped. A file name: the file with the detectors to drop (0-based, k for Dk; see read_drop_file()),
5408 for any DEM. With "css" or a file, if dropping the detectors is not exact, a note is printed to stderr and
5409 the full DEM is used. The codewords found use only the errors touching no dropped detector.
5410 outD: File to which the binary writes the detectors dropped (0-based, see read_drop_file()); with a cached
5411 result, the binary is run only to find them (with dmin = dmax: no search).
5412 out_reduced: True or a prefix PREFIX: writes the DEM without the detectors dropped (see outD) to
5413 PREFIX_red.dem, and the maps to the original DEM (its detectors and errors) to PREFIX_red.json, see
5414 write_reduced_dem() (True: PREFIX = <basename>, in out_dir or the directory of the input file).
5415 finC: Input file with initial codewords.
5416 outC: Output file to save codewords (NZLIST format).
5417 do_cws: Whether to return extracted error mechanisms / codewords.
5418 cache_file: Optional JSON file path for persistent distance caching.
5419 solver: "dist_m4ri" or "codedistance".
5420 codedistance_method: Method if using codedistance library.
5421 codedistance_params: Extra parameters for codedistance library.
5423 debug: Debug bitmap: the bits in BIN_DBG_MASK for the dist_m4ri binary, and the PY_DBG_* bits.
5424 verbose: Verbose reporting flag.
5425 simple / full: Disabled since version 0.13.0 (accepted for backward compatibility, but ignored with a warning
5426 to stderr): the DEM of a Stim circuit always has all detectors, and the minority-basis detectors of a CSS
5427 circuit (which simple=True stripped) are dropped by the binary if this is exact (see drop). The distance
5428 of the simplified circuit (a lower bound) can still be found by passing the circuit stripped with
5429 strip_minority_detectors().
5430 basis: Optional primary memory basis override ('X' or 'Z') for .stim circuits, only used with drop="css" (the
5431 other basis is that of the minority-basis detectors) and for the verbose report of the classification.
5432 rounds: Optional number of REPEAT rounds for .stim circuits (warns if no REPEAT block).
5433 out_dir: Optional output directory for saved DEM and/or Stim circuit files.
5434 out_dem: Optional bool or filename to save the constructed DEM (True = auto-named <basename>_full.dem). The
5435 codewords (outC, do_cws) index its error lines (in the order of the flattened DEM): save the DEM in the
5436 same run, since a DEM generated again from the circuit may differ (e.g., with another stim version); with
5437 outC but without out_dem, a warning is printed to stderr for a Stim circuit.
5438 out_stim: Optional bool or filename to save the processed (noisy, rounds-adjusted) Stim circuit
5439 (True = auto-named <basename>_full.stim).
5440 p_noise: Strength of the uniform circuit-level noise added to a noiseless Stim circuit (see add_noise();
5441 default: 0.001; 0: no noise is added, and a noiseless circuit gives no distance, with a note). The
5442 distance does not depend on it (unless pmin > 0), only on which faults are possible. A circuit with noise
5443 (a noise channel or a noisy measurement such as M(0.001), see has_noise()) is used as is, unless
5445 force_noise: If True, the noise of a Stim circuit is replaced by the uniform noise of strength p_noise (see
5446 strip_noise(); with p_noise=0, all noise is removed, e.g., to save the noiseless circuit with out_stim),
5447 e.g., to compare circuits with the same noise model (default: False).
5450 tuple (dist, d_info, cws) if do_cws else (dist, d_info)
5453 TypeError: for an unknown keyword argument (e.g., a misspelled option).
5454 ValueError: if neither dem nor circuit is given, for rounds without a Stim circuit (or rounds < 0), for
5455 p_noise outside [0, 0.5], for an invalid drop (e.g., a file which does not exist) or another invalid
5456 option value (e.g., start, or cc_prune other than 0, 1, or 2), for method=2 with timeout <= 0 but
5457 without wmax, dmax, or dstop, or for do_cws / outC with solver="codedistance".
5458 FileNotFoundError: if the dist_m4ri binary is not found (see find_dist_m4ri_binary()).
5459 RuntimeError: if the binary exits with an error.
5460 ImportError: if stim is needed (e.g., for a Stim circuit) or solver="codedistance", but the package is not
5463 global _last_run_stats
5464 eff_dmin = dmin
if dmin > 0
else d_min
5465 eff_dmax = dmax
if dmax > 0
else d_max
5469 _warn_ignored(
"start_scan=1 is not available for a DEM (ignored)")
5471 start_list =
_prepare_start_option(start, trust_start, noscan, cbeg, cend, solver, start_scan=start_scan)
5475 circ_verbose = verbose
or bool(debug & PY_DBG_CIRCUITS)
5476 if not 0.0 <= p_noise <= 0.5:
5477 raise ValueError(f
"invalid p_noise={p_noise}: the noise strength must be in [0, 0.5]")
5479 src_in = dem
if dem
is not None else circuit
5480 src_name = str(src_in)
if isinstance(src_in, (str, Path))
else f
"stim.{type(src_in).__name__}"
5481 is_codedist = solver ==
"codedistance" or str(engine
or "").strip().lower() ==
"codedistance"
5482 css_list: List[int] = []
5484 dem, circuit, basis=basis, rounds=rounds, p_noise=p_noise, out_dir=out_dir, out_dem=out_dem,
5485 out_stim=out_stim, circ_verbose=circ_verbose,
5486 drop_out=css_list
if (drop_mode ==
"css" and not is_codedist)
else None, force_noise=force_noise
5488 if getattr(dem,
"num_errors",
None) == 0:
5489 sys.stderr.write(
"# Note: the DEM has no error mechanisms (no noise, e.g., p_noise=0 for a noiseless circuit "
5490 "or with force_noise): no distance computed\n")
5491 return (0, [0, 0, 0], [])
if do_cws
else (0, [0, 0, 0])
5495 raise ValueError(
"Codeword extraction is not supported with codedistance solver; use solver='dist_m4ri'.")
5496 if outD
or out_reduced:
5497 _warn_ignored(
"outD and out_reduced are only used with solver='dist_m4ri'; ignored")
5500 params = dict(codedistance_params
or {})
5501 params.setdefault(
"filterCircuit",
False)
5502 if num_steps
is not None and "iterCount" not in params:
5503 params[
"iterCount"] = num_steps
5505 if circuit
is not None:
5506 res = codedistance.circuitDistance(
5507 circuit, method=codedistance_method, params=params,
5508 seed=seed
if seed != 0
else None
5511 H, L, priors = codedistance.StimDEM2HL(dem)
5512 if "priors" not in params
and len(priors) > 0:
5513 params[
"priors"] = priors
5514 res = codedistance.codeDistance(
5515 H, L, tB=1, method=codedistance_method, params=params,
5516 seed=seed
if seed != 0
else None
5518 d = res.get(
"d", -1)
5523 if outC
and circuit
is not None and saved_dem_path
is None:
5524 sys.stderr.write(f
"# Warning: outC={outC}: the codewords index the error lines of the DEM generated from the "
5525 f
"Stim circuit in this run (after the pmin cutoff), which is not saved; save it with "
5526 f
"--out-dem (out_dem=...), since a DEM generated again later (e.g., with another stim "
5527 f
"version) may not fit\n")
5528 fdem_run = saved_dem_path
if (saved_dem_path
is not None and os.path.exists(saved_dem_path))
else dem
5529 temp_files: List[str] = []
5532 if drop_mode ==
"css":
5535 temp_files.append(fdrop)
5536 with open(fdrop,
"w")
as f:
5537 f.write(f
"# {len(css_list)} minority-basis detectors (drop=css)\n" +
5538 " ".join(str(r)
for r
in css_list) +
"\n")
5540 print(f
"[dist_m4ri] drop=css: the binary drops the {len(css_list)} minority-basis detectors if "
5544 _warn_ignored(
"drop=css: no minority-basis detectors (they are only known for a Stim circuit "
5545 "classified as CSS); using drop=auto (the detectors to drop found from the DEM)")
5546 outd_file = str(outD)
if outD
else None
5547 if out_reduced
and outd_file
is None:
5549 temp_files.append(outd_file)
5550 if outd_file
is not None and not isinstance(fdem_run, (str, Path)):
5552 temp_files.append(fdem_file)
5554 fdem_run = fdem_file
5556 if expand
and (do_cws
or outC):
5557 def cws_out_fn(cws_in: List[List[int]]) -> List[List[int]]:
5559 eff_cache_file = str(Path(cache_file).resolve())
if cache_file
is not None else _distance_cache_file
5561 cert_mode, method=method, do_cws=do_cws, outC=outC, finC=finC, smax=smax,
5562 dW=dW, maxC=maxC, start_list=start_list, outD=outD, out_reduced=out_reduced, pmin=pmin
5565 mode_arg =
"ub" if method == 1
else "auto"
5566 t_lim = float(timeout)
if timeout
and timeout > 0
else 0.0
5567 n_thr = threads
or (os.cpu_count()
or 8)
5568 r_seed = seed
if seed != 0
else 1
5571 lambda w_lo, w_hi: (
5572 qec_pipeline.certify_circuit(
5573 circuit, wmin=w_lo, wmax=w_hi, threads=n_thr, seed=r_seed, time_limit=t_lim, mode=mode_arg
5575 if circuit
is not None else
5576 qec_pipeline.certify_dem(
5577 fdem_run, wmin=w_lo, wmax=w_hi, threads=n_thr, seed=r_seed, time_limit=t_lim, mode=mode_arg
5580 label=
"DEM", dmin=eff_dmin, dmax=eff_dmax, dstop=dstop, wmax=wmax,
5581 eff_cache_file=eff_cache_file, debug=debug, verbose=verbose,
5583 dist, d_info, cws = res[
"dist"], res[
"d_info"], res[
"cws"]
5584 return (dist, d_info, cws)
if do_cws
else (dist, d_info)
5586 None,
None,
None,
False,
"DEM", dist_m4ri=dist_m4ri, method=method, threads=threads, timeout=timeout,
5587 num_steps=num_steps, d_exp=d_exp, dmin=eff_dmin, dmax=eff_dmax, dstop=dstop, wmin=wmin, wmax=wmax,
5588 smax=smax
if smax
is not None else 0, start_list=start_list, dW=dW, maxC=maxC, finC=finC, outC=outC,
5589 need_cws=bool(do_cws
or outC), eff_cache_file=eff_cache_file, seed=seed, debug=debug, verbose=verbose,
5590 nothrottle=nothrottle, chunk_size=chunk_size, ksub=ksub,
5591 kwin=kwin
if kwin != 0
else int(kwargs.get(
"win", 0)
or 0), win_mode=win_mode, min_hits=min_hits,
5592 cov_cws=cov_cws, refresh=refresh, trust_start=trust_start, fdem=fdem_run, pmin=pmin,
5593 merge=merge, key_fn=
lambda:
_dem_cache_key(dem, pmin), cws_out_fn=cws_out_fn, fdrop=fdrop, outD=outd_file,
5594 sort_rows=sort_rows, win_hyb=win_hyb, pairs=pairs, cc_prune=cc_prune
5596 ran =
not res[
"cached"]
5597 drop_stats = _last_run_stats
if ran
else {}
5598 if outd_file
is not None and res[
"cached"]:
5600 print(
"[dist_m4ri] Cached result: running the binary only for the detectors dropped (outD)")
5601 cached_stats = _last_run_stats
5602 run_dist_m4ri(dist_m4ri_path=dist_m4ri, method=2, fdem=str(fdem_run), dmin=1, dmax=1, wmax=1, threads=1,
5603 pmin=pmin, merge=merge, drop=fdrop, outD=outd_file, debug=debug & ~BIN_DBG_MASK)
5604 drop_stats, _last_run_stats = _last_run_stats, cached_stats
5606 if ran
and fdrop
not in (
"auto",
"0")
and drop_stats.get(
"drop_note"):
5607 what =
"the minority-basis detectors (drop=css)" if drop_mode ==
"css" else f
"the detectors in {fdrop}"
5608 sys.stderr.write(f
"# Note: dropping {what} is not exact for this DEM ({drop_stats['drop_note']}); "
5609 f
"the full DEM was used\n")
5610 elif ran
and fdrop
not in (
"auto",
"0")
and drop_stats.get(
"drop_none_listed"):
5611 sys.stderr.write(f
"# Note: none of the detectors in {fdrop} is in the DEM; nothing dropped\n")
5612 if drop_mode ==
"css" and fdrop !=
"auto" and outd_file
is not None and os.path.isfile(outd_file):
5613 with open(outd_file)
as f:
5615 with open(outd_file,
"w")
as f:
5616 f.write(text.replace(f
"(drop={fdrop}",
"(drop=css", 1))
5617 if out_reduced
and outd_file
is not None:
5620 drop_label =
"css" if (drop_mode ==
"css" and fdrop !=
"auto")
else str(fdrop)
5622 fdem_run, dropped, prefix, pmin=pmin, num_detectors=num_det,
5623 meta={
"source": src_name,
"dem": saved_dem_path
if saved_dem_path
else
5624 (str(fdem_run)
if str(fdem_run)
not in temp_files
else None),
"drop": drop_label})
5625 if verbose
or (debug & PY_DBG_CIRCUITS):
5626 print(f
"[dist_m4ri] Saved the reduced DEM ({len(dropped)} detectors dropped) to '{dem_red}' and the "
5627 f
"maps to '{json_red}'")
5630 dist, d_info, cws = res[
"dist"], res[
"d_info"], res[
"cws"]
5631 return (dist, d_info, cws)
if do_cws
else (dist, d_info)
5636 The groups of the detectors of a Stim circuit for its code-sized model (see compute_model_lower_bound()): two
5637 detectors are in the same group if they share a measurement record other than a final data measurement. The final
5638 data measurements are the measurements in the final measurement block (after the last detector which precedes the
5639 last measurement) of qubits not measured before it, or all measurements of the final block if there are none
5640 (e.g., if every qubit is measured in each round). In a memory circuit, the groups are the chains of the detectors
5641 of the checks, linked by the measurements of their ancillas in consecutive rounds; the chain of a check of the
5642 measured basis ends with its detector of the final data measurements.
5645 (group, final, num_final): the group of each detector (numbered in the order of their first detectors), for
5646 each group whether one of its detectors has a final data measurement, and the number of final data
5650 det_recs: List[List[int]] = []
5651 det_pos: List[int] = []
5652 meas_pos: List[int] = []
5653 meas_qubits: List[frozenset] = []
5655 for pos, inst
in enumerate(circuit.flattened()):
5656 if inst.name ==
"DETECTOR":
5658 det_recs.append([n + t.value
for t
in inst.targets_copy()
if t.is_measurement_record_target])
5661 gd = stim.gate_data(inst.name)
5662 if gd.produces_measurements:
5663 for grp
in inst.target_groups():
5664 meas_pos.append(pos)
5665 meas_qubits.append(frozenset()
if inst.name ==
"MPAD" else
5666 frozenset(t.qubit_value
for t
in grp
if t.qubit_value
is not None))
5669 last_meas = meas_pos[-1]
if meas_pos
else -1
5670 last_det = max((p
for p
in det_pos
if p < last_meas), default=-1)
5671 cutoff = max(last_det, last_gate_pos)
5672 before: Set[int] = set()
5673 for p, qs
in zip(meas_pos, meas_qubits):
5676 final_data = {r
for r, (p, qs)
in enumerate(zip(meas_pos, meas_qubits))
if p > cutoff
and qs
and not qs & before}
5678 final_data = {r
for r, p
in enumerate(meas_pos)
if p > cutoff}
5679 parent = list(range(len(det_recs)))
5681 def find(a: int) -> int:
5682 while parent[a] != a:
5683 parent[a] = parent[parent[a]]
5687 first_det: Dict[int, int] = {}
5688 for d, recs
in enumerate(det_recs):
5690 if r
not in final_data:
5691 a, b = find(d), find(first_det.setdefault(r, d))
5693 parent[max(a, b)] = min(a, b)
5694 ids: Dict[int, int] = {}
5695 group = [ids.setdefault(find(d), len(ids))
for d
in range(len(det_recs))]
5696 final = [
False] * len(ids)
5697 for d, recs
in enumerate(det_recs):
5698 if any(r
in final_data
for r
in recs):
5699 final[group[d]] =
True
5700 return group, final, len(final_data)
5705 Pure-DEM detector chains for compute_model_lower_bound() when no Stim circuit is available (or when the circuit
5706 groups are trivial). Uses detector coordinates (x, y, ..., t) when present in the DEM to group detectors with the
5707 same spatial coordinates across rounds, and falls back to linking consecutive-round detectors via dominant weight-2
5708 observable-free (ancilla measurement) error mechanisms when coordinates are absent.
5711 (group, final, num_final, num_detectors, num_observables).
5713 if isinstance(dem, (str, Path)):
5714 with open(str(dem))
as f:
5715 lines = f.read().splitlines()
5717 lines = str(dem.flattened()
if hasattr(dem,
"flattened")
else dem).splitlines()
5719 det_coords: Dict[int, Tuple[float, ...]] = {}
5723 def parse_block(i: int, det_shift: int, c_shift: Tuple[float, ...]) -> Tuple[int, int, Tuple[float, ...]]:
5724 nonlocal max_det, max_obs
5725 while i < len(lines):
5726 s = lines[i].split(
"#", 1)[0].strip()
5730 if s.startswith(
"}"):
5731 return i, det_shift, c_shift
5732 if s.startswith(
"repeat"):
5733 count = int(s.split()[1])
5735 for _
in range(count):
5736 end, det_shift, c_shift = parse_block(body, det_shift, c_shift)
5739 while end < len(lines)
and depth > 0:
5740 t = lines[end].split(
"#", 1)[0].strip()
5742 depth += t.startswith(
"repeat") - t.startswith(
"}")
5744 elif s.startswith(
"shift_detectors"):
5745 if "(" in s
and ")" in s:
5746 vals = [float(x)
for x
in s[s.index(
"(") + 1:s.index(
")")].split(
",")
if x.strip()]
5747 n_c = max(len(c_shift), len(vals))
5749 (c_shift[k]
if k < len(c_shift)
else 0.0) + (vals[k]
if k < len(vals)
else 0.0)
5752 rest = s[s.index(
")") + 1:].strip()
5754 rest = s.split(
None, 1)[1].strip()
if len(s.split()) > 1
else "0"
5756 det_shift += int(rest.rsplit(
None, 1)[-1])
5757 elif s.startswith(
"detector"):
5758 coords: Tuple[float, ...] = ()
5759 if "(" in s
and ")" in s:
5760 vals = [float(x)
for x
in s[s.index(
"(") + 1:s.index(
")")].split(
",")
if x.strip()]
5761 coords = tuple(vals[k] + (c_shift[k]
if k < len(c_shift)
else 0.0)
for k
in range(len(vals)))
5762 rest = s[s.index(
")") + 1:]
5764 rest = s[len(
"detector"):]
5765 for tok
in rest.split():
5766 if tok.startswith(
"D"):
5767 d = int(tok[1:]) + det_shift
5768 max_det = max(max_det, d)
5770 det_coords[d] = coords
5771 elif s.startswith(
"logical_observable"):
5772 for tok
in s.split()[1:]:
5773 if tok.startswith(
"L"):
5774 max_obs = max(max_obs, int(tok[1:]))
5775 elif s.startswith(
"error"):
5776 p_text = s[s.index(
"(") + 1:s.index(
")")].strip()
5777 if float(p_text) < pmin:
5779 for tok
in s[s.index(
")") + 1:].split():
5780 if tok.startswith(
"D"):
5781 max_det = max(max_det, int(tok[1:]) + det_shift)
5782 elif tok.startswith(
"L"):
5783 max_obs = max(max_obs, int(tok[1:]))
5784 return i, det_shift, c_shift
5786 parse_block(0, 0, ())
5787 num_det = int(getattr(dem,
"num_detectors", max_det + 1)
or (max_det + 1))
5788 num_obs = int(getattr(dem,
"num_observables", max_obs + 1)
or (max_obs + 1))
5790 return [], [], 0, 0, max(0, num_obs)
5792 if len(det_coords) == num_det
and all(len(c) >= 2
for c
in det_coords.values()):
5793 spatial_ids: Dict[Tuple[float, ...], int] = {}
5794 group = [spatial_ids.setdefault(det_coords[d][:-1], len(spatial_ids))
for d
in range(num_det)]
5795 if 1 <= len(spatial_ids) < num_det:
5796 max_t = max(c[-1]
for c
in det_coords.values())
5797 final = [
False] * len(spatial_ids)
5798 for d
in range(num_det):
5799 if det_coords[d][-1] == max_t:
5800 final[group[d]] =
True
5802 counts = [0] * len(spatial_ids)
5805 max_c = max(counts, default=0)
5806 if max_c > 1
and 0 < sum(c == max_c
for c
in counts) < len(spatial_ids):
5807 final = [c == max_c
for c
in counts]
5808 return group, final, sum(final), num_det, max(0, num_obs)
5811 pair_prob: Dict[Tuple[int, int], float] = {}
5812 best_inc = [0.0] * num_det
5813 for dets, obs, p_text
in cols_prob:
5814 if len(dets) == 2
and not obs:
5816 if 0 <= u < v < num_det:
5818 if p > pair_prob.get((u, v), -1.0):
5819 pair_prob[(u, v)] = p
5825 parent = list(range(num_det))
5827 def find(a: int) -> int:
5828 while parent[a] != a:
5829 parent[a] = parent[parent[a]]
5833 has_next = [
False] * num_det
5834 has_prev = [
False] * num_det
5836 (p, u, v)
for (u, v), p
in pair_prob.items()
5837 if p >= 0.5 * max(best_inc[u], best_inc[v])
5839 cand_edges.sort(key=
lambda item: (-item[0], item[1], item[2]))
5840 for _, u, v
in cand_edges:
5841 if not has_next[u]
and not has_prev[v]:
5842 ru, rv = find(u), find(v)
5844 parent[max(ru, rv)] = min(ru, rv)
5848 ids: Dict[int, int] = {}
5849 group = [ids.setdefault(find(d), len(ids))
for d
in range(num_det)]
5850 counts = [0] * len(ids)
5853 max_len = max(counts, default=0)
5854 if max_len > 1
and 0 < sum(c == max_len
for c
in counts) < len(ids):
5855 final = [c == max_len
for c
in counts]
5857 final = [
True] * len(ids)
5858 return group, final, sum(final), num_det, max(0, num_obs)
5862 cols: List[Tuple[Any, ...]], group: List[int], use: List[bool], num_observables: int
5865 The matrices of a code-sized model (see compute_model_lower_bound()): one row per group of detectors with use[g]
5866 (the sum of the rows of its detectors), and one column per distinct pair (model column, observables) of the error
5867 mechanisms `cols` (see _dem_error_columns()); the errors with no effect on the rows nor on the observables are
5871 dict with "H" and "L" (scipy.sparse.csr_matrix), "col_map" (the model column of each error mechanism, -1 if
5872 it is dropped), "groups" (the group of each row), and "trivial" (the error mechanisms which flip an observable
5873 but no row: then the model has a codeword of weight 1).
5876 import scipy.sparse
as sp
5877 index: Dict[Tuple[frozenset, frozenset], int] = {}
5878 col_map: List[int] = []
5879 trivial: List[int] = []
5880 for j, (dets, obs)
in enumerate(cols):
5881 rows: Set[int] = set()
5890 col_map.append(index.setdefault((frozenset(rows), obs), len(index)))
5891 used = sorted({g
for rows, _
in index
for g
in rows})
5892 row_of = {g: i
for i, g
in enumerate(used)}
5897 for c, (rows, obs)
in enumerate(index):
5898 hi.extend(row_of[g]
for g
in rows)
5899 hj.extend([c] * len(rows))
5901 lj.extend([c] * len(obs))
5902 H = sp.csr_matrix((np.ones(len(hi), dtype=np.int8), (hi, hj)), shape=(len(used), len(index)))
5903 L = sp.csr_matrix((np.ones(len(li), dtype=np.int8), (li, lj)), shape=(num_observables, len(index)))
5904 return {
"H": H,
"L": L,
"col_map": col_map,
"groups": used,
"trivial": trivial}
5908 circuit: Any =
None,
5909 dist_m4ri: Optional[str] =
None,
5911 threads: Optional[int] =
None,
5912 timeout: float = 60.0,
5913 num_steps: Optional[int] =
None,
5916 minority: bool =
False,
5918 rounds: Optional[int] =
None,
5919 p_noise: float = 0.001,
5920 force_noise: bool =
False,
5921 out_model: Optional[Union[str, Path]] =
None,
5922 cache_file: Optional[Union[str, Path]] =
None,
5925 verbose: bool =
False,
5926 nothrottle: bool =
False,
5928 sort_rows: Optional[bool] =
None,
5929 cc_prune: Optional[int] =
None,
5930 return_info: bool =
False,
5931 dem: Optional[Any] =
None,
5935 Certified lower bound on the circuit-level distance of a Stim memory circuit from its code-sized model.
5937 The model is a row reduction of the DEM (H, L) of the circuit (processed as in compute_dem_distance(), with the
5938 options rounds, p_noise, force_noise, and pmin): two detectors are linked if they share a measurement record other
5939 than a final data measurement, and each group of linked detectors gives one row, the sum of the rows of its
5940 detectors (see _model_detector_groups()). In a memory circuit, the groups are the chains of the detectors of the
5941 checks over the rounds: the chain of a check h of the measured basis telescopes to the parity of the final data
5942 measurements on its support, so that the column of an error mechanism is the syndrome of its net data error (a
5943 data error or a hook error), and the chains of the minority-basis checks (with minority=True) add the net change
5944 of their values between their first and last measurements. Equal columns are merged, and the errors with no effect
5945 on the rows nor on the observables (e.g., ancilla measurement errors) are dropped; the model has a few columns per
5946 data qubit. Since every row is a sum of detector rows, every codeword x of the DEM (Hx = 0, Lx != 0) gives a model
5947 codeword of weight at most |x|: the distance of the model, as certified by CC (its dmin), is a lower bound on the
5948 distance of the DEM. It ignores the timing within the rounds, so it is not tight in general (e.g., 5 for the
5949 bb_72 circuit of distance 6). Any circuit gives a valid bound, but a useful one needs this structure; if an error
5950 flips an observable but no row, the bound is 1 (with a note to stderr).
5952 The binary runs on the model with the rows of the measured-basis chains first, and then (with minority=True) on
5953 the model with all rows, starting from that bound (the extra rows can only raise it): RW first, which ends as soon
5954 as it finds a model codeword of that weight (then the bound is not raised), and only otherwise with `method` (CC
5955 from dmin). The minority-basis rows are off by default: with depolarizing-type noise, every net data error which
5956 flips the measured-basis observable also occurs without a component of the other basis, so that a lightest model
5957 codeword has a version which the minority-basis rows do not detect (they never raised the bound of the benchmark
5958 circuits or of the surface-code circuits of stim, and cost up to 9 s for the mitten_270 circuits); they may raise
5959 it for other noise models (e.g., biased noise). The certified bound is stored as dmin in the cache record of the
5960 DEM (the same key as compute_dem_distance(); "dmin_model": the bound), so that a later compute_dem_distance()
5961 starts CC from it; the distances of the models are cached under their own keys.
5964 circuit: Stim memory circuit (stim.Circuit or .stim file).
5965 dist_m4ri: Path to dist_m4ri executable (optional).
5966 method: Solver method for the model (1=RW, 2=CC, 3=Bracketing default); only CC certifies a bound.
5967 threads / timeout / num_steps / seed / nothrottle / pairs: As for compute_dem_distance(), for each run of the
5968 binary (at most three).
5969 sort_rows / cc_prune: The row order and the CC search of the binary (see run_dist_m4ri(); None, the default:
5970 the binary's defaults, i.e., the rows of the model sorted by weight, and the branch and bound), for each
5971 run. The bound and the cache keys do not depend on them.
5972 dstop: Stop target (see compute_dem_distance()): the runs end once CC has certified dmin >= dstop.
5973 wmax: Maximum weight of the CC rounds on the model (0: until the bounds meet or the timeout).
5974 minority: If True, the groups without a final data measurement (e.g., the chains of the minority-basis
5975 checks of a CSS circuit) are rows of the model as well (default: False; CLI: --model-minority).
5976 pmin: Probability cutoff for the error mechanisms of the DEM.
5977 rounds / p_noise / force_noise: The processing of the circuit, as in compute_dem_distance().
5978 out_model: If given, the model (with all rows used) is written to PREFIX_HR.mtx and PREFIX_LR.mtx.
5979 cache_file: Optional JSON file path for persistent distance caching.
5980 debug / verbose: As for compute_dem_distance().
5981 return_info: If True, return (bound, info).
5984 The certified lower bound, or (bound, info) with return_info=True, where info has "d_info" (the bounds
5985 [dmin, dmax, rw_steps] of the circuit's DEM from the cache record, with dmin including the bound; [bound, 0, 0]
5986 without the cache), "model_d_info" and "measured_d_info" (the bounds of the model and of its measured-basis
5987 part, [] if not run), the sizes "rows", "cols", "rows_measured", "cols_measured", "groups", "groups_final",
5988 "detectors", "final_data", "dem_cols", "trivial" (the error mechanisms which flip an observable but no row),
5989 the model "H", "L", and "col_map" (the model column of each error mechanism of the DEM, -1 if dropped), and
5990 "files" (the files written with out_model).
5993 TypeError: for an unknown keyword argument (e.g., a misspelled option).
5994 ValueError: if the input is not a Stim circuit.
5996 global _distance_cache
5997 _check_kwargs(compute_model_lower_bound, kwargs, aliases=())
5998 src = dem
if dem
is not None else circuit
6000 raise ValueError(
"compute_model_lower_bound: a Stim circuit (.stim) or DEM (.dem) is required")
6001 is_dem_input = dem
is not None or (
6002 isinstance(src, (str, Path))
and str(src).endswith(
".dem")
6003 )
or (
not isinstance(src, (str, Path))
and not hasattr(src,
"detector_error_model"))
6004 if isinstance(src, (str, Path))
and not str(src).endswith((
".stim",
".dem")):
6005 raise ValueError(f
"compute_model_lower_bound: {src} is not a Stim circuit (.stim) or DEM (.dem)")
6006 if not 0.0 <= p_noise <= 0.5:
6007 raise ValueError(f
"invalid p_noise={p_noise}: the noise strength must be in [0, 0.5]")
6008 circ_verbose = verbose
or bool(debug & PY_DBG_CIRCUITS)
6010 src
if is_dem_input
else None,
None if is_dem_input
else src,
6011 rounds=rounds, p_noise=p_noise, circ_verbose=circ_verbose, force_noise=force_noise
6014 info: Dict[str, Any] = {
"d_info": [0, 0, 0],
"model_d_info": [],
"measured_d_info": [],
"files": []}
6015 if getattr(dem,
"num_errors",
None) == 0:
6016 sys.stderr.write(
"# Note: the DEM has no error mechanisms (no noise, e.g., p_noise=0 for a noiseless circuit "
6017 "or with force_noise): no bound computed\n")
6018 return (0, info)
if return_info
else 0
6021 sys.stderr.write(
"# Note: the DEM has no error mechanisms (no noise, e.g., p_noise=0 for a noiseless circuit "
6022 "or with force_noise): no bound computed\n")
6023 return (0, info)
if return_info
else 0
6024 if circ
is not None:
6026 num_det = int(getattr(dem,
"num_detectors", len(group)))
6027 num_obs = int(getattr(dem,
"num_observables", 0))
6028 if len(group) != num_det:
6029 raise ValueError(f
"compute_model_lower_bound: {len(group)} detectors in the circuit, {num_det} in its DEM")
6030 if len(final) == len(group)
and len(group) > 1:
6032 if len(dem_group) == len(group)
and len(dem_final) < len(group):
6033 group, final, num_final = dem_group, dem_final, dem_num_final
6037 full =
_model_matrices(cols, group, [
True] * len(final), num_obs)
if minority
else part
6038 info.update(rows=full[
"H"].shape[0], cols=full[
"H"].shape[1], rows_measured=part[
"H"].shape[0],
6039 cols_measured=part[
"H"].shape[1], groups=len(final), groups_final=sum(final), detectors=len(group),
6040 final_data=num_final, dem_cols=len(cols), trivial=len(full[
"trivial"]), H=full[
"H"], L=full[
"L"],
6041 col_map=full[
"col_map"])
6043 print(f
"[dist_m4ri] Code-sized model: {info['rows']} x {info['cols']} ({info['rows_measured']} rows of the "
6044 f
"chains with final data measurements, {info['rows'] - info['rows_measured']} other), from "
6045 f
"{len(cols)} error mechanisms and {len(group)} detectors in {len(final)} groups ({num_final} final data "
6048 from scipy.io
import mmwrite
6049 for name, M
in ((
"HR", full[
"H"]), (
"LR", full[
"L"])):
6050 path = f
"{out_model}_{name}.mtx"
6051 parent_dir = os.path.dirname(os.path.abspath(path))
6052 os.makedirs(parent_dir, exist_ok=
True)
6053 mmwrite(path, M, symmetry=
"general")
6054 info[
"files"].append(path)
6056 print(f
"[dist_m4ri] Saved the code-sized model to '{info['files'][0]}' and '{info['files'][1]}'")
6057 eff_cache_file = str(Path(cache_file).resolve())
if cache_file
is not None else _distance_cache_file
6059 def run(model: Dict[str, Any], dmin: int, run_method: int, wmin: int = 1) -> List[int]:
6062 model[
"H"],
None, model[
"L"],
False,
"code-sized model", dist_m4ri=dist_m4ri, method=run_method,
6063 threads=threads, timeout=timeout, num_steps=num_steps, d_exp=0, dmin=dmin, dmax=0, dstop=dstop,
6064 wmin=wmin, wmax=wmax, smax=0, start_list=
None, dW=-1, maxC=0, finC=
None, outC=
None, need_cws=
False,
6065 eff_cache_file=eff_cache_file, seed=seed, debug=debug, verbose=verbose, nothrottle=nothrottle,
6066 chunk_size=0, ksub=0, kwin=0, win_mode=0, min_hits=
None, cov_cws=100, refresh=
None, trust_start=
False,
6067 pairs=pairs, sort_rows=sort_rows, cc_prune=cc_prune)
6068 return list(res[
"d_info"])
6071 sys.stderr.write(f
"# Note: code-sized model: {len(full['trivial'])} error mechanisms flip an observable but no "
6072 f
"group of detectors (e.g., the first one, column {full['trivial'][0]} of the DEM), so that "
6073 f
"the model has a codeword of weight 1: the bound is 1\n")
6077 if not part[
"trivial"]:
6078 info[
"measured_d_info"] = run(part, 0, method)
6079 bound = max(bound, info[
"measured_d_info"][0])
6080 info[
"model_d_info"] = info[
"measured_d_info"]
6081 if full
is not part
and (dstop <= 0
or bound < dstop)
and (wmax <= 0
or bound <= wmax):
6083 dmin = bound
if bound > 1
else 0
6084 info[
"model_d_info"] = run(full, dmin, 1, wmin=bound)
6085 if method != 1
and not 0 < info[
"model_d_info"][1] <= bound:
6086 info[
"model_d_info"] = run(full, dmin, method)
6087 bound = max(bound, info[
"model_d_info"][0])
6088 info[
"d_info"] = [bound, 0, 0]
6089 if _use_distance_cache:
6093 prev = _distance_cache.get(key)
or {}
6094 p_dmin, p_dmax, p_rw = (prev.get(
"dmin", 0)
or 0), (prev.get(
"dmax", 0)
or 0), (prev.get(
"rw_steps", 0)
or 0)
6095 if p_dmax > 0
and bound > p_dmax:
6096 sys.stderr.write(f
"# Warning: the code-sized model bound {bound} exceeds the cached upper bound "
6097 f
"{p_dmax} of the DEM: not stored\n")
6100 record =
_sector_record(max(p_dmin, bound), p_dmax, p_rw, prev.get(
"cws", []))
6101 record[
"dmin_model"] = max(bound, prev.get(
"dmin_model", 0)
or 0)
6103 _distance_cache[key] = record
6106 info[
"d_info"] = list(record[
"d_info"])
6108 print(f
"[dist_m4ri] Code-sized model: certified lower bound d >= {bound} on the circuit distance (model bounds "
6109 f
"{info['model_d_info']}; measured-basis rows only: {info['measured_d_info']})")
6110 return (bound, info)
if return_info
else bound
6114 """Writes codewords to a text file in NZLIST format (1-based indices).
6115 Skips creating the file if cws is empty.
6119 with open(filepath,
"w")
as f:
6120 f.write(
"%% NZLIST\n")
6121 f.write(f
"% {len(cws)} codewords\n")
6123 f.write(f
"{len(cw)} " +
" ".join(str(idx + 1)
for idx
in cw) +
"\n")
6127 """Parses CLI arguments supporting both key=value pairs and standard --flags."""
6128 args: Dict[str, Any] = {
6150 "trust_start":
False,
6151 "start_scan":
False,
6164 "out_reduced":
None,
6173 "solver":
"dist_m4ri",
6175 "cache_file":
"tmp_dist_cache.json",
6180 "nothrottle":
False,
6195 "force_noise":
False,
6200 "model_minority":
False,
6203 "morehelp_topic":
None,
6206 "ignored_files": [],
6209 def _has_later_input_file(start_idx: int) -> bool:
6210 for tok
in argv[start_idx:]:
6212 not tok.startswith(
"-")
6214 and tok.endswith((
".stim",
".dem",
".mtx",
".mmx"))
6215 and os.path.exists(tok)
6220 def _add_help_topic(topic: Optional[str]) ->
None:
6221 args[
"morehelp"] =
True
6222 if topic
and topic !=
"1":
6223 args[
"morehelp_topic"] = f
"{args['morehelp_topic']},{topic}" if args[
"morehelp_topic"]
else topic
6225 def _help_topic_at(j: int) -> Optional[str]:
6226 nxt = argv[j]
if j < len(argv)
else None
6227 return nxt
if nxt
and not nxt.startswith(
"-")
and "=" not in nxt
and not os.path.exists(nxt)
else None
6230 while i < len(argv):
6236 if arg
in (
"--version",
"-version",
"version"):
6237 args[
"version"] =
True
6241 if arg
in (
"--morehelp",
"-morehelp",
"--more-help",
"-more-help",
"morehelp"):
6242 topic = _help_topic_at(i + 1)
6243 _add_help_topic(topic)
6244 i += 2
if topic
else 1
6247 if arg
in (
"-h",
"--help",
"help",
"-?"):
6248 topic = _help_topic_at(i + 1)
6250 _add_help_topic(topic)
6253 i += 2
if topic
else 1
6256 if arg
in (
"--no-throttle",
"-no-throttle",
"nothrottle",
"--nothrottle"):
6257 args[
"nothrottle"] =
True
6261 if arg
in (
"-v",
"--verbose",
"verbose",
"-verbose"):
6262 args[
"verbose"] =
True
6266 if arg
in (
"-q",
"--silent",
"-silent",
"silent",
"--quiet"):
6267 args[
"silent"] =
True
6271 if arg
in (
"--no-cache",
"-no-cache",
"nocache",
"--nocache"):
6272 args[
"use_cache"] =
False
6273 args[
"cache_file"] =
None
6277 if arg
in (
"--trust-start",
"-trust-start",
"--trust_start",
"-trust_start",
"trust_start",
"trust-start"):
6278 args[
"trust_start"] =
True
6282 if arg
in (
"--start-scan",
"-start-scan",
"--start_scan",
"-start_scan",
"start_scan",
"start-scan"):
6283 args[
"start_scan"] =
True
6287 if arg
in (
"--cws",
"-cws",
"cws",
"do_cws=1",
"--do_cws"):
6288 args[
"do_cws"] =
True
6292 if arg
in (
"--model-lb",
"-model-lb",
"--model_lb",
"-model_lb",
"model_lb",
"model-lb"):
6293 args[
"model_lb"] =
True
6297 if arg
in (
"--model-minority",
"-model-minority",
"--model_minority",
"-model_minority",
"model_minority",
6299 args[
"model_minority"] =
True
6304 "--simple",
"-simple",
"--simplified",
"-simplified",
6305 "simple",
"simplified"
6307 args[
"simple"] =
True
6311 if arg
in (
"--full",
"-full",
"full"):
6316 if arg
in (
"--force-noise",
"-force-noise",
"--force_noise",
"-force_noise",
"force_noise",
"force-noise",
6317 "--force",
"-force"):
6318 args[
"force_noise"] =
True
6322 if arg
in (
"--rounds",
"-rounds",
"rounds"):
6325 and "=" not in argv[i + 1]
6326 and argv[i + 1].lstrip(
"-").isdigit()
6328 args[
"rounds"] = int(argv[i + 1])
6335 if arg
in (
"--out-dem",
"-out-dem",
"--out_dem",
"-out_dem",
"out_dem"):
6336 next_tok = argv[i + 1]
if i + 1 < len(argv)
else None
6338 next_tok
is not None
6339 and not next_tok.startswith(
"-")
6340 and "=" not in next_tok
6341 and not next_tok.endswith((
".stim",
".mtx",
".mmx"))
6343 next_tok.endswith(
".dem")
6344 and os.path.exists(next_tok)
6345 and args[
"fdem"]
is None
6346 and not _has_later_input_file(i + 2)
6349 args[
"out_dem"] = next_tok
6352 args[
"out_dem"] =
True
6356 if arg
in (
"--out-stim",
"-out-stim",
"--out_stim",
"-out_stim",
"out_stim"):
6357 next_tok = argv[i + 1]
if i + 1 < len(argv)
else None
6359 next_tok
is not None
6360 and not next_tok.startswith(
"-")
6361 and "=" not in next_tok
6362 and not next_tok.endswith((
".dem",
".mtx",
".mmx"))
6364 next_tok.endswith(
".stim")
6365 and os.path.exists(next_tok)
6366 and args[
"fdem"]
is None
6367 and not _has_later_input_file(i + 2)
6370 args[
"out_stim"] = next_tok
6373 args[
"out_stim"] =
True
6377 if arg
in (
"--out-reduced",
"-out-reduced",
"--out_reduced",
"-out_reduced",
"out_reduced",
"out-reduced"):
6378 next_tok = argv[i + 1]
if i + 1 < len(argv)
else None
6380 next_tok
is not None
6381 and not next_tok.startswith(
"-")
6382 and "=" not in next_tok
6383 and not (next_tok.endswith((
".stim",
".dem",
".mtx",
".mmx"))
and os.path.exists(next_tok))
6385 args[
"out_reduced"] = next_tok
6388 args[
"out_reduced"] =
True
6395 key, val = arg.split(
"=", 1)
6396 if key.startswith(
"--"):
6398 elif key.startswith(
"-"):
6400 elif arg.startswith(
"--")
or arg.startswith(
"-"):
6401 key = arg.lstrip(
"-")
6402 if i + 1 < len(argv)
and not argv[i + 1].startswith(
"-")
and "=" not in argv[i + 1]:
6408 if os.path.exists(arg):
6409 if arg.endswith(
".dem")
or arg.endswith(
".stim"):
6411 elif arg.endswith(
".mmx")
or arg.endswith(
".mtx"):
6412 if args[
"finH"]
is None:
6414 elif args[
"finG"]
is None and args[
"finL"]
is None:
6417 args[
"ignored_files"].append(arg)
6419 args[
"ignored_files"].append(arg)
6421 args[
"unrecognized"].append(arg)
6426 key_lower = key.lower()
6427 if key_lower
in (
"cache",
"cache_file",
"cachefile"):
6428 if val.lower()
in (
"0",
"none",
"false",
"off",
"no"):
6429 args[
"use_cache"] =
False
6430 args[
"cache_file"] =
None
6432 args[
"use_cache"] =
True
6433 args[
"cache_file"] = val
6434 elif key_lower
in (
"fdem",
"dem"):
6436 elif key_lower ==
"finh":
6438 elif key_lower ==
"fing":
6440 elif key_lower ==
"finl":
6442 elif key_lower ==
"fin":
6444 elif key_lower
in (
"hx",
"finhx"):
6446 elif key_lower
in (
"hz",
"finhz"):
6448 elif key_lower
in (
"lx",
"finlx"):
6450 elif key_lower
in (
"lz",
"finlz"):
6452 elif key_lower ==
"finc":
6454 elif key_lower ==
"outc":
6456 args[
"do_cws"] =
True
6457 elif key_lower
in (
"method",
"m"):
6458 args[
"method"] = int(val)
6459 elif key_lower
in (
"dmin",
"d_min"):
6460 args[
"dmin"] = int(val)
6461 elif key_lower
in (
"dmax",
"d_max"):
6462 args[
"dmax"] = int(val)
6463 elif key_lower ==
"dstop":
6464 args[
"dstop"] = int(val)
6465 if args[
"dstop"] < 0:
6466 raise ValueError(f
"invalid '{arg}': dstop must be non-negative")
6467 elif key_lower
in (
"wmin",
"w_min"):
6468 args[
"wmin"] = int(val)
6469 elif key_lower
in (
"wmax",
"w_max"):
6470 args[
"wmax"] = int(val)
6471 elif key_lower ==
"smax":
6472 args[
"smax"] = int(val)
6473 elif key_lower ==
"start":
6475 elif key_lower
in (
"trust_start",
"trust-start",
"truststart"):
6476 args[
"trust_start"] = (
6477 bool(int(val))
if val.isdigit()
else (val.lower()
not in (
"0",
"false",
"no",
"off"))
6479 elif key_lower
in (
"start_scan",
"start-scan",
"startscan"):
6480 if val
not in (
"0",
"1"):
6481 raise ValueError(f
"invalid '{arg}': start_scan should be 0 or 1")
6482 args[
"start_scan"] = val ==
"1"
6483 elif key_lower ==
"cbeg":
6484 args[
"cbeg"] = int(val)
6485 elif key_lower ==
"cend":
6486 args[
"cend"] = int(val)
6487 elif key_lower ==
"css":
6488 args[
"css"] = int(val)
6489 elif key_lower
in (
"dexp",
"dest",
"d_exp"):
6490 args[
"dexp"] = int(val)
6491 elif key_lower
in (
"steps",
"num_steps",
"nsteps"):
6492 args[
"steps"] = int(val)
6493 elif key_lower
in (
"threads",
"num_threads",
"t"):
6494 args[
"threads"] = int(val)
6495 elif key_lower
in (
"timeout",
"time"):
6496 args[
"timeout"] = float(val)
6497 elif key_lower ==
"dw":
6498 args[
"dW"] = int(val)
6499 elif key_lower
in (
"maxc",
"max_c"):
6500 args[
"maxC"] = int(val)
6501 elif key_lower ==
"pmin":
6502 args[
"pmin"] = float(val)
6503 elif key_lower
in (
"merge",
"expand"):
6504 if val.lower()
in (
"1",
"true",
"yes",
"on"):
6505 args[key_lower] =
True
6506 elif val.lower()
in (
"0",
"false",
"no",
"off"):
6507 args[key_lower] =
False
6509 raise ValueError(f
"invalid '{arg}': {key_lower} should be 0 or 1")
6510 elif key_lower
in (
"sort_rows",
"sort-rows",
"sortrows"):
6511 if val.lower()
in (
"1",
"true",
"yes",
"on"):
6512 args[
"sort_rows"] =
True
6513 elif val.lower()
in (
"0",
"false",
"no",
"off"):
6514 args[
"sort_rows"] =
False
6516 raise ValueError(f
"invalid '{arg}': sort_rows should be 0 or 1")
6517 elif key_lower
in (
"cc_prune",
"cc-prune",
"ccprune"):
6518 if val
not in (
"0",
"1",
"2"):
6519 raise ValueError(f
"invalid '{arg}': cc_prune should be 0, 1, or 2")
6520 args[
"cc_prune"] = int(val)
6521 elif key_lower ==
"ortho":
6522 if val
not in (
"0",
"1"):
6523 raise ValueError(f
"invalid '{arg}': ortho should be 0 or 1")
6524 args[
"ortho"] = val ==
"1"
6525 elif key_lower ==
"drop":
6526 if val.lower()
in (
"1",
"true",
"yes",
"on",
"auto"):
6528 elif val.lower()
in (
"0",
"false",
"no",
"off"):
6529 args[
"drop"] =
False
6530 elif val.lower() ==
"css":
6531 args[
"drop"] =
"css"
6534 elif key_lower ==
"outd":
6536 elif key_lower
in (
"out-reduced",
"out_reduced",
"outreduced"):
6537 if val.lower()
in (
"0",
"false",
"no",
"off",
"none"):
6538 args[
"out_reduced"] =
None
6539 elif val.lower()
in (
"1",
"true",
"yes",
"auto",
""):
6540 args[
"out_reduced"] =
True
6542 args[
"out_reduced"] = val
6543 elif key_lower
in (
"morehelp",
"more-help",
"more_help"):
6544 _add_help_topic(val)
6545 elif key_lower
in (
"help",
"h"):
6546 if val
in (
"",
"1"):
6549 _add_help_topic(val)
6550 elif key_lower ==
"noscan":
6551 args[
"noscan"] = int(val)
6552 elif key_lower ==
"irred":
6553 args[
"irred"] = int(val)
6554 elif key_lower ==
"classical":
6555 args[
"classical"] = int(val)
6556 elif key_lower ==
"seed":
6557 args[
"seed"] = int(val)
6558 elif key_lower
in (
"debug",
"dbg"):
6560 elif key_lower ==
"solver":
6561 args[
"solver"] = val
6562 elif key_lower ==
"engine":
6563 args[
"engine"] = val
6564 elif key_lower
in (
"verbose",
"v"):
6565 args[
"verbose"] = bool(int(val))
if val.isdigit()
else (val.lower()
not in (
"0",
"false",
"no",
"off"))
6566 elif key_lower ==
"cws":
6567 args[
"do_cws"] = bool(int(val))
if val.isdigit()
else (val.lower()
not in (
"0",
"false",
"no"))
6568 elif key_lower
in (
"nothrottle",
"no_throttle"):
6569 args[
"nothrottle"] = (
6570 bool(int(val))
if val.isdigit()
else (val.lower()
not in (
"0",
"false",
"no",
"off"))
6572 elif key_lower
in (
"chunk_size",
"chunksize",
"batch",
"chunk"):
6573 args[
"chunk_size"] = int(val)
6574 elif key_lower ==
"ksub":
6575 args[
"ksub"] = int(val)
6576 elif key_lower
in (
"kwin",
"win"):
6577 args[
"kwin"] = int(val)
6578 elif key_lower
in (
"win_mode",
"winmode"):
6579 args[
"win_mode"] = int(val)
6580 elif key_lower
in (
"win_hyb",
"winhyb"):
6581 args[
"win_hyb"] = int(val)
6582 if args[
"win_hyb"] < 0:
6583 raise ValueError(f
"invalid '{arg}': win_hyb should be 0 (256 columns for a DEM, 512 for "
6584 f
"matrices) or positive")
6585 elif key_lower ==
"pairs":
6586 args[
"pairs"] = int(val)
6587 if not -1 <= args[
"pairs"] <= 64:
6588 raise ValueError(f
"invalid '{arg}': pairs should be -1 (auto), 0 (off), or 1 to 64 (random pivot "
6589 f
"rows of the buckets)")
6590 elif key_lower
in (
"min_hits",
"minhits",
"max_hits",
"maxhits"):
6591 args[
"min_hits"] = int(val)
6592 elif key_lower
in (
"cov_cws",
"covcws"):
6593 args[
"cov_cws"] = int(val)
6594 elif key_lower ==
"refresh":
6595 args[
"refresh"] = int(val)
6596 elif key_lower
in (
"simple",
"simplified",
"full"):
6597 args[
"full" if key_lower ==
"full" else "simple"] = (
6598 bool(int(val))
if val.isdigit()
6599 else (val.lower()
not in (
"0",
"false",
"no",
"off"))
6601 elif key_lower ==
"basis":
6602 if val.upper()
not in (
"X",
"Z"):
6604 f
"Invalid basis '{val}'; must be 'X' or 'Z'."
6606 args[
"basis"] = val.upper()
6607 elif key_lower ==
"rounds":
6608 args[
"rounds"] = int(val)
6609 elif key_lower
in (
"p_noise",
"p-noise",
"pnoise"):
6610 args[
"p_noise"] = float(val)
6611 if not 0.0 <= args[
"p_noise"] <= 0.5:
6612 raise ValueError(f
"invalid '{arg}': the noise strength must be in [0, 0.5]")
6613 elif key_lower
in (
"force_noise",
"force-noise",
"forcenoise",
"force"):
6614 args[
"force_noise"] = (
6615 bool(int(val))
if val.isdigit()
else (val.lower()
not in (
"0",
"false",
"no",
"off"))
6617 elif key_lower
in (
"out-dir",
"out_dir",
"outdir"):
6618 args[
"out_dir"] = val
6619 elif key_lower
in (
"out-dem",
"out_dem",
"outdem"):
6620 if val.lower()
in (
"0",
"false",
"no",
"off",
"none"):
6621 args[
"out_dem"] =
None
6622 elif val.lower()
in (
"1",
"true",
"yes",
"auto",
""):
6623 args[
"out_dem"] =
True
6625 args[
"out_dem"] = val
6626 elif key_lower
in (
"model-lb",
"model_lb",
"modellb"):
6627 args[
"model_lb"] = (
6628 bool(int(val))
if val.isdigit()
else (val.lower()
not in (
"0",
"false",
"no",
"off"))
6630 elif key_lower
in (
"model-minority",
"model_minority",
"modelminority"):
6631 args[
"model_minority"] = (
6632 bool(int(val))
if val.isdigit()
else (val.lower()
not in (
"0",
"false",
"no",
"off"))
6634 elif key_lower
in (
"out-model",
"out_model",
"outmodel"):
6635 args[
"out_model"] = val
6636 elif key_lower
in (
"out-stim",
"out_stim",
"outstim"):
6637 if val.lower()
in (
"0",
"false",
"no",
"off",
"none"):
6638 args[
"out_stim"] =
None
6639 elif val.lower()
in (
"1",
"true",
"yes",
"auto",
""):
6640 args[
"out_stim"] =
True
6642 args[
"out_stim"] = val
6644 args[
"unrecognized"].append(arg)
6649 if args[
"classical"] == -1:
6651 args[
"finG"]
is not None or args[
"finL"]
is not None
6652 or args[
"Hz"]
is not None or args[
"Lz"]
is not None
6653 or args[
"Lx"]
is not None
6654 or args[
"fdem"]
is not None or args[
"fin"]
is not None
6656 args[
"classical"] = 0
6657 elif args[
"finH"]
is not None or args[
"Hx"]
is not None:
6658 args[
"classical"] = 1
6664 text = f
"""dist_m4ri.py (version {__version__}): Multithreaded distance calculator Python CLI
6665Usage: dist_m4ri.py [key=val | --flag val ...]
6668 fdem, finH, finG, finL, fin, Hx, Hz, Lx, Lz, pmin, merge, drop, outD,
6669 --out-reduced, classical, --simple, --full, basis, --rounds, --out-dir,
6670 --out-dem, --out-stim, --p-noise, --force-noise, --model-lb, --model-minority,
6671 --out-model, method, dmin, dmax, dstop, dexp (dest), steps, wmin, wmax,
6672 timeout, threads, nothrottle, chunk_size (batch), ksub, kwin (win), win_mode,
6673 win_hyb, pairs, min_hits, cov_cws, refresh, smax, noscan, start, start_scan,
6674 cbeg, cend, finC, outC, maxC, expand, dW, irred, seed, debug, solver, engine,
6675 cache, --no-cache, --verbose, --silent, --cws, --trust-start, sort_rows,
6679 -h, --help : display help for commonly used parameters (fits 80 rows; --help TOPIC: as --morehelp TOPIC)
6680 --morehelp : display full help for all available parameters (or for TOPIC: --morehelp TOPIC)
6681 --version : display the versions of dist_m4ri.py and of the dist_m4ri binary
6683 print(text, file=file)
6687 help_text = f
"""dist_m4ri.py (version {__version__}): Multithreaded distance calculator Python CLI
6688Usage: dist_m4ri.py [key=val | --flag val ...]
6690Input matrices & models:
6691 fdem=FILE Detector Error Model (.dem) or Stim circuit (.stim) file
6692 finH=FILE Parity check matrix H (classical) or Hx (CSS quantum) (.mmx/.mtx)
6693 finG=FILE, finL=FILE Hz check matrix or Lx logical operator matrix (quantum CSS)
6694 fin=PREFIX Base prefix for CSS matrices (loads ${{fin}}X.mtx, ${{fin}}Z.mtx, e.g. try -> tryX.mtx)
6695 Hx=FILE, Hz=FILE CSS check matrices, both required (computes dX and dZ)
6696 Lx=FILE, Lz=FILE CSS logical operators (optional, constructed if omitted)
6697 pmin=PROB Minimum error probability threshold for DEM errors (default: 0.0)
6698 merge=0|1 Merge equal DEM columns (exact; codewords use the first one) (default: 1)
6699 drop=auto|0|css|FILE DEM detectors to drop if verified exact: auto=1 (default), 0 (none), css, or a file
6700 outD=FILE Write the DEM detectors dropped (0-based) to FILE
6701 --out-reduced [PFX] Save the reduced DEM and its maps to the DEM: PFX_red.dem and PFX_red.json
6702 classical=0|1 1: classical code (finH only), 0: quantum CSS (auto-detected)
6703 basis=X|Z Override primary memory basis ('X' or 'Z') for .stim circuits (with drop=css)
6704 --rounds [N] Set the REPEAT count of a .stim circuit (N=2 if omitted; warns if no REPEAT)
6705 --out-dir DIR Output directory for saved .dem/.stim files (default: input file dir)
6706 --out-dem [FILE] Save constructed DEM (default: <basename>_full.dem)
6707 --out-stim [FILE] Save processed/noisy Stim circuit (default: <basename>_full.stim)
6708 --p-noise P Strength of the noise added to a noiseless .stim circuit (default: 0.001; 0: none)
6709 --force-noise Replace the noise of a .stim circuit by that of --p-noise (with P=0: remove it)
6710 --model-lb Certified lower bound for a .stim circuit from its code-sized model
6711 --model-minority With --model-lb, also the rows of the minority-basis checks (default: off)
6713Method and distance bounds:
6714 method=1|2|3 1=RW (upper bound), 2=CC (lower bound/exact), 3=Bracketing (default: 3)
6715 dmin=N Certified lower bound, inclusive (CC starts from dmin) (default: 0)
6716 dmax=N Known upper bound, inclusive (CC up to w=dmax-1 only) (default: 0)
6717 dstop=N Stop once CC certifies dmin>=dstop (not an upper bound) (default: 0)
6718 dexp=N Expected distance estimate (alias: dest) (default: 0)
6720Search limits and stopping criteria:
6721 steps=N Maximum RW steps / information sets (default: 100000)
6722 wmax=N Maximum cluster weight to search in CC (0=until bound/timeout)
6723 wmin=N Stop immediately if cw with weight <= wmin is found (default: 1)
6724 min_hits=N Stop RW when lowest-weight cws are hit N times on average (default: 5)
6725 timeout=SEC Execution timeout in seconds, 0 for infinite (default: 60.0)
6727Multithreading & execution:
6728 threads=N Max worker threads (default: all logical CPUs; throttled unless nothrottle=1)
6729 solver=NAME Distance solver backend: 'dist_m4ri' (default), 'cert', 'auto', or 'codedistance' (also engine=)
6731Codeword collection & caching:
6732 --cws Collect and output non-trivial minimum-weight codewords
6733 outC=FILE Save output codewords (for CSS, auto-suffixed _X.nz / _Z.nz)
6734 finC=FILE Input initial candidate codewords (.nz file)
6735 expand=0|1 DEM codewords: list all choices among equal columns (merge) (default: 0)
6736 cache=FILE Persistent JSON cache file (default: tmp_dist_cache.json)
6737 --no-cache / nocache Disable persistent JSON caching
6738 --verbose / -v Explain bounds, steps, cache status, and miss probability estimates
6739 --silent / -q No warnings on stderr for the inputs which are ignored
6741Extra parameters (see --morehelp for details):
6742 smax=N (0) Max syndrome weight for CC confinement profile (0 to disable)
6743 start=LIST (-1) Expert: CC from listed columns only, e.g. start=0,48 (also start_scan=1; see --morehelp)
6744 --trust-start Expert: accept start-list results as valid (copied to main cache record)
6745 noscan, cbeg, cend Disabled in Python (ignored with a warning; also irred=0, --simple, --full)
6746 ortho=0|1 (1) Expert: 0 skips the check H G^T = 0 of G (valid only if it holds)
6747 sort_rows=0|1 Sort the rows of H by weight for CC (default: 1 for matrices, 0 for a DEM)
6748 cc_prune=0|1|2 (1) CC search: 0 first-row walk, 1 branch and bound, 2 also cover bounds
6749 nothrottle=1 (0) Disable automatic thread throttling (also --no-throttle)
6750 chunk_size=N (0) Max RW batch per claim (default: 0 for adaptive 25-500, alias: batch)
6751 ksub=N (0) EXPERIMENTAL, do not use: RW subspace sketch dimension (0: full matrix)
6752 kwin=N (0) RW localized window size (0: auto/hybrid for n>=500, -1: none, alias: win)
6753 win_mode=0|1 (0) RW window metric: 0=Tanner graph BFS, 1=index proximity
6754 win_hyb=N (0) Hybrid RW window size (0: 256 for a DEM, 512 for matrices)
6755 pairs=N (-1) RW pairs of non-pivot columns (p=2; -1: auto, 0: off)
6756 cov_cws=N (100) Number of lowest-weight cws used for the min_hits statistic
6757 refresh=N (5000) Basis refresh interval in RW steps (experimental ksub only; 0 = off)
6758 maxC=N (0) Maximum number of codewords to collect (0: unlimited)
6759 dW=N (0) Extra weight window above dmin to collect codewords
6760 seed=N (0) Random number generator seed
6761 debug=N (0) Debug bitmap, OR-combined (1: summary, 2: progress, 4: status, ...)
6764 -h, --help [TOPIC] Display this help message (with TOPIC: the same as --morehelp TOPIC)
6765 --morehelp [TOPIC] Display full help for all parameters (or only for TOPIC, e.g., maxC or windows)
6766 --version Display the versions of dist_m4ri.py and of the dist_m4ri binary
6768 print(help_text, file=file)
6772 """The full help text of the CLI (--morehelp)."""
6773 return f
"""dist_m4ri.py (version {__version__}): Multithreaded distance calculator Python CLI
6774Usage: dist_m4ri.py [key=val | --flag val ...]
6776Required input (at least one matrix/model specification):
6777 fdem=FILE Detector Error Model (.dem) or Stim circuit (.stim) file.
6778 Automatically constructs parity check H and logical L matrices. An error
6779 channel which stim cannot write as independent errors (e.g., PAULI_CHANNEL_1
6780 without Y errors) is converted with approximate_disjoint_errors=True (only the
6781 probabilities change).
6782 finH=FILE Parity check matrix file in Matrix Market (.mmx / .mtx) format.
6783 For classical codes, this is check matrix H. For CSS codes, Hx.
6784 finG=FILE Generator / Hz matrix for quantum CSS codes in Matrix Market format.
6785 finL=FILE Logical operator matrix Lx for quantum CSS codes in Matrix Market format.
6786 Note: For a quantum CSS code, either finL (Lx) or finG (Hz) is required.
6787 fin=PREFIX Base prefix for CSS matrices (loads ${{fin}}X.mtx and ${{fin}}Z.mtx, e.g. try -> tryX.mtx).
6788 Hx=FILE, Hz=FILE CSS check matrices Hx and Hz, both required: computes both dX and dZ, and
6789 d = min(dX, dZ) (finH=Hx with finG=Hz gives dZ only).
6790 Lx=FILE, Lz=FILE Alternative syntax for specifying CSS logical operator matrices.
6791 ortho=0|1 Expert option (default: 1): 1, the dist_m4ri binary checks that the rows of H
6792 and G (finG, fin without finL, or Hx and Hz in each sector of a CSS code without
6793 Lx or Lz) are orthogonal, H G^T = 0, and stops with an error otherwise; 0 skips
6794 the check (a warning is printed), e.g., to screen many codes which are
6795 orthogonal by construction (the check takes O(nnz(G) times the column weight
6796 of H) operations). The result is valid only if H G^T = 0. The cache key does
6797 not depend on it. No effect without G (a warning).
6798 pmin=PROB Minimum error probability threshold for DEM parsing (default: 0.0).
6799 Error mechanisms with probability < pmin are filtered out. The column
6800 indices (start, finC, outC) count only the error mechanisms kept: with
6801 another pmin, the columns (and the codewords) change.
6802 merge=0|1 Merge the equal columns of the DEM (error mechanisms with the same detectors
6803 and observables) in the dist_m4ri binary (default: 1). Exact: dropping two
6804 equal columns of a codeword gives a lighter one, so the distance does not
6805 change (nor does the cache key). Column indices (finC, outC, start) are those
6806 of the DEM; the codewords found use the first one of each set of equal columns
6807 (use merge=0 to enumerate the codewords with each of them, or expand=1 to
6808 expand the list found).
6809 drop=auto|0|css|FILE The detectors which the binary drops from the DEM if this is verified to be
6810 exact (default: auto; drop=1 is the same as drop=auto; drop=0 keeps all
6811 detectors). Exact if every error touching them has a twin, an error with the
6812 same other detectors which touches none of them (or none if it has no other
6813 detector), with the same observables (or with L + A H for one matrix A):
6814 then the distance of the DEM is found from the smaller DEM without these
6815 detectors and the errors touching them, and the codewords found (outC) are
6816 codewords of the DEM which use only errors touching no dropped detector.
6817 drop=auto: the binary searches for the largest such set (with the same
6818 observables) in the DEM alone, e.g., the minority-basis detectors of a CSS
6819 circuit with the usual noise (see the README). drop=css: for a Stim circuit
6820 classified as CSS (including locally rotated CSS such as XZZX codes, by
6821 ancilla and data basis tracking), its minority-basis detectors (the basis
6822 other than the memory basis, see basis); otherwise (e.g., for a DEM),
6823 drop=auto is used, with a warning. drop=FILE: the detectors in FILE, for any
6824 DEM (a text file with the 0-based detector indices, k for Dk, separated by
6825 white space or commas; lines starting with '#' or '%' are comments; e.g.,
6826 written by outD). With css or FILE, if dropping the detectors is not exact, a
6827 note is printed and the full DEM is used. The values 0, 1, auto, and css take
6828 precedence over files with these names (write ./1 for a file named 1).
6829 outD=FILE Write the detectors dropped from the DEM by the binary to FILE (0-based, in
6830 the format of a drop file; only the comment line '# 0 of M detectors
6831 dropped ...' if none). With a cached result, the binary is run only for
6832 this (with dmin = dmax: no search).
6833 --out-reduced [PREFIX]
6834 Save the DEM without the detectors dropped (see drop and outD) to
6835 PREFIX_red.dem: the errors touching none of them (after the cutoff pmin, in
6836 the order of the flattened DEM), with the other detectors numbered
6837 consecutively. Its maps to the DEM go to PREFIX_red.json: "detectors" (the
6838 original index of each detector of the reduced DEM, i.e., those not in
6839 outD), "errors" (the original index of each error, as the columns of outC),
6840 "dropped", "num_detectors", "num_errors", "pmin", "source", "dem", "drop",
6841 and "__version__" (also out_reduced=PREFIX; default PREFIX: the basename of
6842 the input file, without _full or _simp, in --out-dir or the directory of
6843 the input file). The reduced DEM has the same distance, and its codewords
6844 are mapped to codewords of the DEM by "errors".
6845 classical=0|1 Code type override:
6846 1: Classical linear code (finH only; ignored with a warning if finG, finL,
6847 fin, fdem, Hz, Lx, or Lz is given).
6848 0: Quantum CSS code (requires logicals or Hz).
6849 Default: auto-detected (1 if only finH is given; 0 otherwise).
6850 --simple / --full Disabled since version 0.13.0 (also simple=1, full=1): accepted for backward
6851 compatibility, but ignored with a warning to stderr. The DEM of a Stim circuit
6852 always has all detectors: the binary drops detectors if this is exact (see
6853 drop; e.g., the minority-basis detectors which --simple stripped), which is
6854 as fast and gives the distance of the full DEM (the stripped DEM only gives a
6855 lower bound when dropping them is not exact).
6856 basis=X|Z Explicitly set primary memory basis ('X' or 'Z') for .stim circuits
6857 (default: auto-detected from filename or initial/final resets/measurements);
6858 with drop=css, the detectors of the other basis are dropped if this is exact
6859 (otherwise only used for the --verbose report; ignored with a warning).
6860 --rounds [N] Construct DEM from a .stim circuit with N repetitions in its REPEAT block
6861 (default: 2 when --rounds is given without a number; also rounds=N).
6862 Issues a warning and continues with the original circuit if no REPEAT block.
6863 --out-dir DIR Output directory for saving constructed DEM and/or processed Stim circuit
6864 files (also out_dir=DIR; default: directory of the input .stim/.dem file).
6865 --out-dem [FILE] Save constructed (flattened) DEM to FILE. If given without a filename (or
6866 out_dem=1), automatically saves as <basename>_full.dem.
6867 The codewords (outC) index its error lines: save the DEM in the same run (a DEM
6868 generated again from the circuit may differ, e.g., with another stim version).
6869 --out-stim [FILE] Save processed (noisy, rounds-adjusted) Stim circuit to FILE. If given without
6870 a filename (or out_stim=1), automatically saves as <basename>_full.stim.
6871 --p-noise P Strength p of the uniform circuit-level noise added to a noiseless Stim circuit
6872 (also p_noise=P; default: 0.001; 0: no noise is added, and then a noiseless
6873 circuit gives no distance): DEPOLARIZE2(p) after two-qubit gates (CX, CZ,
6874 XCY, SWAP, ISWAP, SQRT_XX, CXSWAP, ..., also II), DEPOLARIZE1(p/10) after
6875 one-qubit gates (I, X, H, H_XY, S, SQRT_X, C_XYZ, ...), on the qubit of a
6876 classically controlled gate (e.g., CX rec[-1] 5), and on the qubits idle since
6877 the previous TICK, and X (Z) flips with probability p after Z-basis or Y-basis
6878 (X-basis) resets and before measurements (before and after MR, MRX, MRY); the
6879 multi-qubit measurements (MPP, MXX, MYY, MZZ) and Pauli product gates (SPP,
6880 SPP_DAG) get no noise. A circuit with noise (a noise channel, or a noisy
6881 measurement such as M(0.001)) is used as is, unless --force-noise. The
6882 distance does not depend on p (unless pmin > 0), only on which faults exist.
6883 --force-noise Replace the noise of a Stim circuit (also --force or force_noise=1): all of its
6884 noise is removed (noise channels, measurement flip probabilities, heralded
6885 noise; see stim's Circuit.without_noise()), and then the uniform noise of
6886 --p-noise is added (none with --p-noise 0, e.g., to save the noiseless circuit
6887 with --out-stim), e.g., to compare circuits with the same noise model.
6888 --model-lb Certified lower bound on the distance of a Stim memory circuit (fdem=FILE.stim)
6889 from its code-sized model, instead of the distance of its DEM (also
6890 model_lb=1; see compute_model_lower_bound()): the detectors linked by shared
6891 measurements, other than the final data measurements, form chains (one per
6892 check of a memory circuit), and each chain gives a row of the model, the sum
6893 of its detector rows (the chain of a check of the measured basis telescopes
6894 to the parity of the final data measurements on the check; the chains of the
6895 minority-basis checks only with --model-minority), with the equal columns
6896 merged. Each codeword of the DEM gives a model codeword of no larger weight,
6897 so the model distance certified by CC is a lower bound on the circuit
6898 distance; it ignores the timing within the rounds (not tight in general, e.g.,
6899 5 for the bb_72 circuit of distance 6). The binary runs on the model with the
6900 measured-basis rows, then (with --model-minority) with all rows from that bound
6901 (RW first; method, threads, timeout, steps, dstop, wmax, seed, nothrottle,
6902 pairs, sort_rows, and cc_prune apply to each run). The bound is stored as dmin
6903 in the cache record of the DEM, and the bounds of the circuit are printed.
6904 --model-minority With --model-lb, also the chains of the minority-basis checks are rows of the
6905 model (also model_minority=1; default: off). With depolarizing-type noise,
6906 they do not raise the bound (a lightest model codeword has a version without
6907 components of the other basis), but they may for other noise models.
6908 --out-model PREFIX With --model-lb, write the model to PREFIX_HR.mtx and PREFIX_LR.mtx (also
6910 (positional files) A file name without a key (the file must exist): a .dem or .stim file is used
6911 as fdem=FILE, and a .mtx or .mmx file as finH=FILE, or as finG=FILE if finH is
6912 already given (and neither finG nor finL); other files are ignored.
6913 (conflicting inputs) The input is selected in the order fdem, Hx/Hz (CSS code), and
6914 finH/finG/finL/fin (fin only supplies the missing finH and finG, while the
6915 binary ignores fin followed by finH, finG, or finL, and fin after them is an
6916 error); the ignored inputs (e.g., matrices with fdem, pmin, merge, expand,
6917 drop, or outD without fdem, basis, --p-noise, or --force-noise without a Stim
6918 circuit, basis without drop=css, fin with finH, classical=1 with finG,
6919 ortho=0 without G) give a warning (unless --silent).
6922 method=1|2|3 Calculation method (default: 3):
6923 1: Random Window (RW) algorithm (upper bound on distance).
6924 Repeatedly samples random information sets to find low-weight
6925 codewords. Fast for discovering small errors.
6926 2: Connected Cluster (CC) algorithm (lower bound / exact distance).
6927 Exhaustive cluster search finding certified lower bound dmin or
6928 exact distance if run to completion.
6929 3: Bracketing mode (concurrent RW and CC).
6930 Dynamically allocates worker threads between CC (lower bound) and
6931 RW (upper bound) to converge on the exact distance rapidly, based on
6932 the measured CC and RW times, the bounds, and the remaining timeout.
6934Distance bounds and guidance:
6935 dmin=N Known certified lower bound on distance (default: 0).
6936 CC search begins at weight w = max(1, dmin).
6937 dmax=N Known upper bound on distance (default: 0).
6938 CC ends once dmin = dmax (with outC, after the rounds w = dmax..dmax+dW);
6939 RW ignores candidate codewords of weight >= dmax unless collecting codewords
6940 or needed for min_hits. For a CSS code (Hx=, Hz=), dmax is a bound on
6941 d = min(dX, dZ), not on either sector: it is used as dstop for both sectors.
6942 dstop=N Stop target for the lower bound (default: 0 = off): the search (method=2
6943 or 3) ends once CC has certified dmin >= dstop, unless a lighter codeword is
6944 found. Unlike dmax, dstop is not an upper bound and is not reported as dmax
6945 (the bounds are 'dmin w steps' with dmin >= dstop, w: the lightest codeword
6946 found, or 0); a cached lower bound dmin >= dstop answers the request without
6947 a calculation. E.g., dstop=U wmin=U-1 decides whether d >= U.
6948 dexp=N Expected code distance estimate (alias: dest) (default: 0).
6949 Hint for method=3: as long as RW has found no codeword, CC rounds at
6950 w > dexp run only on threads which cannot run RW (CC pauses if RW can
6951 use all threads); CC resumes once RW finds a codeword or ends.
6953Search limits and stopping criteria:
6954 steps=N Maximum number of RW steps / information sets across all threads
6956 wmax=N Maximum cluster weight to analyze in CC (default: 0 = until bound/timeout;
6957 with method=2, a known dmax is used as wmax).
6958 wmin=N Minimum distance threshold (default: 1).
6959 If a codeword of weight w <= wmin is discovered, search halts immediately
6960 (not when collecting codewords with outC or maxC).
6961 timeout=SEC Execution timeout in seconds (default: 60.0; set 0 for infinite).
6962 In method=3, it guides the CC vs RW thread balance, and a CC round predicted
6963 not to finish in time is not started while RW runs (in method=2, and with
6964 steps=0, it is started anyway: it may still find a codeword of weight dmin).
6966Multithreading & throttling:
6967 threads=N Maximum number of worker threads to allocate (default: all logical CPUs
6968 available to the process, i.e., its CPU affinity mask, e.g., with taskset).
6969 Subject to automatic thread throttling unless nothrottle=1 is specified:
6970 - Small codes, methods 1 and 3 (<= 4 threads for n < 60, or for m*n < 2e4
6971 with a small RW workload m*n*steps < 5e7; otherwise <= 16 threads for
6972 n < 150 or m*n < 1e5, if m*n*steps < 2e8; m: number of rows of H).
6973 Method 2 starts 4 threads, and the others once a CC round takes longer
6974 (predicted > 2 ms on the threads started, or running for 5 ms).
6975 - Large memory matrices (above 15 MB of dense RW working memory per thread:
6976 at most 32 RW threads, within a quarter of the physical memory).
6977 - Small RW step counts (RW threads clamped to <= (steps + 9) / 10).
6978 The last two limits apply to RW threads only: CC (method=2) is not
6979 limited, and in method=3 CC rounds can use all threads.
6980 CC rounds with few start columns per thread are split into work units
6981 which also fix the first 1-3 cluster extensions (balanced load).
6982 nothrottle=1 Disable automatic thread throttling (also --no-throttle).
6983 Forces allocation of the exact number of threads requested.
6984 chunk_size=N Maximum RW batch claimed at once by a thread (default: 0 = adaptive
6985 25-500). Near the end of the steps, batches shrink to at most
6986 ceil(remaining / (4 * RW threads)) steps. Alias: batch=N.
6987 ksub=N EXPERIMENTAL, should not be used (a warning is printed): subspace sketch
6988 dimension for RW (default: 0 = full matrix).
6989 Precomputes N = ker(H) once; each thread echelonizes a compact
6990 ksub x n sampled subspace in L1/L2 cache (e.g. ksub=32 or 64).
6991 Automatically falls back to ksub=0 if m < nu = dim(ker H) (a warning is
6992 printed with --verbose or debug&1). Each step reduces the span of ksub
6993 distinct rows of N, so that a few codewords are found much more often than
6994 others, and min_hits may end RW early.
6995 kwin=N Localized column permutation window size W around a random seed
6996 column j0, used in every RW step (alias: win=N). Default: 0 = automatic
6997 hybrid for n>=500: every other step uses an index window (win_mode=1) of
6998 win_hyb columns, and the other steps uniform random permutations (also
6999 for codewords which are not local in the column order). kwin=-1 (any
7000 negative value): no windows, uniform random permutations in every step.
7001 win_mode=0|1 Locality metric for kwin > 0: 0 = Tanner graph BFS neighbors
7002 (default), 1 = contiguous column index window.
7003 win_hyb=N Window size of the hybrid RW steps (kwin=0, n>=500; at most 3n/4).
7004 Default: 0 = 256 columns for a DEM, 512 for matrices. The window
7005 columns come first in the elimination order; in a DEM, they are errors
7006 close in time, which touch few detectors: windows of 192-256 columns find
7007 circuit codewords 2-10 times faster than 512, and windows of one round of
7008 columns much slower. With --verbose (or debug&1), the binary prints the
7009 rounds of a DEM (estimated from the time translation of its errors: D
7010 detectors and L columns per round) and the codewords found by the
7011 uniform and by the window steps.
7012 pairs=N RW pairs of non-pivot columns (p=2, as in Lee-Brickell and Stern): a RW
7013 step also finds the codewords with exactly two non-pivot columns,
7014 checking only the pairs of columns which agree on N random pivot rows
7015 (at most 64; 0 = off). Default: -1 = auto, about one non-pivot column per
7016 bucket, with fewer rows while a codeword of the weight of the upper bound
7017 would be kept with probability below 0.8, but enough rows to keep the
7018 pairs of a step within about the work of its elimination (e.g., at least
7019 6 for large circuit DEMs while the upper bound is far above the
7020 distance). The pairs find the minimum-weight codewords of circuit DEMs
7021 7-26 times faster, and lighter codewords of codes with a large distance.
7022 Not with ksub. With --verbose (or debug&1), the binary prints the pairs
7023 checked per step and the codewords found by pairs and by single columns.
7024 min_hits=N QDistRnd-style RW stopping criterion (default: 5, 0 = off; alias: max_hits).
7025 Stops RW when the average number of hits <n> per codeword (by all RW steps,
7026 with or without windows, and by CC or finC) reaches min_hits, both for the
7027 codewords of the minimum weight found and for a representative set of
7028 cov_cws lowest-weight codewords (heavier codewords are kept until enough
7029 lighter ones are found). A single codeword suffices. A lighter codeword is
7030 missed with probability about exp(-<n>), assuming that lighter codewords
7031 are found at least as often. With debug&1 (or --verbose), a warning is
7032 printed at the end if the hit counts of codewords of the same weight are
7033 strongly non-uniform (then exp(-<n>) is optimistic), together with an
7034 information-set estimate (uniform random information sets) of the
7035 probability to miss a lighter codeword and of the RW steps needed for 1%.
7036 With --verbose, both estimates are always shown for a result which is not
7037 certified. With min_hits=0, RW records only the codewords lighter than the
7038 upper bound (unless collecting codewords with outC, maxC, or --cws), so
7039 that the repeated hits are not counted.
7040 cov_cws=N Size of the representative set of lowest-weight codewords for min_hits
7041 (default: 100; 0 = only the minimum weight). Without outC and maxC, at
7042 most cov_cws minimum-weight codewords are tracked (all if cov_cws=0).
7043 With fewer minimum-weight codewords, a smaller cov_cws may stop RW
7045 refresh=N Periodic adaptive basis refresh interval in RW steps, only with the
7046 experimental ksub > 0 (default: 5000 when ksub > 0, 0 to disable).
7047 Re-echelonizes N and replaces one heavier basis row by a discovered
7048 minimum-weight codeword.
7050Connected Cluster (CC) search options:
7051 smax=N Maximum syndrome weight for confinement profile (default: 0).
7052 When smax > 0, tracks minimum syndrome weights for each error weight.
7053 When smax=0, confinement is not computed. The binary disables it if
7054 dmin > 1 (also a lower bound from the cache), as the small cluster weights
7056 sort_rows=0|1 Sort the rows of H by increasing weight (stable) in the dist_m4ri binary
7057 (default: the binary's, 1 for matrices and 0 for a DEM). With cc_prune=0,
7058 CC extends a cluster by the columns of the first row of its syndrome (in
7059 row order), so that light rows first reduce the CC work for matrices with
7060 very different row weights (e.g., 1.7-4x with the hook columns of an
7061 extended CSS distance), while the detectors of a DEM are in time order,
7062 which is better (use sort_rows=0 also for matrices exported from a DEM).
7063 With cc_prune=1 or 2, the order of the rows only breaks ties. The
7064 distance, RW, and the cache key do not depend on the order of the rows; CC
7065 enumerates the same codewords (outC), possibly in another order (with
7066 cc_prune=0, also with other hit counts).
7067 cc_prune=0|1|2 The CC search of the dist_m4ri binary (with smax=0; default: the binary's,
7068 1; also --cc-prune N). 0: the walk of the versions before 0.15.0, which
7069 extends a cluster by the columns of the first unsatisfied row of its
7070 syndrome. 1: the branch and bound: it branches on the unsatisfied row
7071 with the fewest available columns, the columns of the earlier branches are
7072 forbidden in the later ones (each codeword is found once: one CC hit), and
7073 a branch is pruned by lower bounds on the number of columns needed (an
7074 unsatisfied row without available columns, or a packing of unsatisfied
7075 rows with disjoint sets of available columns). 2: also the coverage and
7076 fractional cover bounds (fewer nodes, more work per node). All values
7077 find the same codewords and distances (doc/cc_completeness.tex); with the
7078 expert start list (and start_scan=0), cc_prune=1 and 2 forbid the columns
7079 listed before the start column. The cache key does not depend on it.
7080 start=LIST Expert option: comma-separated list of CC start columns (0-based), e.g.,
7081 start=0,48,96 (default: -1 = all columns; start=N is a one-element list).
7082 CC clusters are grown only from the listed columns, and they are NOT
7083 limited to larger column indices. Use, e.g., one column per block of a
7084 quasi-cyclic code. WARNING: dmin (and an exact result) is valid only if a
7085 code symmetry maps every minimum-weight codeword to one containing a listed
7086 column; dmax is always valid. For DEM input, columns index the error
7087 mechanisms in their order in the (flattened) DEM, after the pmin cutoff.
7088 Results are cached under a separate key '<key>:start=a,b,c'; only dmax and
7089 codewords are merged into the main cache record. (Before version 0.10.0,
7090 start=N limited CC to clusters started at column N using only larger
7091 columns; this is now cbeg=N cend=N of the dist_m4ri binary.)
7092 start_scan=0|1 Expert option, with start (also --start-scan; default: 0): 1: a cluster grown
7093 from a listed column c only uses the columns > c, as in the scan of all
7094 columns. WARNING: dmin is then valid only if a code symmetry maps every
7095 minimum-weight codeword to one whose smallest column is listed, e.g., if
7096 the listed columns are the smallest columns of the orbits of a group of code
7097 automorphisms, one per orbit (start=0,l for the two blocks of a two-block
7098 group-algebra code of length 2l over an abelian group). The later start
7099 columns then need much less work, the least if each orbit is a range of
7100 consecutive columns. Cached under '<key>:start=a,b,c:scan'. Not for a DEM.
7101 --trust-start Expert option (also trust_start=1): accept the start-list results as valid,
7102 i.e., also merge dmin and rw_steps into the main cache record, and copy an
7103 existing exact start-list record to the main record even if no calculation
7104 is needed. Only use it if the code symmetry assumption above holds.
7105 noscan, cbeg, cend Disabled in the Python interface: accepted for backward compatibility, but
7106 ignored with a warning to stderr. These expert options of the dist_m4ri
7107 binary do not give valid bounds on their own: noscan=1 runs CC only at
7108 weight w=wmax (method=2); cbeg/cend limit CC start columns to a range for
7109 split runs, where every column is eventually covered by one of the runs.
7110 Run the dist_m4ri binary directly to use them (see its --morehelp).
7112Codeword collection and export:
7113 --cws Collect and display non-trivial minimum-weight codewords.
7114 outC=FILE Export found codewords of weight up to min_w + dW (min_w: minimum weight
7115 found) to file in .nz format. In method=2/3, once the distance d is
7116 known, CC rounds w = d..d+dW enumerate all such irreducible codewords
7117 (see irred; unless the timeout is hit; for a DEM, up to the choice of
7118 equal columns: see merge).
7119 In CSS mode, automatically saves X-codewords to FILE_X.nz and
7120 Z-codewords to FILE_Z.nz. The codewords of the run are merged with the
7121 cached ones; with finC=FILE (the same file), those already in FILE are kept
7122 (also reducible ones recorded before version 0.14.8).
7123 finC=FILE Import initial candidate codewords from file in .nz format.
7124 In CSS mode (Hx=, Hz=), each sector reads its own file FILE_X.nz or FILE_Z.nz
7125 if at least one of them exists and is not empty (a sector without its file
7126 gets no input codewords); otherwise, FILE itself is given to both sectors
7127 (the binary skips the codewords which are not valid in a sector). If FILE
7128 is the same file as outC, a missing or empty file is ignored. The binary
7129 replaces a reducible codeword by an irreducible part (see irred).
7130 maxC=N Maximum number of codewords to collect (default: 0 = unlimited); with
7131 expand=1, also the maximum number of codewords of the expanded list.
7132 dW=N Extra weight window above minimum distance to collect codewords
7133 (w <= min_w + dW) (default: 0).
7134 irred=0|1 Disabled in the Python interface: irred=0, an expert option of the
7135 dist_m4ri binary, is ignored with a warning to stderr. Only irreducible
7136 codewords are recorded (irred=1, the default of the binary): a codeword
7137 which is the union of a codeword and a disjoint nonzero vector with zero
7138 syndrome (e.g., a trivial weight-3 vector of a circuit DEM, from weight
7139 d+3 on) is not. Minimum-weight codewords are always irreducible.
7140 expand=0|1 Python only, for a DEM: list each codeword found once for every choice
7141 among equal columns, which the binary merges (see merge; default: 0). The
7142 binary and the cache keep the merged list. With dW <= 1, the expanded list
7143 is that of merge=0 (with dW >= 2, merge=0 also lists lighter codewords with
7144 two equal columns added). The list grows quickly with the weight: with
7145 maxC > 0, at most maxC codewords are written (a warning if some are dropped).
7146 (DEM codewords) The indices refer to the error lines of the DEM in the order of the flattened
7147 DEM, after the pmin cutoff. For a Stim circuit, save the DEM with --out-dem in
7148 the same run (a warning is printed otherwise): a DEM generated again from the
7149 circuit may differ (e.g., with another stim version).
7151Distance caching (Python CLI):
7152 cache=FILE Path to persistent JSON cache file (default: tmp_dist_cache.json).
7153 --no-cache / nocache Disable reading and writing to the persistent JSON cache.
7156 solver=NAME, engine=NAME
7157 Distance calculation engine: 'dist_m4ri' (default), 'cert' ('qec_cert'),
7158 'auto' (delegates distance-only calls to qec_cert if installed), or
7160 --verbose / -v Enable verbose output with detailed explanations of bounds,
7161 timings, steps, and cache status (implies the debug bits 1, 2,
7162 65536, and 131072). For a RW upper bound which is not certified,
7163 also estimates the probability that a lighter codeword was missed
7164 (from the average hits <n>, and from uniform random information sets
7165 with n, rank(H), and the RW steps), with the RW steps and time needed
7166 for a 1% miss probability.
7167 --silent / -q No warnings on stderr for the inputs which are ignored (e.g., matrices with
7168 fdem, the options of the binary with solver=codedistance, or the options
7169 not used with --model-lb); the warnings on the validity of a result are
7170 still written. In Python: set_silent().
7171 seed=N Random number generator seed (default: 0; the binary replaces seed<=0 by
7172 time(NULL) + 10*pid - 1000*seed).
7173 debug=N Debug bitmap (default: 0), a decimal or hexadecimal (0x...) integer;
7174 multiple debug arguments are OR-combined. Bits 1 to 32768 are passed
7175 to the dist_m4ri binary, which prints diagnostic output to stderr (see
7176 its --morehelp; lower bits are more informative):
7177 1: summary (input matrices, rounds of a DEM, RW windows, stop reason,
7178 codeword statistics, codewords found by each kind of RW step),
7179 2: progress (run plan, finished CC rounds, new upper bounds),
7180 4: periodic status (after 1, 2, 4, ... s, then every 60 s),
7181 8: thread allocation and timing, 16: code parameters (ranks, k),
7182 32: codeword supports, 64: arguments, 128: matrices,
7183 256: codeword dump, 2048: no size cutoff.
7184 Bits 65536 and above are used by the Python wrapper only:
7185 65536: dist_m4ri command lines and run times,
7186 131072: cache messages,
7187 262144: Stim circuit to DEM conversion details,
7188 524288: keep (and list) the temporary files.
7190Option groups (--help GROUP prints all entries of GROUP):
7191 windows kwin, win_mode, win_hyb
7192 stopping, stop steps, wmin, timeout, min_hits, cov_cws
7193 bounds dmin, dmax, dstop, dexp, wmax
7194 dem fdem, pmin, merge, drop, outD, --out-reduced, expand
7195 stim fdem, basis, --rounds, --out-dir, --out-dem, --out-stim, --p-noise,
7196 --force-noise, --model-lb, --model-minority, --out-model
7197 matrices, matrix finH, finG, finL, fin, Hx, Lx, classical, ortho
7198 codewords --cws, outC, finC, maxC, dW, irred, expand
7199 threading threads, nothrottle, chunk_size
7200 cache cache, --no-cache, --trust-start
7201 expert start, start_scan, --trust-start, noscan
7202 experimental ksub, refresh
7205 -h, --help [TOPIC] Display summary help message (fits 80 rows); with TOPIC, the same as
7206 --morehelp TOPIC (also --help=TOPIC).
7207 --morehelp [TOPIC] Display this full help message with all parameters, or only the entries of
7208 TOPIC, e.g., --morehelp maxC (also --morehelp=TOPIC; several topics separated
7209 by commas, e.g., maxC,dW; the names are case-insensitive, without '-' or '_'
7210 (and '=VALUE' in --help=TOPIC and --morehelp=TOPIC: a separate argument with
7211 '=' is not a TOPIC), and include aliases such as dest or batch; an unknown
7212 name gives the closest names on stderr, and the exit code 1). A group name
7213 (see Option groups, also as singular or plural, e.g., window) gives all
7214 entries of the group, and a parameter of a group also the other entries of
7215 its group (see also).
7216 --version Display the versions of dist_m4ri.py and of the dist_m4ri binary.
7222 The entries of a help text (see _cli_morehelp_text()): (section header, the lines of the entry, its names). An
7223 entry starts with a line indented by 2 spaces (the names, then the description), and continues with the lines
7224 indented by more; a section header is a line without indentation ending with ':'. The names (a dict: normalized
7225 name -> name as written) are those in the first column (e.g., 'Hx=FILE, Hz=FILE' -> Hx, Hz; '--no-cache /
7226 nocache'), without the values (FILE, [N], ...; but all words of a note such as '(DEM codewords)'), and the aliases
7227 in the description ('alias: dest', 'Alias: win=N', 'also --no-throttle', 'also rounds=N'); normalized: lowercase,
7228 without '-' and '_'.
7231 entries: List[Tuple[str, List[str], Dict[str, str]]] = []
7233 for line
in text.splitlines():
7234 if line
and not line[0].isspace()
and line.rstrip().endswith(
":"):
7235 section = line.rstrip()
7237 m = re.match(
r"^ (\S.*?)(?:\s{2,}|$)", line)
7239 names: Dict[str, str] = {}
7240 for tok
in re.split(
r"[,/\s]+", m.group(1)):
7241 tok = tok.strip(
"()[]").split(
"=", 1)[0]
7243 if tok.strip(
"-")
and (m.group(1).startswith(
"(")
or not tok.isupper()):
7245 entries.append((section, [line], names))
7246 elif line.startswith(
" ")
and entries:
7247 entries[-1][1].append(line)
7248 for _, lines, names
in entries:
7249 desc =
" ".join(s.strip()
for s
in lines)
7250 for a
in re.findall(
r"(?i)\balias(?:es)?:\s*(-{0,2}[\w-]+)", desc) + re.findall(
r"\balso (--?[\w-]+)", desc) + \
7251 re.findall(
r"\balso ([\w-]+)=", desc):
7257 """A name of a help entry normalized for matching (see _help_entries()): lowercase, without '-' and '_'."""
7258 return name.lower().replace(
"-",
"").replace(
"_",
"")
7263 The normalized names of the members of a group (an entry of the section 'Option groups' of the full help: the
7264 names listed after its first column), or None for another entry.
7267 section, lines, _ = entry
7268 if not section.startswith(
"Option groups"):
7270 m = re.match(
r"^ \S.*?(?:\s{2,}|$)", lines[0])
7271 text =
" ".join([lines[0][m.end():]
if m
else ""] + [s.strip()
for s
in lines[1:]])
7272 return {
_help_name_key(t)
for t
in re.split(
r"[,\s]+", text)
if t.strip(
"-")}
7276 """Whether the topic key matches the name gkey of a group, also as singular or plural (e.g., window, windows)."""
7277 return key
in (gkey, gkey +
"s")
or (gkey.endswith(
"s")
and key == gkey[:-1])
7282 Prints the full help of the CLI (--morehelp), or with `topic` (one or more names separated by commas, see
7283 _help_entries()) only the entries of these parameters, in the order of the full help under their section headers.
7284 A topic may also be a group of the section 'Option groups' (also as singular or plural, e.g., window): then all
7285 its members are printed; and an entry of a group also gives the other members of its groups and these groups
7286 (one level: 'see also'). Returns 0, or 1 if some topic has no entry (then the closest names are suggested on
7291 keys = [(t,
_help_name_key(t.split(
"=", 1)[0]))
for t
in (s.strip()
for s
in (topic
or "").split(
","))]
7292 keys = [(t, k)
for t, k
in keys
if k]
7294 print(text, file=file)
7299 asked: Set[int] = set()
7301 found = [i
for i, e
in enumerate(entries)
7304 names = {k: v
for e
in entries
for k, v
in e[2].items()}
7305 close = [names[k]
for k
in difflib.get_close_matches(key, list(names), n=5, cutoff=0.6)]
7306 hint = f
"; did you mean {', '.join(close)}?" if close
else ""
7307 sys.stderr.write(f
"dist_m4ri.py: no help entry for '{t}'{hint} (--morehelp lists all parameters)\n")
7310 chosen: Set[int] = set(asked)
7311 for g, mem
in enumerate(members):
7314 in_group = [i
for i, e
in enumerate(entries)
if members[i]
is None and mem & set(e[2])]
7315 if g
in asked
or asked & set(in_group):
7317 chosen.update(in_group)
7320 for i
in sorted(chosen):
7321 sec, lines, _ = entries[i]
7329 print(
"\n".join(out), file=file)
7335 Writes warnings to stderr for the CLI inputs which are ignored. The input is selected in the order fdem (a DEM or
7336 a Stim circuit), Hx / Hz (a CSS code), and finH / finG / finL / fin (one sector of a quantum code, or a classical
7337 code); fin=PREFIX supplies only the missing finH (PREFIX + 'X.mtx') and finG (PREFIX + 'Z.mtx', unless finL). The
7338 disabled options --simple and --full always give a warning (see _warn_disabled_simple_full()), and so does the
7339 expert option irred of the dist_m4ri binary, unless irred=1 (the codewords recorded are always irreducible). The
7340 expert option ortho=0 gives a warning here for a DEM or a classical code (for a quantum code, see _warn_ortho()).
7342 def given(*keys: str) -> List[str]:
7343 return [k
for k
in keys
if args.get(k)
is not None]
7346 if args.get(
"irred")
is not None and args[
"irred"] != 1:
7347 _warn_ignored(f
"irred={args['irred']} is disabled in the Python interface (ignored: only irreducible "
7348 f
"codewords are recorded); use the dist_m4ri binary directly")
7349 stim_opts = [opt
for opt, on
in ((
"basis=", args.get(
"basis")
is not None),
7350 (
"--out-stim", args.get(
"out_stim")
is not None),
7351 (
"--p-noise", args.get(
"p_noise")
is not None),
7352 (
"--force-noise", bool(args.get(
"force_noise"))))
if on]
7354 if args.get(
"fdem"):
7355 ign = given(
"finH",
"finG",
"finL",
"fin",
"Hx",
"Hz",
"Lx",
"Lz")
7357 warns.append(f
"fdem={args['fdem']} given: ignoring {', '.join(k + '=' for k in ign)}")
7358 if stim_opts
and not str(args[
"fdem"]).endswith(
".stim"):
7359 warns.append(f
"{', '.join(stim_opts)}: only used for a Stim circuit (.stim); ignored")
7360 elif (args.get(
"basis")
is not None and str(args.get(
"drop")).lower() !=
"css"
7361 and not args.get(
"verbose")):
7362 warns.append(
"basis=: only used with drop=css (the minority-basis detectors of a CSS circuit); ignored")
7363 if args.get(
"expand")
and not (args.get(
"outC")
or args.get(
"do_cws")):
7364 warns.append(
"expand=: only used with outC= or --cws; ignored")
7366 dem_opts = stim_opts + [opt
for opt, on
in ((
"--out-dir", args.get(
"out_dir")
is not None),
7367 (
"--out-dem", args.get(
"out_dem")
is not None))
if on]
7368 if (args.get(
"pmin")
or 0.0) > 0.0:
7369 dem_opts.append(
"pmin=")
7370 if args.get(
"merge")
is not None:
7371 dem_opts.append(
"merge=")
7372 if args.get(
"expand")
is not None:
7373 dem_opts.append(
"expand=")
7374 if args.get(
"drop")
is not None:
7375 dem_opts.append(
"drop=")
7376 if args.get(
"outD")
is not None:
7377 dem_opts.append(
"outD=")
7378 if args.get(
"out_reduced")
is not None:
7379 dem_opts.append(
"--out-reduced")
7381 warns.append(f
"{', '.join(dem_opts)}: only used with fdem= (a DEM or Stim circuit); ignored")
7382 if args.get(
"Hx")
is not None or args.get(
"Hz")
is not None:
7383 ign = given(
"finH",
"finG",
"finL",
"fin")
7385 warns.append(f
"Hx=/Hz= given (CSS code): ignoring {', '.join(k + '=' for k in ign)}")
7387 ign = given(
"Lx",
"Lz")
7389 warns.append(f
"{', '.join(k + '=' for k in ign)}: only used for a CSS code (Hx= and Hz=); ignored")
7390 ign = given(
"finH",
"finG",
"finL")
7391 if args.get(
"fin")
is not None and ign:
7392 warns.append(f
"fin={args['fin']} together with {', '.join(k + '=' for k in ign)}: the explicit file "
7393 "names take precedence")
7394 if args.get(
"classical") == 1
and given(
"finG",
"finL",
"fin",
"fdem",
"Hz",
"Lx",
"Lz"):
7395 warns.append(
"classical=1 ignored: the input is a quantum code or a DEM")
7396 if args.get(
"ortho")
is False:
7397 quantum = bool(given(
"Hx",
"Hz",
"finG",
"finL",
"fin"))
or args.get(
"classical") == 0
7398 if args.get(
"fdem")
or not quantum:
7399 warns.append(
"ortho=0: only used for a quantum code with G (finG=, fin=, or Hx= and Hz=); ignored")
7400 if args.get(
"css")
is not None:
7401 warns.append(f
"css={args['css']}: not used by the Python interface (CSS codes only); ignored")
7402 for f
in args.get(
"ignored_files")
or []:
7403 warns.append(f
"{f}: a positional file which is not used (not a .dem, .stim, .mtx, or .mmx file, or a third "
7404 f
"matrix file); ignored")
7405 if args.get(
"model_lb"):
7406 ign = [opt
for opt, on
in ((
"outC=", args.get(
"outC")
is not None), (
"finC=", args.get(
"finC")
is not None),
7407 (
"--cws", bool(args.get(
"do_cws"))
and args.get(
"outC")
is None),
7408 (
"drop=", args.get(
"drop")
is not None),
7409 (
"outD=", args.get(
"outD")
is not None),
7410 (
"--out-reduced", args.get(
"out_reduced")
is not None),
7411 (
"--out-dir", args.get(
"out_dir")
is not None),
7412 (
"--out-dem", args.get(
"out_dem")
is not None),
7413 (
"--out-stim", args.get(
"out_stim")
is not None),
7414 (
"start=", args.get(
"start")
is not None), (
"dmin=", bool(args.get(
"dmin"))),
7415 (
"dmax=", bool(args.get(
"dmax"))), (
"dexp=", bool(args.get(
"dexp"))),
7416 (
"maxC=", bool(args.get(
"maxC"))), (
"dW=", args.get(
"dW", -1) >= 0),
7417 (
"merge=", args.get(
"merge")
is not None),
7418 (
"expand=", args.get(
"expand")
is not None),
7419 (
"wmin=", args.get(
"wmin", 1) != 1), (
"smax=", args.get(
"smax")
is not None),
7420 (
"noscan=", bool(args.get(
"noscan"))), (
"cbeg=", args.get(
"cbeg")
is not None),
7421 (
"cend=", args.get(
"cend")
is not None),
7422 (
"--trust-start", bool(args.get(
"trust_start"))),
7423 (
"start_scan=", bool(args.get(
"start_scan"))),
7424 (
"chunk_size=", bool(args.get(
"chunk_size"))), (
"ksub=", bool(args.get(
"ksub"))),
7425 (
"kwin=", bool(args.get(
"kwin"))), (
"win_mode=", bool(args.get(
"win_mode"))),
7426 (
"win_hyb=", bool(args.get(
"win_hyb"))),
7427 (
"min_hits=", args.get(
"min_hits")
is not None),
7428 (
"cov_cws=", args.get(
"cov_cws", 100) != 100),
7429 (
"refresh=", args.get(
"refresh")
is not None),
7430 (
"solver=", args.get(
"solver")
not in (
None,
"dist_m4ri")),
7431 (
"engine=", args.get(
"engine")
not in (
None,
"dist_m4ri")))
if on]
7433 warns.append(f
"{', '.join(ign)}: not used with --model-lb; ignored")
7435 if args.get(
"out_model")
is not None:
7436 warns.append(
"--out-model: only used with --model-lb; ignored")
7437 if args.get(
"model_minority"):
7438 warns.append(
"--model-minority: only used with --model-lb; ignored")
7443def main(argv: Optional[List[str]] =
None) -> int:
7444 """The CLI (see print_cli_help()); with --silent, set_silent() is on during the run, and restored at its end."""
7445 report = _report_ignored
7452def _main(argv: Optional[List[str]] =
None) -> int:
7458 except ValueError
as e:
7459 sys.stderr.write(f
"Error: {e}\n")
7461 if args.get(
"silent"):
7464 if args.get(
"version"):
7465 print(f
"dist_m4ri.py version {__version__}")
7467 if bin_ver
is not None:
7468 print(f
"dist_m4ri version {bin_ver} (binary '{bin_path}')")
7471 print(f
"dist_m4ri.py: {compat_warn}", file=sys.stderr)
7474 if args.get(
"morehelp")
or args.get(
"help"):
7477 print(f
"dist_m4ri.py: {compat_warn}", file=sys.stderr)
7478 if args.get(
"morehelp"):
7483 if args.get(
"unrecognized"):
7484 for u
in args[
"unrecognized"]:
7485 print(f
"dist_m4ri.py: unrecognized parameter '{u}'", file=sys.stderr)
7490 not args.get(
"fdem")
and not args.get(
"finH")
7491 and not args.get(
"Hx")
and not args.get(
"Hz")
and not args.get(
"fin")
7493 print(
"dist_m4ri.py: no input matrix or model specified", file=sys.stderr)
7502 cache_file = args[
"cache_file"]
if args[
"use_cache"]
else None
7503 if not args[
"use_cache"]:
7507 if args.get(
"model_lb"):
7508 if not args[
"fdem"]
or not str(args[
"fdem"]).endswith((
".stim",
".dem")):
7509 raise ValueError(
"--model-lb needs a Stim circuit (.stim) or DEM (.dem) (fdem=FILE)")
7511 circuit=args[
"fdem"],
7512 method=args[
"method"],
7513 threads=args[
"threads"],
7514 timeout=args[
"timeout"],
7515 num_steps=args[
"steps"],
7516 dstop=args[
"dstop"],
7518 minority=args[
"model_minority"],
7520 rounds=args[
"rounds"],
7521 p_noise=args[
"p_noise"]
if args[
"p_noise"]
is not None else 0.001,
7522 force_noise=args[
"force_noise"],
7523 out_model=args[
"out_model"],
7524 cache_file=cache_file,
7526 debug=args[
"debug"],
7527 verbose=args[
"verbose"],
7528 nothrottle=args[
"nothrottle"],
7529 pairs=args[
"pairs"],
7530 sort_rows=args[
"sort_rows"],
7531 cc_prune=args[
"cc_prune"],
7535 print(
"=== Code-Sized Model Lower Bound ===")
7536 print(f
" Model: {info.get('rows', 0)} rows ({info.get('rows_measured', 0)} chains of the "
7537 f
"measured-basis checks, {info.get('rows', 0) - info.get('rows_measured', 0)} other) x "
7538 f
"{info.get('cols', 0)} columns, from {info.get('dem_cols', 0)} error mechanisms and "
7539 f
"{info.get('detectors', 0)} detectors of the DEM.")
7540 if info[
"model_d_info"]:
7541 print(f
" Model bounds (CC certifies dmin): {format_bounds_str(info['model_d_info'])}"
7542 + (f
"; measured-basis rows only: {format_bounds_str(info['measured_d_info'])}"
7543 if info[
"measured_d_info"]
and info[
"measured_d_info"] != info[
"model_d_info"]
else ""))
7544 print(f
" Certified lower bound on the circuit distance: d >= {bound}.")
7545 print(f
" Circuit bounds (with the cache): {format_bounds_str(info['d_info'])}")
7552 method=args[
"method"],
7553 threads=args[
"threads"],
7554 timeout=args[
"timeout"],
7555 num_steps=args[
"steps"],
7559 dstop=args[
"dstop"],
7563 start=args[
"start"],
7566 noscan=args[
"noscan"],
7570 merge=args[
"merge"]
is not False,
7571 expand=bool(args[
"expand"]),
7574 out_reduced=args[
"out_reduced"],
7577 do_cws=args[
"do_cws"]
or (args[
"outC"]
is not None),
7578 cache_file=cache_file,
7579 solver=args[
"solver"],
7580 engine=args[
"engine"],
7582 debug=args[
"debug"],
7583 verbose=args[
"verbose"],
7584 nothrottle=args[
"nothrottle"],
7585 chunk_size=args[
"chunk_size"],
7588 win_mode=args[
"win_mode"],
7589 win_hyb=args[
"win_hyb"],
7590 pairs=args[
"pairs"],
7591 sort_rows=args[
"sort_rows"],
7592 cc_prune=args[
"cc_prune"],
7593 min_hits=args[
"min_hits"],
7594 cov_cws=args[
"cov_cws"],
7595 refresh=args[
"refresh"],
7596 basis=args[
"basis"],
7597 rounds=args[
"rounds"],
7598 p_noise=args[
"p_noise"]
if args[
"p_noise"]
is not None else 0.001,
7599 force_noise=args[
"force_noise"],
7600 out_dir=args[
"out_dir"],
7601 out_dem=args[
"out_dem"],
7602 out_stim=args[
"out_stim"],
7603 trust_start=args[
"trust_start"],
7604 start_scan=args[
"start_scan"]
7606 dist, d_info = res[0], res[1]
7609 print(
"=== DEM Distance Results ===")
7611 d_info, method=args[
"method"], label=
"DEM", stats=_last_run_stats, dstop=args[
"dstop"]
7613 print(f
" Summary bounds: {format_bounds_str(d_info)}")
7617 if args.get(
"rounds")
is not None:
7619 "--rounds option is only supported for Stim circuit (.stim) inputs."
7622 if args[
"Hx"]
is not None or args[
"Hz"]
is not None:
7628 method=args[
"method"],
7629 threads=args[
"threads"],
7630 timeout=args[
"timeout"],
7631 num_steps=args[
"steps"],
7635 dstop=args[
"dstop"],
7639 start=args[
"start"],
7642 noscan=args[
"noscan"],
7647 do_cws=args[
"do_cws"]
or (args[
"outC"]
is not None),
7648 cache_file=cache_file,
7649 solver=args[
"solver"],
7650 engine=args[
"engine"],
7652 debug=args[
"debug"],
7653 verbose=args[
"verbose"],
7654 nothrottle=args[
"nothrottle"],
7655 chunk_size=args[
"chunk_size"],
7658 win_mode=args[
"win_mode"],
7659 win_hyb=args[
"win_hyb"],
7660 pairs=args[
"pairs"],
7661 sort_rows=args[
"sort_rows"],
7662 cc_prune=args[
"cc_prune"],
7663 min_hits=args[
"min_hits"],
7664 cov_cws=args[
"cov_cws"],
7665 refresh=args[
"refresh"],
7666 trust_start=args[
"trust_start"],
7667 start_scan=args[
"start_scan"],
7670 dist, dx_info, dz_info = res[0], res[1], res[2]
7674 _, d_lo, d_hi, _ =
_css_combine(tuple(dx_info), tuple(dz_info), args[
"dmax"])
7675 exact_tag =
" (exact)" if (d_lo > 0
and d_lo == d_hi)
else ""
7676 stops = [v
for v
in (args[
"dstop"], args[
"dmax"])
if v > 0]
7677 sector_stop = min(stops)
if stops
else 0
7680 print(
"=== CSS Quantum Code Distance Results ===")
7682 print(
"--- X-Component Distance (dX) ---")
7684 dx_info, method=args[
"method"], label=
"dX",
7685 stats=_last_css_stats.get(
"X"), dstop=sector_stop
7687 print(f
" dX bounds: {format_bounds_str(dx_info)}")
7689 print(
"--- Z-Component Distance (dZ) ---")
7691 dz_info, method=args[
"method"], label=
"dZ",
7692 stats=_last_css_stats.get(
"Z"), dstop=sector_stop
7694 print(f
" dZ bounds: {format_bounds_str(dz_info)}")
7695 print(
"--- Overall CSS Code Distance ---")
7696 print(f
" d = min(dX, dZ) = {dist}{exact_tag}")
7700 print(f
"dX: {dx_str} dZ: {dz_str} (d = {dist}){exact_tag}")
7707 if finH
is None: finH = f
"{args['fin']}X.mtx"
7708 if finG
is None and args[
"finL"]
is None: finG = f
"{args['fin']}Z.mtx"
7711 if finH
and (finG
is not None or args[
"finL"]
is not None or args[
"classical"] == 0):
7716 method=args[
"method"],
7717 threads=args[
"threads"],
7718 timeout=args[
"timeout"],
7719 num_steps=args[
"steps"],
7723 dstop=args[
"dstop"],
7727 start=args[
"start"],
7730 noscan=args[
"noscan"],
7735 do_cws=args[
"do_cws"]
or (args[
"outC"]
is not None),
7737 cache_file=cache_file,
7738 solver=args[
"solver"],
7739 engine=args[
"engine"],
7741 debug=args[
"debug"],
7742 verbose=args[
"verbose"],
7743 nothrottle=args[
"nothrottle"],
7744 chunk_size=args[
"chunk_size"],
7747 win_mode=args[
"win_mode"],
7748 win_hyb=args[
"win_hyb"],
7749 pairs=args[
"pairs"],
7750 sort_rows=args[
"sort_rows"],
7751 cc_prune=args[
"cc_prune"],
7752 min_hits=args[
"min_hits"],
7753 cov_cws=args[
"cov_cws"],
7754 refresh=args[
"refresh"],
7755 trust_start=args[
"trust_start"],
7756 start_scan=args[
"start_scan"],
7759 dist, d_info = res[0], res[1]
7762 print(
"=== Quantum Code Distance Results (Single-Sided) ===")
7764 d_info, method=args[
"method"], label=
"Quantum", stats=_last_run_stats, dstop=args[
"dstop"]
7766 print(f
" Summary bounds: {format_bounds_str(d_info)}")
7774 method=args[
"method"],
7775 threads=args[
"threads"],
7776 timeout=args[
"timeout"],
7777 num_steps=args[
"steps"],
7781 dstop=args[
"dstop"],
7785 start=args[
"start"],
7788 noscan=args[
"noscan"],
7793 do_cws=args[
"do_cws"]
or (args[
"outC"]
is not None),
7795 cache_file=cache_file,
7796 solver=args[
"solver"],
7797 engine=args[
"engine"],
7799 debug=args[
"debug"],
7800 verbose=args[
"verbose"],
7801 nothrottle=args[
"nothrottle"],
7802 chunk_size=args[
"chunk_size"],
7805 win_mode=args[
"win_mode"],
7806 win_hyb=args[
"win_hyb"],
7807 pairs=args[
"pairs"],
7808 sort_rows=args[
"sort_rows"],
7809 cc_prune=args[
"cc_prune"],
7810 min_hits=args[
"min_hits"],
7811 cov_cws=args[
"cov_cws"],
7812 refresh=args[
"refresh"],
7813 trust_start=args[
"trust_start"],
7814 start_scan=args[
"start_scan"]
7816 dist, d_info = res[0], res[1]
7819 print(
"=== Classical Code Distance Results ===")
7821 d_info, method=args[
"method"], label=
"Classical", stats=_last_run_stats, dstop=args[
"dstop"]
7823 print(f
" Summary bounds: {format_bounds_str(d_info)}")
7826 except ValueError
as e:
7827 sys.stderr.write(f
"Error: {e}\n")
7833if __name__ ==
"__main__":
bool __eq__(self, Any other)
__init__(self, int dmin, int dmax, int rw_steps=0, Optional[List[List[int]]] cws=None, Optional[List[List[int]]] cws_X=None, Optional[List[List[int]]] cws_Z=None, Optional[int] dmin_X=None, Optional[int] dmax_X=None, Optional[int] rw_steps_X=None, Optional[int] dmin_Z=None, Optional[int] dmax_Z=None, Optional[int] rw_steps_Z=None)
__getitem__(self, int index)
Dict[str, Any] _cert_code_distance(Callable[[], str] key_fn, Callable[[int, int], Any] run_cert_fn, *, str label="code", int dmin=0, int dmax=0, int dstop=0, int wmax=0, Optional[str] eff_cache_file=None, int debug=0, bool verbose=False)
str _format_start_list(List[int] start_list, int max_show=8)
str _help_name_key(str name)
str find_dist_m4ri_binary(Optional[str] custom_path=None)
int main(Optional[List[str]] argv=None)
str create_unique_file(Union[str, Path] directory="tmp", str extension=".tmp")
str format_bounds_str(List[int] bounds)
List[int] format_bounds_list(int dmin, int dmax, int num_rw)
int parse_debug_value(Union[str, int] val)
Any __getattr__(str name)
None _write_mtx_gf2(str path, Any matrix)
str detect_basis(Any circuit, Optional[Union[str, Path]] filepath=None, Optional[str] basis_arg=None)
Tuple[List[int], Optional[int]] read_drop_file(Union[str, Path] path)
bool has_repeat_block(Any circuit)
Tuple[str, Optional[Dict[str, Any]], Optional[Dict[str, Any]]] _resolve_start_cache(str plain_key, Optional[List[int]] start_list, Callable[[Dict[str, Any]], bool] is_hit, bool trust_start, Callable[[Dict[str, Any]], bool] merge_copy, Optional[str] eff_cache_file, bool verbose=False)
Any strip_noise(Any circuit)
None _warn_disabled_options(Any noscan=0, Any cbeg=None, Any cend=None)
None set_distance_cache_file(Optional[Union[str, Path]] filepath=None)
str _matrix_cache_key(Any H, Optional[Any] G=None, Optional[Any] L=None, bool classical=False)
Dict[str, Any] get_distance_cache()
None _warn_cli_conflicts(Dict[str, Any] args)
int count_circuit_rounds(Any circuit)
int _dist_from_bounds(int dmin, int dmax)
Tuple[List[int], List[bool], int] _model_detector_groups(Any circuit)
bool _cert_is_conditional(Any cert)
str _matrix_to_file(matrix, str extension=".mtx", str temp_dir="tmp")
bool _can_delegate_to_cert(str mode, *, int method=3, bool do_cws=False, Optional[str] outC=None, Optional[str] finC=None, Optional[int] smax=None, int dW=-1, int maxC=0, Optional[List[int]] start_list=None, Optional[Any] outD=None, Optional[Any] out_reduced=None, float pmin=0.0)
None _warn_ortho(Any ortho, bool has_g, str solver="dist_m4ri")
Tuple[Optional[str], Optional[str], Optional[str]] _binary_version(Optional[str] binary_path=None)
Tuple[int,...] _parse_version(str v_str)
None _write_nzlist_file(str filepath, List[List[int]] cws)
Dict[str, Any] parse_cli_args(List[str] argv)
Tuple[Any, int] remove_empty_detectors(Any circuit)
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)
None _warn_disabled_simple_full(Optional[bool] simple=None, Optional[bool] full=None, bool cli=False)
Tuple[int, int, Any, Any] _gf2_entries(Any matrix)
None _write_dem_file(Any dem, str path)
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)
List[Tuple[str, List[str], Dict[str, str]]] _help_entries(str text)
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)
Optional[List[int]] _scan_start_list(Optional[List[int]] start_list, bool start_scan)
bool _merge_start_into_plain(str plain_key, Dict[str, Any] src, bool trust=False, Optional[Dict[str, int]] rw_delta=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)
Dict[str, Any] verify_qubit_metadata(Any circuit, bool verbose=False, Optional[str] basis_arg=None, Optional[Union[str, Path]] filepath=None)
Optional[List[int]] _normalize_start_list(Any start)
Tuple[int, int, int] parse_dist_m4ri_output(str stdout)
List[List[int]] _union_cws(Optional[List[List[int]]] base, Optional[List[List[int]]] extra)
str _ksub_warning_text(int ksub)
bool _migrate_css_records(Any Hx, Any Hz, Optional[Any] Lx=None, Optional[Any] Lz=None, Optional[str] eff_cache_file=None)
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)
Dict[str, Any] _sector_record(int dmin, int dmax, int rw_steps, List[List[int]] cws)
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)
None _check_kwargs(Callable[..., Any] func, Dict[str, Any] kwargs, Sequence[str] aliases=("win",))
bool _help_group_key_match(str gkey, str key)
str _start_key_suffix(Optional[List[int]] start_list)
Dict[str, Any] _model_matrices(List[Tuple[Any,...]] cols, List[int] group, List[bool] use, int num_observables)
Tuple[Any, bool, bool] _circuit_dem(Any circuit)
None _warn_experimental_ksub(Any ksub, int method, str solver="dist_m4ri")
str explain_bounds(List[int] bounds, Optional[int] method=None, str label="", Optional[Dict[str, Any]] stats=None, int dstop=0)
None clear_distance_cache(Optional[Union[str, Path]] cache_file=None, bool clear_file=False)
str _reduced_out_prefix(Union[bool, str, Path] spec, str src, Optional[Union[str, Path]] out_dir=None)
str _dem_cache_key(Any dem, float pmin=0.0)
None _warn_ignored(str msg)
List[List[int]] read_sparse_vectors(str filepath)
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)
int _main(Optional[List[str]] argv=None)
Tuple[List[int], List[bool], int, int, int] _dem_detector_groups(Any dem, float pmin=0.0)
Any _add_noise_recursive(Any circuit, float p, int num_qubits, Set[int] active_qubits)
str get_sparse_array_state(A)
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)
bool _single_entry_is_exact(Dict[str, Any] entry, bool need_cws=False, int dstop=0)
List[List[int]] _expand_equal_columns(List[List[int]] cws, Any dem, float pmin=0.0, int maxC=0, bool verbose=False)
Optional[str] _resolve_stim_dem_out_path(Optional[Union[bool, str, Path]] out_spec, str circuit_src, str mode_tag, str ext, Optional[Union[str, Path]] out_dir=None)
None _warn_codedistance_ignored(Dict[str, Any] values)
Optional[str] check_binary_compatibility(Optional[str] binary_path=None)
Any _cert_to_csr(Any M, Any qec_io)
str _ortho_warning_text()
Dict[str, Any] _parse_stderr_stats(str stderr)
None enable_distance_cache()
float rw_infoset_find_prob(int n, int rank, int w, int pairs_l=0)
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 print_cli_short_help(Optional[Any] file=None)
None print_cli_help(Optional[Any] file=None)
None save_distance_cache(Optional[Union[str, Path]] filepath=None)
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)
bool _cert_wmin_discharged(Any cert, int certified_dmin)
Any _rewrite_circuit_internal(Any circ, Dict[int, int] phys_to_log, Dict[int, int] active_gates_count)
Any set_circuit_rounds(Any circuit, int rounds)
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)
int print_cli_morehelp(Optional[Any] file=None, Optional[str] topic=None)
Optional[Set[str]] _help_group_members(Tuple[str, List[str], Dict[str, str]] entry)
Optional[str] _split_css_filename(Optional[str] filepath, str sector)
Tuple[int, int, int, int] _css_combine(Tuple[int, int, int] x, Tuple[int, int, int] z, int dmax=0)
str _start_warning_text(List[int] start_list)
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)
str _resolve_cert_engine(str solver="dist_m4ri", Optional[str] engine=None)
Tuple[int, int] _cert_extract_bounds(Any cert, int certified_dmin=1)
List[List[int]] track_circuit_carriers(Any circuit)
Dict[str, Tuple[Any, Any, Any]] _css_sector_codes(Any Hx, Any Hz, Optional[Any] Lx, Optional[Any] Lz)
str _format_duration(float t)
Optional[List[int]] _prepare_start_option(Any start, bool trust_start=False, Any noscan=0, Any cbeg=None, Any cend=None, str solver="dist_m4ri", bool start_scan=False)
None disable_distance_cache()
Tuple[int, int] _seed_bounds_from_entry(int eff_dmin, int eff_dmax, Optional[Dict[str, Any]] entry)
Dict[str, Any] _cached_code_distance(Any H, Optional[Any] G, Optional[Any] L, bool classical, str kind, Optional[str] dist_m4ri, int method, Optional[int] threads, float timeout, Optional[int] num_steps, int d_exp, int dmin, int dmax, int dstop, int wmin, int wmax, Optional[int] smax, Optional[List[int]] start_list, int dW, int maxC, Optional[str] finC, Optional[str] outC, bool need_cws, Optional[str] eff_cache_file, int seed, int debug, bool verbose, bool nothrottle, int chunk_size, int ksub, int kwin, int win_mode, Optional[int] min_hits, int cov_cws, Optional[int] refresh, bool trust_start, Optional[Any] fdem=None, float pmin=0.0, bool merge=True, Optional[Callable[[], str]] key_fn=None, Optional[Callable[[List[List[int]]], List[List[int]]]] cws_out_fn=None, Optional[Union[str, Path, List[int]]] fdrop=None, Optional[str] outD=None, Optional[bool] sort_rows=None, int win_hyb=0, int pairs=-1, Optional[int] cc_prune=None, bool ortho=True)
None _remove_temp_files(Sequence[Optional[str]] files, int debug=0)
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)
Tuple[Optional[str], Optional[str]] _css_sector_finc(Optional[str] finC, Optional[str] outC)