Dist m4ri 0.0.1.alpha
Computing distance of a classical or quantum CSS code
Loading...
Searching...
No Matches
dist_m4ri.py
Go to the documentation of this file.
1"""
2dist_m4ri.py: Python wrapper for the multithreaded dist_m4ri distance calculator.
3
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
10Supports distance caching, codeword export, and fallback/option for the codedistance library.
11Since dist_m4ri natively handles multithreading (via POSIX threads and dynamic bracketing),
12no Python-level threading or subprocess-per-core logic is necessary.
13"""
14
15import os
16import sys
17import json
18import time
19import random
20import shutil
21import hashlib
22import tempfile
23import threading
24import subprocess
25from pathlib import Path
26from typing import List, Tuple, Union, Optional, Dict, Any
27
28_codedistance_mod = None
29_stim_mod = None
30
31
33 """Lazily imports the codedistance library only when requested."""
34 global _codedistance_mod
35 if _codedistance_mod is None:
36 try:
37 import codedistance
38 _codedistance_mod = codedistance
39 except ImportError:
40 raise ImportError("codedistance library is requested but not installed.")
41 return _codedistance_mod
42
43
45 """Lazily imports stim only when requested."""
46 global _stim_mod
47 if _stim_mod is None:
48 try:
49 import stim
50 _stim_mod = stim
51 except ImportError:
52 raise ImportError("stim library is requested but not installed.")
53 return _stim_mod
54
55
56def __getattr__(name: str) -> Any:
57 """Lazily resolves module attributes without loading heavy dependencies at startup."""
58 if name == "_HAS_STIM":
59 try:
60 import stim
61 return True
62 except ImportError:
63 return False
64 if name == "_HAS_CODEDISTANCE":
65 try:
66 import codedistance
67 return True
68 except ImportError:
69 return False
70 raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
71
72# Global cache for distance results
73_distance_cache: Dict[str, Any] = {}
74_use_distance_cache: bool = True
75_distance_cache_file: Optional[str] = None
76
77# Aliases for backward compatibility with vecdec.py
78_css_distance_cache = _distance_cache
79_use_css_distance_cache = _use_distance_cache
80
81
82def set_distance_cache_file(filepath: Optional[Union[str, Path]] = None) -> None:
83 """
84 Sets the default JSON file for persistent distance caching.
85 If the file exists, its contents are loaded into memory.
86 """
87 global _distance_cache_file
88 if filepath is not None:
89 _distance_cache_file = str(Path(filepath).resolve())
90 load_distance_cache(_distance_cache_file)
91 else:
92 _distance_cache_file = None
93
94
95def load_distance_cache(filepath: Optional[Union[str, Path]] = None) -> Dict[str, Any]:
96 """
97 Loads distance cache from a JSON file into memory.
98 """
99 global _distance_cache, _distance_cache_file
100 target_file = str(Path(filepath).resolve()) if filepath is not None else _distance_cache_file
101 if target_file and os.path.isfile(target_file):
102 try:
103 with open(target_file, "r") as f:
104 data = json.load(f)
105 if isinstance(data, dict):
106 _distance_cache.update(data)
107 except Exception as e:
108 sys.stderr.write(f"# Warning: Failed to load distance cache from {target_file}: {e}\n")
109 return _distance_cache
110
111
112def save_distance_cache(filepath: Optional[Union[str, Path]] = None) -> None:
113 """
114 Saves the in-memory distance cache to a JSON file.
115 Uses atomic write via a temporary file to prevent corruption.
116 """
117 global _distance_cache, _distance_cache_file
118 target_file = str(Path(filepath).resolve()) if filepath is not None else _distance_cache_file
119 if not target_file:
120 return
121
122 parent_dir = os.path.dirname(os.path.abspath(target_file)) or "."
123 os.makedirs(parent_dir, exist_ok=True)
124 fd, temp_path = tempfile.mkstemp(suffix=".tmp", prefix="dist_cache_", dir=parent_dir)
125 try:
126 with open(fd, "w") as f:
127 json.dump(_distance_cache, f, indent=2)
128 os.replace(temp_path, target_file)
129 except Exception as e:
130 if os.path.exists(temp_path):
131 try: os.remove(temp_path)
132 except OSError: pass
133 sys.stderr.write(f"# Warning: Failed to save distance cache to {target_file}: {e}\n")
134
135
136def clear_distance_cache(cache_file: Optional[Union[str, Path]] = None, clear_file: bool = False) -> None:
137 """
138 Clears all cached distance calculations from memory, and optionally deletes the persistent JSON file.
139 """
140 global _distance_cache, _distance_cache_file
141 _distance_cache.clear()
142 target_file = str(Path(cache_file).resolve()) if cache_file is not None else _distance_cache_file
143 if clear_file and target_file and os.path.isfile(target_file):
144 try:
145 os.remove(target_file)
146 except OSError:
147 pass
148
149
151 """Enables distance caching."""
152 global _use_distance_cache
153 _use_distance_cache = True
154
155
157 """Disables distance caching."""
158 global _use_distance_cache
159 _use_distance_cache = False
160
161
162def get_distance_cache() -> Dict[str, Any]:
163 """Returns the global distance cache dictionary."""
164 global _distance_cache
165 return _distance_cache
166
167
168def format_bounds_list(dmin: int, dmax: int, num_rw: int) -> List[int]:
169 """
170 Returns [dmin, dmax, num_rw] according to:
171 - [d, d, 0] if known exactly
172 - [dmin, 0, 0] if there is no upper bound (dmax == 0)
173 - [0, dmax, num_rw] if there is no lower bound (dmin <= 1)
174 - [dmin, dmax, num_rw] otherwise
175 """
176 eff_dmin = dmin if dmin > 1 else 0
177 eff_dmax = dmax if dmax > 0 else 0
178 eff_rw = num_rw if num_rw > 0 else 0
179 if eff_dmin > 0 and eff_dmin == eff_dmax:
180 return [eff_dmin, eff_dmax, 0]
181 elif eff_dmax == 0:
182 return [eff_dmin, 0, 0]
183 elif eff_dmin == 0:
184 return [0, eff_dmax, eff_rw]
185 else:
186 return [eff_dmin, eff_dmax, eff_rw]
187
188
189def format_bounds_str(bounds: List[int]) -> str:
190 """Formats a bounds list [dmin, dmax, num_rw] as a string, stating '(exact)' if bounds coincide."""
191 dmin, dmax, num_rw = bounds[0], bounds[1], bounds[2]
192 if dmin > 0 and dmin == dmax:
193 return f"{dmin} {dmax} {num_rw} (exact)"
194 return f"{dmin} {dmax} {num_rw}"
195
196
197def explain_bounds(bounds: List[int], method: Optional[int] = None, label: str = "") -> str:
198 """
199 Returns a human-readable explanation of [dmin, dmax, num_rw] following README.md.
200
201 Args:
202 bounds: [dmin, dmax, rw_steps] list.
203 method: Optional solver method (1=RW, 2=CC, 3=Bracketing).
204 label: Optional prefix/label (e.g. "dX", "dZ", "").
205
206 Returns:
207 Multi-line formatted explanation string.
208 """
209 dmin, dmax, num_rw = bounds[0], bounds[1], bounds[2]
210 lines = []
211 prefix = f"{label} " if label else ""
212
213 # Lower bound explanation
214 if dmin > 0 and dmin == dmax:
215 lines.append(f" {prefix}Lower bound (dmin = {dmin}): Exact distance certified (dmin == dmax == {dmin}).")
216 elif dmin > 1:
217 lines.append(f" {prefix}Lower bound (dmin = {dmin}): All cluster weights w <= {dmin - 1} were exhaustively analyzed by CC without finding any non-trivial codewords.")
218 else:
219 lines.append(f" {prefix}Lower bound (dmin = {dmin}): No non-trivial lower bound certified (dmin <= 1).")
220
221 # Upper bound explanation
222 if dmax > 0:
223 lines.append(f" {prefix}Upper bound (dmax = {dmax}): Weight of the smallest non-trivial codeword discovered.")
224 else:
225 lines.append(f" {prefix}Upper bound (dmax = {dmax}): No non-trivial codeword discovered yet (dmax = 0).")
226
227 # RW steps explanation (and why it is zero if num_rw == 0)
228 if num_rw > 0:
229 lines.append(f" {prefix}Random window steps (rw_steps = {num_rw}): {num_rw} completed random information set searches across worker threads.")
230 else:
231 if dmin > 0 and dmin == dmax:
232 lines.append(f" {prefix}Random window steps (rw_steps = 0): Set to 0 because the exact distance d = {dmin} was proven by Connected Cluster search or certified bounds coincided.")
233 elif method == 2:
234 lines.append(f" {prefix}Random window steps (rw_steps = 0): Set to 0 because Method 2 (Connected Cluster) is an exhaustive search that does not perform random information set (RW) sampling.")
235 else:
236 lines.append(f" {prefix}Random window steps (rw_steps = 0): 0 completed random information set steps.")
237
238 return "\n".join(lines)
239
240
242 H: Optional[Any] = None,
243 G: Optional[Any] = None,
244 L: Optional[Any] = None,
245 Hx: Optional[Any] = None,
246 Hz: Optional[Any] = None,
247 Lx: Optional[Any] = None,
248 Lz: Optional[Any] = None,
249 dem: Optional[Any] = None,
250 circuit: Optional[Any] = None,
251 pmin: float = 0.0,
252 cache_file: Optional[Union[str, Path]] = None
253) -> Optional[Dict[str, Any]]:
254 """
255 Retrieves the cached distance entry (including bounds and cumulative rw_steps)
256 for a given code matrix, CSS code, or DEM.
257
258 Returns:
259 dict with keys {"dist", "dmin", "dmax", "rw_steps", ...} or None if not cached.
260 """
261 global _distance_cache, _distance_cache_file
262 eff_cache_file = str(Path(cache_file).resolve()) if cache_file is not None else _distance_cache_file
263 if eff_cache_file:
264 load_distance_cache(eff_cache_file)
265
266 if H is not None:
267 if G is not None:
268 key = f"quantum:H={get_sparse_array_state(H)}:G={get_sparse_array_state(G)}"
269 elif L is not None:
270 key = f"quantum:H={get_sparse_array_state(H)}:L={get_sparse_array_state(L)}"
271 else:
272 key = f"classical:{get_sparse_array_state(H)}"
273 entry = _distance_cache.get(key)
274 if entry:
275 entry = dict(entry)
276 entry["d_info"] = format_bounds_list(entry.get("dmin", 0), entry.get("dmax", 0), entry.get("rw_steps", 0))
277 return entry
278 elif Hx is not None or Hz is not None:
279 hx_st = get_sparse_array_state(Hx) if Hx is not None else "none"
280 hz_st = get_sparse_array_state(Hz) if Hz is not None else "none"
281 key = f"css:X={hx_st}:Z={hz_st}"
282 if Lx is not None or Lz is not None:
283 lx_st = get_sparse_array_state(Lx) if Lx is not None else "none"
284 lz_st = get_sparse_array_state(Lz) if Lz is not None else "none"
285 key = f"{key}:Lx={lx_st}:Lz={lz_st}"
286 entry = _distance_cache.get(key)
287 if entry:
288 entry = dict(entry)
289 if "dmin_X" in entry:
290 entry["dX"] = format_bounds_list(entry.get("dmin_X", 0), entry.get("dmax_X", 0), entry.get("rw_steps_X", 0))
291 if "dmin_Z" in entry:
292 entry["dZ"] = format_bounds_list(entry.get("dmin_Z", 0), entry.get("dmax_Z", 0), entry.get("rw_steps_Z", 0))
293 return entry
294 elif dem is not None or circuit is not None:
295 if dem is None and circuit is not None:
296 if hasattr(circuit, 'detector_error_model'):
297 obj = circuit.detector_error_model(decompose_errors=True)
298 else:
299 obj = circuit
300 else:
301 obj = dem
302 dem_st = get_sparse_array_state(obj)
303 key = f"dem:{dem_st}" if pmin <= 0.0 else f"dem:{dem_st}:pmin={pmin}"
304 entry = _distance_cache.get(key)
305 if entry:
306 entry = dict(entry)
307 entry["d_info"] = format_bounds_list(entry.get("dmin", 0), entry.get("dmax", 0), entry.get("rw_steps", 0))
308 return entry
309 return None
310
311
312# Backward-compatibility aliases
313clear_css_distance_cache = clear_distance_cache
314enable_css_distance_cache = enable_distance_cache
315disable_css_distance_cache = disable_distance_cache
316
317
319 """Returns a deterministic string representation for JSON-compatible cache keys."""
320 if A is None:
321 return "none"
322 if isinstance(A, (str, Path)):
323 path_str = str(Path(A).resolve())
324 if os.path.isfile(path_str):
325 try:
326 with open(path_str, "rb") as f:
327 content_h = hashlib.sha256(f.read()).hexdigest()
328 return f"file:{path_str}:{content_h}"
329 except Exception:
330 return f"file:{path_str}"
331 return path_str
332 if hasattr(A, 'shape') and hasattr(A, 'dtype') and hasattr(A, 'tobytes'):
333 h = hashlib.sha256(A.tobytes()).hexdigest()
334 dtype_str = getattr(A.dtype, 'str', str(A.dtype))
335 return f"ndarray:{A.shape}:{dtype_str}:{h}"
336 if hasattr(A, 'tocsr'):
337 csr = A.tocsr()
338 h = hashlib.sha256(csr.data.tobytes() + csr.indices.tobytes() + csr.indptr.tobytes()).hexdigest()
339 return f"csr:{csr.shape}:{h}"
340 if hasattr(A, 'tobytes'):
341 h = hashlib.sha256(A.tobytes()).hexdigest()
342 return f"bytes:{h}"
343 h = hashlib.sha256(str(A).encode('utf-8')).hexdigest()
344 return f"str_sha256:{h}"
345
346
347def create_unique_file(directory: Union[str, Path] = "tmp", extension: str = ".tmp") -> str:
348 """Creates a unique temporary file path and ensures the parent directory exists."""
349 os.makedirs(directory, exist_ok=True)
350 fd, path = tempfile.mkstemp(suffix=extension, dir=directory)
351 os.close(fd)
352 return path
353
354
355def read_sparse_vectors(filepath: str) -> List[List[int]]:
356 """
357 Reads a list of sparse vectors from a text file in NZLIST format,
358 converting from 1-based indexing (in the file) to 0-based indexing (in Python).
359
360 Args:
361 filepath (str): The path to the text file.
362
363 Returns:
364 list of list of int: A list where each element is a 0-based sparse vector.
365 """
366 sparse_vectors = []
367 if not os.path.exists(filepath) or os.path.getsize(filepath) == 0:
368 return sparse_vectors
369
370 with open(filepath, 'r') as f:
371 first_line = f.readline().strip()
372 if not first_line:
373 return sparse_vectors
374 if first_line != '%% NZLIST':
375 raise ValueError(f"Invalid file format in {filepath}: Missing '%% NZLIST' header.")
376
377 for line_num, line in enumerate(f, start=2):
378 line = line.strip()
379 if not line or line.startswith('%'):
380 continue
381 try:
382 parts = list(map(int, line.split()))
383 except ValueError:
384 raise ValueError(f"Non-integer data found on line {line_num}: {line}")
385
386 stated_length = parts[0]
387 vector_elements = [x - 1 for x in parts[1:]]
388 if len(vector_elements) != stated_length:
389 raise ValueError(
390 f"Length mismatch on line {line_num}. "
391 f"Expected {stated_length} elements, but found {len(vector_elements)}."
392 )
393 sparse_vectors.append(vector_elements)
394
395 return sparse_vectors
396
397
398def find_dist_m4ri_binary(custom_path: Optional[str] = None) -> str:
399 """Finds the dist_m4ri executable."""
400 if custom_path and os.path.isfile(custom_path) and os.access(custom_path, os.X_OK):
401 return os.path.abspath(custom_path)
402
403 pkg_dir = os.path.dirname(os.path.abspath(__file__))
404 candidates = [
405 os.path.join(pkg_dir, "src", "dist_m4ri"),
406 os.path.join(pkg_dir, "dist_m4ri"),
407 os.path.join(pkg_dir, "bin", "dist_m4ri"),
408 os.path.join(pkg_dir, "..", "dist-m4ri", "src", "dist_m4ri"),
409 os.path.join(os.getcwd(), "src", "dist_m4ri"),
410 os.path.join(os.getcwd(), "dist_m4ri"),
411 os.path.join(os.getcwd(), "bin", "dist_m4ri"),
412 ]
413
414 for cand in candidates:
415 if os.path.isfile(cand) and os.access(cand, os.X_OK):
416 return os.path.abspath(cand)
417
418 which_path = shutil.which("dist_m4ri")
419 if which_path:
420 return which_path
421
422 raise FileNotFoundError(
423 "Could not find executable 'dist_m4ri'. Please run 'make -C src' to build it."
424 )
425
426
427def parse_dist_m4ri_output(stdout: str) -> Tuple[int, int, int]:
428 """
429 Parses the standard output of dist_m4ri.
430 Expected format on stdout: "dmin dmax rw_steps", "dmin dmax", or a single integer.
431
432 Returns:
433 tuple (dmin, dmax, rw_steps)
434 """
435 lines = stdout.strip().split('\n')
436 for line in reversed(lines):
437 line = line.strip()
438 if not line or line.startswith('#'):
439 continue
440 parts = line.split()
441 if len(parts) >= 3:
442 try:
443 return int(parts[0]), int(parts[1]), int(parts[2])
444 except ValueError:
445 continue
446 elif len(parts) == 2:
447 try:
448 return int(parts[0]), int(parts[1]), 0
449 except ValueError:
450 continue
451 elif len(parts) == 1:
452 try:
453 val = int(parts[0])
454 return val, val, 0
455 except ValueError:
456 continue
457
458 raise RuntimeError(f"Could not parse dist_m4ri output: {stdout}")
459
460
462 """
463 Structured result for code distance calculations containing:
464 - dmin: lower bound on distance
465 - dmax: upper bound on distance (minimum non-trivial codeword weight found)
466 - rw_steps: cumulative number of completed random window information sets
467 - cws: discovered codewords (if requested)
468 """
470 self,
471 dmin: int,
472 dmax: int,
473 rw_steps: int = 0,
474 cws: Optional[List[List[int]]] = None,
475 cws_X: Optional[List[List[int]]] = None,
476 cws_Z: Optional[List[List[int]]] = None,
477 dmin_X: Optional[int] = None,
478 dmax_X: Optional[int] = None,
479 rw_steps_X: Optional[int] = None,
480 dmin_Z: Optional[int] = None,
481 dmax_Z: Optional[int] = None,
482 rw_steps_Z: Optional[int] = None,
483 ):
484 self.dmin = dmin
485 self.dmax = dmax
486 self.rw_steps = rw_steps
487 self.cws = cws
488 self.cws_X = cws_X
489 self.cws_Z = cws_Z
490 self.dmin_X = dmin_X
491 self.dmax_X = dmax_X
492 self.rw_steps_X = rw_steps_X
493 self.dmin_Z = dmin_Z
494 self.dmax_Z = dmax_Z
495 self.rw_steps_Z = rw_steps_Z
496
497 @property
498 def is_exact(self) -> bool:
499 return self.dmin > 0 and self.dmin == self.dmax
500
501 @property
502 def dist(self) -> int:
503 return self.dmin if self.is_exact else (self.dmax if self.dmax > 0 else self.dmin)
504
505 def __int__(self) -> int:
506 return self.distdist
507
508 def __index__(self) -> int:
509 return self.distdist
510
511 def __eq__(self, other: Any) -> bool:
512 if isinstance(other, DistanceResult):
513 return (self.dmin, self.dmax, self.rw_steps) == (other.dmin, other.dmax, other.rw_steps)
514 if isinstance(other, (tuple, list)):
515 return tuple(self) == tuple(other)
516 if isinstance(other, (int, np.integer)):
517 return self.distdist == other
518 return False
519
520 def __iter__(self):
521 if self.cws_X is not None or self.cws_Z is not None:
522 return iter((self.dmin, self.dmax, self.rw_steps, self.cws_X or [], self.cws_Z or []))
523 if self.cws is not None:
524 return iter((self.dmin, self.dmax, self.rw_steps, self.cws))
525 return iter((self.dmin, self.dmax, self.rw_steps))
526
527 def __getitem__(self, index: int):
528 return tuple(self)[index]
529
530 def __len__(self) -> int:
531 return len(tuple(self))
532
533 def __str__(self) -> str:
534 if self.is_exact:
535 return f"{self.dmin} {self.dmax} {self.rw_steps} (exact)"
536 return f"{self.dmin} {self.dmax} {self.rw_steps}"
537
538 def __repr__(self) -> str:
539 if self.is_exact:
540 return f"DistanceResult({self.dmin} {self.dmax} {self.rw_steps} (exact))"
541 return f"DistanceResult(dmin={self.dmin}, dmax={self.dmax}, rw_steps={self.rw_steps})"
542
543
544def check_finc_outc(finC: Optional[str], outC: Optional[str], verbose: bool = False) -> Optional[str]:
545 """
546 When finC and outC names are identical, an empty or non-existent file is silently ignored
547 (with a warning if verbose is True).
548
549 Returns:
550 The effective finC filepath to use (or None if ignored).
551 """
552 if not finC:
553 return None
554 if outC and (finC == outC or os.path.abspath(finC) == os.path.abspath(outC)):
555 if not os.path.exists(finC) or os.path.getsize(finC) == 0:
556 if verbose:
557 print(f"[dist_m4ri] Warning: finC='{finC}' (identical to outC) is empty or non-existent; silently ignoring input codewords.")
558 return None
559 return finC
560
561
563 dist_m4ri_path: Optional[str] = None,
564 method: int = 3,
565 finH: Optional[str] = None,
566 finG: Optional[str] = None,
567 finL: Optional[str] = None,
568 fin: Optional[str] = None,
569 finC: Optional[str] = None,
570 fdem: Optional[str] = None,
571 dmin: int = 0,
572 dmax: int = 0,
573 wmax: int = 0,
574 wmin: int = 1,
575 dexp: int = 0,
576 dest: int = 0,
577 steps: Optional[int] = None,
578 threads: Optional[int] = None,
579 timeout: float = 60.0,
580 smax: Optional[int] = None,
581 start: Optional[int] = None,
582 cbeg: Optional[int] = None,
583 cend: Optional[int] = None,
584 css: Optional[int] = None,
585 noscan: int = 0,
586 classical: int = -1,
587 dW: int = -1,
588 maxC: int = 0,
589 pmin: float = 0.0,
590 outC: Optional[str] = None,
591 seed: int = 0,
592 debug: int = 0,
593 stop_event: Optional[threading.Event] = None
594) -> Tuple[int, int, int]:
595 """
596 Low-level invocation of the multithreaded dist_m4ri binary.
597
598 Returns:
599 tuple (dmin, dmax, rw_steps)
600 """
601 exec_path = find_dist_m4ri_binary(dist_m4ri_path)
602
603 finC = check_finc_outc(finC, outC, verbose=False)
604
605 if method == 2 and wmax <= 0:
606 if dmax > 0:
607 wmax = dmax
608 elif timeout <= 0.0:
609 raise ValueError("either parameter wmax>0 or timeout>0 should be specified for CC method=2.")
610
611 cmd = [exec_path, f"debug={debug}", f"method={method}"]
612
613 if finH: cmd.append(f"finH={finH}")
614 if finG: cmd.append(f"finG={finG}")
615 if finL: cmd.append(f"finL={finL}")
616 if fin: cmd.append(f"fin={fin}")
617 if finC: cmd.append(f"finC={finC}")
618 if fdem: cmd.append(f"fdem={fdem}")
619 if dmin > 0: cmd.append(f"dmin={dmin}")
620 if dmax > 0: cmd.append(f"dmax={dmax}")
621 if wmax > 0: cmd.append(f"wmax={wmax}")
622 if wmin > 1: cmd.append(f"wmin={wmin}")
623 if dexp > 0: cmd.append(f"dexp={dexp}")
624 elif dest > 0: cmd.append(f"dest={dest}")
625 if steps is not None and steps > 0: cmd.append(f"steps={steps}")
626 if threads is not None and threads > 0: cmd.append(f"threads={threads}")
627 if timeout > 0: cmd.append(f"timeout={timeout}")
628 if smax is not None: cmd.append(f"smax={smax}")
629 if start is not None and start >= 0: cmd.append(f"start={start}")
630 if cbeg is not None and cbeg >= 0: cmd.append(f"cbeg={cbeg}")
631 if cend is not None and cend >= 0: cmd.append(f"cend={cend}")
632 if css is not None: cmd.append(f"css={css}")
633 if noscan: cmd.append(f"noscan={noscan}")
634 if classical >= 0: cmd.append(f"classical={classical}")
635 if dW >= 0: cmd.append(f"dW={dW}")
636 if maxC > 0: cmd.append(f"maxC={maxC}")
637 if pmin > 0.0: cmd.append(f"pmin={pmin}")
638 if outC: cmd.append(f"outC={outC}")
639 if seed != 0: cmd.append(f"seed={seed}")
640
641 if debug & 2:
642 print(f"[dist_m4ri] Running: {' '.join(cmd)}")
643
644 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
645
646 if stop_event is not None:
647 while proc.poll() is None:
648 if stop_event.is_set():
649 proc.terminate()
650 try:
651 proc.wait(timeout=1.0)
652 except subprocess.TimeoutExpired:
653 proc.kill()
654 raise RuntimeError("dist_m4ri execution cancelled by stop_event")
655 time.sleep(0.05)
656 stdout, stderr = proc.communicate()
657 else:
658 stdout, stderr = proc.communicate()
659
660 if proc.returncode != 0:
661 raise RuntimeError(f"dist_m4ri failed with exit code {proc.returncode}:\n{stderr}")
662
663 return parse_dist_m4ri_output(stdout)
664
665
666def _matrix_to_file(matrix, extension: str = ".mtx", temp_dir: str = "tmp") -> str:
667 """Helper to convert a matrix (numpy or scipy sparse) or file path to an MTX file path."""
668 if isinstance(matrix, (str, Path)):
669 return str(matrix)
670
671 import numpy as np
672 from scipy.io import mmwrite
673 from scipy.sparse import csr_matrix, issparse
674
675 path = create_unique_file(directory=temp_dir, extension=extension)
676 if issparse(matrix):
677 csr = matrix.astype(np.int8)
678 mmwrite(path, csr, symmetry='general')
679 else:
680 mat_arr = np.asarray(matrix, dtype=np.int8)
681 csr = csr_matrix(mat_arr)
682 mmwrite(path, csr, symmetry='general')
683 return path
684
685
687 H: Any,
688 dist_m4ri: Optional[str] = None,
689 method: int = 3,
690 threads: Optional[int] = None,
691 timeout: float = 60.0,
692 num_steps: Optional[int] = None,
693 d_exp: int = 0,
694 d_min: int = 0,
695 d_max: int = 0,
696 dmin: int = 0,
697 dmax: int = 0,
698 wmin: int = 1,
699 wmax: int = 0,
700 smax: Optional[int] = None,
701 start: Optional[int] = None,
702 cbeg: Optional[int] = None,
703 cend: Optional[int] = None,
704 noscan: int = 0,
705 dW: int = -1,
706 maxC: int = 0,
707 finC: Optional[str] = None,
708 outC: Optional[str] = None,
709 do_cws: bool = False,
710 return_info: bool = False,
711 cache_file: Optional[Union[str, Path]] = None,
712 solver: str = "dist_m4ri",
713 codedistance_method: str = "QDistEvol",
714 codedistance_params: Optional[Dict[str, Any]] = None,
715 seed: int = 0,
716 debug: int = 0,
717 verbose: bool = False
718) -> Any:
719 """
720 Computes the minimum distance of a classical linear code given parity check matrix H.
721
722 Args:
723 H: Parity check matrix (numpy array, scipy sparse matrix, or file path).
724 dist_m4ri: Path to dist_m4ri executable (optional).
725 method: Solver method (1=RW, 2=CC, 3=Bracketing default).
726 threads: Number of worker threads.
727 timeout: Execution timeout in seconds.
728 num_steps: Maximum RW steps.
729 d_exp: Expected distance estimate.
730 d_min / dmin: Known lower bound on distance.
731 d_max / dmax: Known upper bound on distance.
732 wmin: Minimum distance of interest (terminate early if cw of weight <= wmin is found in RW or CC, default: 1).
733 wmax: Maximum weight to search in CC.
734 smax: Maximum syndrome weight for CC confinement profile.
735 start / cbeg / cend: Column search range for CC.
736 noscan: Skip CC scan loop if 1.
737 dW: Extra weight window above dmin to collect codewords.
738 maxC: Maximum number of codewords to collect.
739 finC: Input file with initial codewords.
740 outC: Output file to save codewords (NZLIST format).
741 do_cws: Whether to return extracted codewords.
742 return_info: If True, return (dist, d_info) or (dist, d_info, cws) where d_info is [dmin, dmax, num_rw].
743 cache_file: Optional JSON file path for persistent distance caching.
744 solver: "dist_m4ri" or "codedistance".
745 codedistance_method: Method if using codedistance library.
746 codedistance_params: Extra parameters for codedistance library.
747 seed: Random seed.
748 debug: Debug level flags.
749 verbose: Verbose reporting flag.
750
751 Returns:
752 dist or (dist, cws) if do_cws is True (or (dist, d_info) / (dist, d_info, cws) if return_info=True)
753 """
754 eff_dmin = dmin if dmin > 0 else d_min
755 eff_dmax = dmax if dmax > 0 else d_max
756
757 finC = check_finc_outc(finC, outC, verbose=verbose)
758
759 global _distance_cache, _use_distance_cache, _distance_cache_file
760 eff_cache_file = str(Path(cache_file).resolve()) if cache_file is not None else _distance_cache_file
761 code_key = None
762 cached_entry = None
763
764 if solver == "codedistance":
765 if do_cws or outC:
766 raise ValueError("Codeword extraction is not supported with codedistance solver; use solver='dist_m4ri'.")
767 codedistance = _get_codedistance()
768 import numpy as np
769 params = dict(codedistance_params or {})
770 if num_steps is not None and "iterCount" not in params:
771 params["iterCount"] = num_steps
772
773 H_mat = H.toarray() if hasattr(H, 'toarray') else (np.asarray(H, dtype=np.int8) if isinstance(H, (np.ndarray, list)) else None)
774 res = codedistance.codeDistance(
775 H_mat, None, tB=1, method=codedistance_method, params=params,
776 seed=seed if seed != 0 else None
777 )
778 return res.get("d", -1)
779
780 # Solver is native multithreaded dist_m4ri (supports bounds caching and cumulative RW steps)
781 if _use_distance_cache:
782 if eff_cache_file:
783 load_distance_cache(eff_cache_file)
784 try:
785 h_state = get_sparse_array_state(H)
786 code_key = f"classical:{h_state}"
787 cached_entry = _distance_cache.get(code_key)
788 if cached_entry is not None:
789 # If exact distance is already proven and not asking for more codewords
790 if cached_entry.get("dmin", 0) > 0 and cached_entry.get("dmin") == cached_entry.get("dmax"):
791 if not (do_cws or outC) or (cached_entry.get("cws") and len(cached_entry["cws"]) > 0):
792 d_info = format_bounds_list(cached_entry.get("dmin", 0), cached_entry.get("dmax", 0), cached_entry.get("rw_steps", 0))
793 if verbose:
794 print(f"[dist_m4ri] Cache retrieval: SUCCESS (found cached exact distance for '{code_key}')")
795 print(f"[dist_m4ri] Cached result: dist={cached_entry['dist']}, bounds={format_bounds_str(d_info)}")
796 elif debug & 4:
797 print("[dist_m4ri] Cache hit for classical distance (exact distance known)!")
798 cws_res = cached_entry.get("cws", [])
799 if outC and cws_res:
800 _write_nzlist_file(outC, cws_res)
801 if return_info:
802 return (cached_entry["dist"], d_info, cws_res) if do_cws else (cached_entry["dist"], d_info)
803 return (cached_entry["dist"], cws_res) if do_cws else cached_entry["dist"]
804 if verbose:
805 print(f"[dist_m4ri] Cache retrieval: PARTIAL (cached bounds: dmin={cached_entry.get('dmin', 0)}, dmax={cached_entry.get('dmax', 0)}, rw_steps={cached_entry.get('rw_steps', 0)}; continuing search)")
806 # Use existing cached bounds to accelerate subsequent runs
807 if eff_dmax == 0 and cached_entry.get("dmax", 0) > 0:
808 eff_dmax = cached_entry["dmax"]
809 elif eff_dmax > 0 and cached_entry.get("dmax", 0) > 0:
810 eff_dmax = min(eff_dmax, cached_entry["dmax"])
811 if eff_dmin <= 1 and cached_entry.get("dmin", 0) > 1:
812 eff_dmin = cached_entry["dmin"]
813 elif eff_dmin > 1 and cached_entry.get("dmin", 0) > 1:
814 eff_dmin = max(eff_dmin, cached_entry["dmin"])
815 else:
816 if verbose:
817 print(f"[dist_m4ri] Cache retrieval: MISS (no entry for '{code_key}')")
818 except Exception:
819 code_key = None
820 cached_entry = None
821 else:
822 if verbose:
823 print("[dist_m4ri] Cache retrieval: DISABLED (cache is turned off)")
824
825 temp_files = []
826 try:
827 if isinstance(H, (str, Path)) and os.path.exists(str(H)):
828 file_H = str(H)
829 else:
830 file_H = _matrix_to_file(H, extension="_H.mtx")
831 temp_files.append(file_H)
832
833 outC_file = None
834 if do_cws or outC:
835 outC_file = create_unique_file(extension="_cws.nz")
836 temp_files.append(outC_file)
837
838 dmin_res, dmax_res, rw_steps = run_dist_m4ri(
839 dist_m4ri_path=dist_m4ri,
840 method=method,
841 finH=file_H,
842 finC=finC,
843 classical=1,
844 dmin=eff_dmin,
845 dmax=eff_dmax,
846 wmin=wmin,
847 wmax=wmax,
848 smax=smax,
849 start=start,
850 cbeg=cbeg,
851 cend=cend,
852 noscan=noscan,
853 dexp=d_exp,
854 steps=num_steps,
855 threads=threads,
856 timeout=timeout,
857 dW=dW,
858 maxC=maxC,
859 outC=outC_file,
860 seed=seed,
861 debug=debug
862 )
863
864 dist = dmin_res if (dmin_res == dmax_res or dmax_res == 0) else dmax_res
865 cws = []
866 if (do_cws or outC) and outC_file and os.path.exists(outC_file):
867 cws = read_sparse_vectors(outC_file)
868 cws.sort(key=len)
869 if outC:
870 _write_nzlist_file(outC, cws)
871
872 d_info = format_bounds_list(dmin_res, dmax_res, rw_steps)
873
874 if _use_distance_cache and code_key is not None:
875 prev_steps = cached_entry.get("rw_steps", 0) if cached_entry else 0
876 prev_dmax = cached_entry.get("dmax", 0) if cached_entry else 0
877 prev_dmin = cached_entry.get("dmin", 0) if cached_entry else 0
878 prev_cws = list(cached_entry.get("cws", [])) if cached_entry else []
879
880 total_rw_steps = prev_steps + rw_steps
881 best_dmax = min(prev_dmax, dmax_res) if (prev_dmax > 0 and dmax_res > 0) else (dmax_res if dmax_res > 0 else prev_dmax)
882 best_dmin = max(prev_dmin, dmin_res)
883
884 combined_cws = prev_cws
885 if cws:
886 existing_set = {tuple(cw) for cw in combined_cws}
887 for cw in cws:
888 if tuple(cw) not in existing_set:
889 combined_cws.append(cw)
890 existing_set.add(tuple(cw))
891 combined_cws.sort(key=len)
892
893 d_info = format_bounds_list(best_dmin, best_dmax, total_rw_steps)
894
895 _distance_cache[code_key] = {
896 "dist": dist,
897 "dmin": best_dmin,
898 "dmax": best_dmax,
899 "rw_steps": total_rw_steps,
900 "d_info": d_info,
901 "cws": combined_cws
902 }
903 if eff_cache_file:
904 save_distance_cache(eff_cache_file)
905
906 if return_info:
907 return (dist, d_info, combined_cws) if do_cws else (dist, d_info)
908 return (dist, combined_cws) if do_cws else dist
909
910 if return_info:
911 return (dist, d_info, cws) if do_cws else (dist, d_info)
912 return (dist, cws) if do_cws else dist
913
914 finally:
915 for f in temp_files:
916 if os.path.exists(f):
917 try: os.remove(f)
918 except OSError: pass
919
920
922 H: Any,
923 G: Optional[Any] = None,
924 L: Optional[Any] = None,
925 dist_m4ri: Optional[str] = None,
926 method: int = 3,
927 threads: Optional[int] = None,
928 timeout: float = 60.0,
929 num_steps: Optional[int] = None,
930 d_exp: int = 0,
931 d_min: int = 0,
932 d_max: int = 0,
933 dmin: int = 0,
934 dmax: int = 0,
935 wmin: int = 1,
936 wmax: int = 0,
937 smax: Optional[int] = None,
938 start: Optional[int] = None,
939 cbeg: Optional[int] = None,
940 cend: Optional[int] = None,
941 noscan: int = 0,
942 dW: int = -1,
943 maxC: int = 0,
944 finC: Optional[str] = None,
945 outC: Optional[str] = None,
946 do_cws: bool = False,
947 return_info: bool = False,
948 cache_file: Optional[Union[str, Path]] = None,
949 solver: str = "dist_m4ri",
950 codedistance_method: str = "QDistEvol",
951 codedistance_params: Optional[Dict[str, Any]] = None,
952 seed: int = 0,
953 debug: int = 0,
954 verbose: bool = False
955) -> Any:
956 """
957 Computes the minimum distance of a single-sided quantum code given parity check matrix H
958 and degeneracy generator G (or logical operator matrix L).
959
960 Args:
961 H: Parity check matrix (numpy array, scipy sparse matrix, or file path).
962 G: Degeneracy generator matrix (numpy array, scipy sparse matrix, or file path).
963 L: Logical operator matrix (numpy array, scipy sparse matrix, or file path).
964 dist_m4ri: Path to dist_m4ri executable (optional).
965 method: Solver method (1=RW, 2=CC, 3=Bracketing default).
966 threads: Number of worker threads.
967 timeout: Execution timeout in seconds.
968 num_steps: Maximum RW steps.
969 d_exp: Expected distance estimate.
970 d_min / dmin: Known lower bound on distance.
971 d_max / dmax: Known upper bound on distance.
972 wmin: Minimum distance of interest (terminate early if cw of weight <= wmin is found in RW or CC, default: 1).
973 wmax: Maximum weight to search in CC.
974 smax: Maximum syndrome weight for CC confinement profile.
975 start / cbeg / cend: Column search range for CC.
976 noscan: Skip CC scan loop if 1.
977 dW: Extra weight window above dmin to collect codewords.
978 maxC: Maximum number of codewords to collect.
979 finC: Input file with initial codewords.
980 outC: Output file to save codewords (NZLIST format).
981 do_cws: Whether to return extracted codewords.
982 return_info: If True, return (dist, d_info) or (dist, d_info, cws) where d_info is [dmin, dmax, num_rw].
983 cache_file: Optional JSON file path for persistent distance caching.
984 solver: "dist_m4ri" or "codedistance".
985 codedistance_method: Method if using codedistance library.
986 codedistance_params: Extra parameters for codedistance library.
987 seed: Random seed.
988 debug: Debug level flags.
989 verbose: Verbose reporting flag.
990
991 Returns:
992 dist or (dist, cws) if do_cws is True (or (dist, d_info) / (dist, d_info, cws) if return_info=True)
993 """
994 eff_dmin = dmin if dmin > 0 else d_min
995 eff_dmax = dmax if dmax > 0 else d_max
996
997 if G is None and L is None:
998 raise ValueError("Either G (dual generator matrix) or L (logical operator matrix) must be specified for quantum distance.")
999
1000 finC = check_finc_outc(finC, outC, verbose=verbose)
1001
1002 global _distance_cache, _use_distance_cache, _distance_cache_file
1003 eff_cache_file = str(Path(cache_file).resolve()) if cache_file is not None else _distance_cache_file
1004 code_key = None
1005 cached_entry = None
1006
1007 if solver == "codedistance":
1008 if do_cws or outC:
1009 raise ValueError("Codeword extraction is not supported with codedistance solver; use solver='dist_m4ri'.")
1010 codedistance = _get_codedistance()
1011 import numpy as np
1012 params = dict(codedistance_params or {})
1013 if num_steps is not None and "iterCount" not in params:
1014 params["iterCount"] = num_steps
1015
1016 H_mat = H.toarray() if hasattr(H, 'toarray') else (np.asarray(H, dtype=np.int8) if isinstance(H, (np.ndarray, list)) else None)
1017 dual_mat = G if G is not None else L
1018 dual_arr = dual_mat.toarray() if hasattr(dual_mat, 'toarray') else (np.asarray(dual_mat, dtype=np.int8) if isinstance(dual_mat, (np.ndarray, list)) else None)
1019
1020 res = codedistance.codeDistance(
1021 H_mat, dual_arr, tB=1, method=codedistance_method, params=params,
1022 seed=seed if seed != 0 else None
1023 )
1024 return res.get("d", -1)
1025
1026 # Solver is native multithreaded dist_m4ri
1027 if _use_distance_cache:
1028 if eff_cache_file:
1029 load_distance_cache(eff_cache_file)
1030 try:
1031 h_state = get_sparse_array_state(H)
1032 if G is not None:
1033 g_state = get_sparse_array_state(G)
1034 code_key = f"quantum:H={h_state}:G={g_state}"
1035 else:
1036 l_state = get_sparse_array_state(L)
1037 code_key = f"quantum:H={h_state}:L={l_state}"
1038 cached_entry = _distance_cache.get(code_key)
1039 if cached_entry is not None:
1040 if cached_entry.get("dmin", 0) > 0 and cached_entry.get("dmin") == cached_entry.get("dmax"):
1041 if not (do_cws or outC) or (cached_entry.get("cws") and len(cached_entry["cws"]) > 0):
1042 d_info = format_bounds_list(cached_entry.get("dmin", 0), cached_entry.get("dmax", 0), cached_entry.get("rw_steps", 0))
1043 if verbose:
1044 print(f"[dist_m4ri] Cache retrieval: SUCCESS (found cached exact distance for '{code_key}')")
1045 print(f"[dist_m4ri] Cached result: dist={cached_entry['dist']}, bounds={format_bounds_str(d_info)}")
1046 elif debug & 4:
1047 print("[dist_m4ri] Cache hit for quantum distance (exact distance known)!")
1048 cws_res = cached_entry.get("cws", [])
1049 if outC and cws_res:
1050 _write_nzlist_file(outC, cws_res)
1051 if return_info:
1052 return (cached_entry["dist"], d_info, cws_res) if do_cws else (cached_entry["dist"], d_info)
1053 return (cached_entry["dist"], cws_res) if do_cws else cached_entry["dist"]
1054 if verbose:
1055 print(f"[dist_m4ri] Cache retrieval: PARTIAL (cached bounds: dmin={cached_entry.get('dmin', 0)}, dmax={cached_entry.get('dmax', 0)}, rw_steps={cached_entry.get('rw_steps', 0)}; continuing search)")
1056 if eff_dmax == 0 and cached_entry.get("dmax", 0) > 0:
1057 eff_dmax = cached_entry["dmax"]
1058 elif eff_dmax > 0 and cached_entry.get("dmax", 0) > 0:
1059 eff_dmax = min(eff_dmax, cached_entry["dmax"])
1060 if eff_dmin <= 1 and cached_entry.get("dmin", 0) > 1:
1061 eff_dmin = cached_entry["dmin"]
1062 elif eff_dmin > 1 and cached_entry.get("dmin", 0) > 1:
1063 eff_dmin = max(eff_dmin, cached_entry["dmin"])
1064 else:
1065 if verbose:
1066 print(f"[dist_m4ri] Cache retrieval: MISS (no entry for '{code_key}')")
1067 except Exception:
1068 code_key = None
1069 cached_entry = None
1070 else:
1071 if verbose:
1072 print("[dist_m4ri] Cache retrieval: DISABLED (cache is turned off)")
1073
1074 temp_files = []
1075 try:
1076 if isinstance(H, (str, Path)) and os.path.exists(str(H)):
1077 file_H = str(H)
1078 else:
1079 file_H = _matrix_to_file(H, extension="_H.mtx")
1080 temp_files.append(file_H)
1081
1082 file_G = None
1083 if G is not None:
1084 if isinstance(G, (str, Path)) and os.path.exists(str(G)):
1085 file_G = str(G)
1086 else:
1087 file_G = _matrix_to_file(G, extension="_G.mtx")
1088 temp_files.append(file_G)
1089
1090 file_L = None
1091 if L is not None:
1092 if isinstance(L, (str, Path)) and os.path.exists(str(L)):
1093 file_L = str(L)
1094 else:
1095 file_L = _matrix_to_file(L, extension="_L.mtx")
1096 temp_files.append(file_L)
1097
1098 outC_file = None
1099 if do_cws or outC:
1100 outC_file = create_unique_file(extension="_cws.nz")
1101 temp_files.append(outC_file)
1102
1103 dmin_res, dmax_res, rw_steps = run_dist_m4ri(
1104 dist_m4ri_path=dist_m4ri,
1105 method=method,
1106 finH=file_H,
1107 finG=file_G,
1108 finL=file_L,
1109 finC=finC,
1110 classical=0,
1111 dmin=eff_dmin,
1112 dmax=eff_dmax,
1113 wmin=wmin,
1114 wmax=wmax,
1115 smax=smax,
1116 start=start,
1117 cbeg=cbeg,
1118 cend=cend,
1119 noscan=noscan,
1120 dexp=d_exp,
1121 steps=num_steps,
1122 threads=threads,
1123 timeout=timeout,
1124 dW=dW,
1125 maxC=maxC,
1126 outC=outC_file,
1127 seed=seed,
1128 debug=debug
1129 )
1130
1131 dist = dmin_res if (dmin_res == dmax_res or dmax_res == 0) else dmax_res
1132 cws = []
1133 if (do_cws or outC) and outC_file and os.path.exists(outC_file):
1134 cws = read_sparse_vectors(outC_file)
1135 cws.sort(key=len)
1136 if outC:
1137 _write_nzlist_file(outC, cws)
1138
1139 d_info = format_bounds_list(dmin_res, dmax_res, rw_steps)
1140
1141 if _use_distance_cache and code_key is not None:
1142 prev_steps = cached_entry.get("rw_steps", 0) if cached_entry else 0
1143 prev_dmax = cached_entry.get("dmax", 0) if cached_entry else 0
1144 prev_dmin = cached_entry.get("dmin", 0) if cached_entry else 0
1145 prev_cws = list(cached_entry.get("cws", [])) if cached_entry else []
1146
1147 total_rw_steps = prev_steps + rw_steps
1148 best_dmax = min(prev_dmax, dmax_res) if (prev_dmax > 0 and dmax_res > 0) else (dmax_res if dmax_res > 0 else prev_dmax)
1149 best_dmin = max(prev_dmin, dmin_res)
1150
1151 combined_cws = prev_cws
1152 if cws:
1153 existing_set = {tuple(cw) for cw in combined_cws}
1154 for cw in cws:
1155 if tuple(cw) not in existing_set:
1156 combined_cws.append(cw)
1157 existing_set.add(tuple(cw))
1158 combined_cws.sort(key=len)
1159
1160 d_info = format_bounds_list(best_dmin, best_dmax, total_rw_steps)
1161
1162 _distance_cache[code_key] = {
1163 "dist": dist,
1164 "dmin": best_dmin,
1165 "dmax": best_dmax,
1166 "rw_steps": total_rw_steps,
1167 "d_info": d_info,
1168 "cws": combined_cws
1169 }
1170 if eff_cache_file:
1171 save_distance_cache(eff_cache_file)
1172
1173 if return_info:
1174 return (dist, d_info, combined_cws) if do_cws else (dist, d_info)
1175 return (dist, combined_cws) if do_cws else dist
1176
1177 if return_info:
1178 return (dist, d_info, cws) if do_cws else (dist, d_info)
1179 return (dist, cws) if do_cws else dist
1180
1181 finally:
1182 for f in temp_files:
1183 if os.path.exists(f):
1184 try: os.remove(f)
1185 except OSError: pass
1186
1187
1189 Hx: Any,
1190 Hz: Any,
1191 Lx: Optional[Any] = None,
1192 Lz: Optional[Any] = None,
1193 dist_m4ri: Optional[str] = None,
1194 method: int = 3,
1195 threads: Optional[int] = None,
1196 timeout: float = 60.0,
1197 num_steps: Optional[int] = None,
1198 d_exp: int = 0,
1199 d_min: int = 0,
1200 d_max: int = 0,
1201 dmin: int = 0,
1202 dmax: int = 0,
1203 wmin: int = 1,
1204 wmax: int = 0,
1205 smax: Optional[int] = None,
1206 start: Optional[int] = None,
1207 cbeg: Optional[int] = None,
1208 cend: Optional[int] = None,
1209 noscan: int = 0,
1210 dW: int = -1,
1211 maxC: int = 0,
1212 finC: Optional[str] = None,
1213 outC: Optional[str] = None,
1214 do_cws: bool = False,
1215 cache_file: Optional[Union[str, Path]] = None,
1216 solver: str = "dist_m4ri",
1217 codedistance_method: str = "QDistEvol",
1218 codedistance_params: Optional[Dict[str, Any]] = None,
1219 seed: int = 0,
1220 debug: int = 0,
1221 verbose: bool = False,
1222 **kwargs
1223) -> Tuple[Any, ...]:
1224 """
1225 Computes CSS quantum code distance d = min(d_X, d_Z).
1226
1227 Args:
1228 Hx: X-stabilizer parity check matrix.
1229 Hz: Z-stabilizer parity check matrix.
1230 Lx: Optional X-logical operator matrix (alternative to Hz as finG).
1231 Lz: Optional Z-logical operator matrix (alternative to Hx as finG).
1232 dist_m4ri: Path to dist_m4ri executable (optional).
1233 method: Solver method (1=RW, 2=CC, 3=Bracketing default).
1234 threads: Number of worker threads.
1235 timeout: Execution timeout in seconds.
1236 num_steps: Maximum RW steps.
1237 d_exp: Expected distance estimate.
1238 d_min / dmin: Known lower bound on distance, inclusive.
1239 d_max / dmax: Known upper bound on distance, inclusive.
1240 wmin: Minimum distance of interest (terminate early if cw of weight <= wmin is found in RW or CC, default: 1).
1241 wmax: Maximum weight to search in CC.
1242 smax: Maximum syndrome weight for CC confinement profile.
1243 start / cbeg / cend: Column search range for CC.
1244 noscan: Skip CC scan loop if 1.
1245 dW: Extra weight window above dmin to collect codewords.
1246 maxC: Maximum number of codewords to collect.
1247 finC: Input file with initial codewords.
1248 outC: Output file to save codewords (NZLIST format).
1249 do_cws: Whether to return extracted X and Z codewords.
1250 cache_file: Optional JSON file path for persistent distance caching.
1251 solver: "dist_m4ri" or "codedistance".
1252 codedistance_method: Method if using codedistance library.
1253 codedistance_params: Extra parameters for codedistance library.
1254 seed: Random seed.
1255 debug: Debug level flags.
1256 verbose: Verbose reporting flag.
1257
1258 Returns:
1259 tuple (dist, dX_info, dZ_info, cws_X, cws_Z) if do_cws
1260 else (dist, dX_info, dZ_info)
1261 """
1262 eff_dmin = dmin if dmin > 0 else d_min
1263 eff_dmax = dmax if dmax > 0 else d_max
1264
1265 can_compute_Z = Hx is not None and (hasattr(Hx, 'shape') and Hx.shape[0] > 0 if not isinstance(Hx, str) else True)
1266 can_compute_X = Hz is not None and (hasattr(Hz, 'shape') and Hz.shape[0] > 0 if not isinstance(Hz, str) else True)
1267
1268 if not can_compute_Z and not can_compute_X:
1269 raise ValueError("Cannot compute CSS distance: Both Hx and Hz are empty.")
1270
1271 finC = check_finc_outc(finC, outC, verbose=verbose)
1272
1273 global _distance_cache, _use_distance_cache, _distance_cache_file
1274 eff_cache_file = str(Path(cache_file).resolve()) if cache_file is not None else _distance_cache_file
1275 code_key = None
1276 cached_entry = None
1277
1278 if solver == "codedistance":
1279 if do_cws or outC:
1280 raise ValueError("Codeword extraction is not supported with codedistance solver; use solver='dist_m4ri'.")
1281 codedistance = _get_codedistance()
1282 import numpy as np
1283 params = dict(codedistance_params or {})
1284 if num_steps is not None and "iterCount" not in params:
1285 params["iterCount"] = num_steps
1286
1287 dist_Z, dist_X = None, None
1288 dX_info, dZ_info = None, None
1289
1290 Hx_mat = Hx.toarray() if hasattr(Hx, 'toarray') else (np.asarray(Hx, dtype=np.int8) if isinstance(Hx, (np.ndarray, list)) else None)
1291 Hz_mat = Hz.toarray() if hasattr(Hz, 'toarray') else (np.asarray(Hz, dtype=np.int8) if isinstance(Hz, (np.ndarray, list)) else None)
1292
1293 if can_compute_Z:
1294 res_Z = codedistance.CSScodeDistance(
1295 Hx_mat, Hz_mat, method=codedistance_method, params=params.copy(),
1296 component="Z", seed=seed if seed != 0 else None
1297 )
1298 dist_Z = res_Z.get("d", -1)
1299 dZ_info = format_bounds_list(dist_Z, dist_Z, 0) if dist_Z > 0 else [0, 0, 0]
1300
1301 if can_compute_X:
1302 res_X = codedistance.CSScodeDistance(
1303 Hx_mat, Hz_mat, method=codedistance_method, params=params.copy(),
1304 component="X", seed=seed if seed != 0 else None
1305 )
1306 dist_X = res_X.get("d", -1)
1307 dX_info = format_bounds_list(dist_X, dist_X, 0) if dist_X > 0 else [0, 0, 0]
1308
1309 if dist_X is not None and dist_Z is not None:
1310 dist = min(dist_Z, dist_X) if (dist_Z > 0 and dist_X > 0) else max(dist_Z, dist_X)
1311 elif dist_X is not None:
1312 dist = dist_X
1313 else:
1314 dist = dist_Z
1315
1316 return (dist, dX_info, dZ_info)
1317
1318 # Solver is native multithreaded dist_m4ri
1319 if _use_distance_cache:
1320 if eff_cache_file:
1321 load_distance_cache(eff_cache_file)
1322 try:
1323 hx_state = get_sparse_array_state(Hx) if can_compute_Z else "none"
1324 hz_state = get_sparse_array_state(Hz) if can_compute_X else "none"
1325 code_key = f"css:X={hx_state}:Z={hz_state}"
1326 if Lx is not None or Lz is not None:
1327 lx_state = get_sparse_array_state(Lx) if Lx is not None else "none"
1328 lz_state = get_sparse_array_state(Lz) if Lz is not None else "none"
1329 code_key = f"{code_key}:Lx={lx_state}:Lz={lz_state}"
1330 cached_entry = _distance_cache.get(code_key)
1331 if cached_entry is not None:
1332 # If exact distance is already proven and not asking for more codewords
1333 if cached_entry.get("dmin", 0) > 0 and cached_entry.get("dmin") == cached_entry.get("dmax"):
1334 if not (do_cws or outC) or (cached_entry.get("cws_X") and cached_entry.get("cws_Z")):
1335 dx_res = cached_entry.get("dX", format_bounds_list(cached_entry.get("dmin_X", 0), cached_entry.get("dmax_X", 0), cached_entry.get("rw_steps_X", 0)))
1336 dz_res = cached_entry.get("dZ", format_bounds_list(cached_entry.get("dmin_Z", 0), cached_entry.get("dmax_Z", 0), cached_entry.get("rw_steps_Z", 0)))
1337 if verbose:
1338 print(f"[dist_m4ri] Cache retrieval: SUCCESS (found cached exact CSS distance for '{code_key}')")
1339 print(f"[dist_m4ri] Cached result: dist={cached_entry['dist']}, dX={format_bounds_str(dx_res)}, dZ={format_bounds_str(dz_res)}")
1340 elif debug & 4:
1341 print("[dist_m4ri] Cache hit for CSS distance (exact distance known)!")
1342 cws_x = cached_entry.get("cws_X", [])
1343 cws_z = cached_entry.get("cws_Z", [])
1344 if outC and (cws_x or cws_z):
1345 _write_nzlist_file(outC, (cws_x or []) + (cws_z or []))
1346 return (
1347 cached_entry["dist"], dx_res, dz_res,
1348 cws_x, cws_z
1349 ) if do_cws else (
1350 cached_entry["dist"], dx_res, dz_res
1351 )
1352 if verbose:
1353 print(f"[dist_m4ri] Cache retrieval: PARTIAL (cached CSS bounds: dmin={cached_entry.get('dmin', 0)}, dmax={cached_entry.get('dmax', 0)}; continuing search)")
1354 # Seed bounds from cache
1355 if eff_dmax == 0 and cached_entry.get("dmax", 0) > 0:
1356 eff_dmax = cached_entry["dmax"]
1357 elif eff_dmax > 0 and cached_entry.get("dmax", 0) > 0:
1358 eff_dmax = min(eff_dmax, cached_entry["dmax"])
1359 if eff_dmin <= 1 and cached_entry.get("dmin", 0) > 1:
1360 eff_dmin = cached_entry["dmin"]
1361 elif eff_dmin > 1 and cached_entry.get("dmin", 0) > 1:
1362 eff_dmin = max(eff_dmin, cached_entry["dmin"])
1363 else:
1364 if verbose:
1365 print(f"[dist_m4ri] Cache retrieval: MISS (no entry for '{code_key}')")
1366 except Exception:
1367 code_key = None
1368 cached_entry = None
1369 else:
1370 if verbose:
1371 print("[dist_m4ri] Cache retrieval: DISABLED (cache is turned off)")
1372
1373 temp_files = []
1374 try:
1375 file_Hx = _matrix_to_file(Hx, extension="_Hx.mtx") if can_compute_Z else None
1376 file_Hz = _matrix_to_file(Hz, extension="_Hz.mtx") if can_compute_X else None
1377 file_Lx = _matrix_to_file(Lx, extension="_Lx.mtx") if Lx is not None else None
1378 file_Lz = _matrix_to_file(Lz, extension="_Lz.mtx") if Lz is not None else None
1379
1380 for f in (file_Hx, file_Hz, file_Lx, file_Lz):
1381 if f and not isinstance(f, (str, Path)) or (f and not os.path.exists(f)):
1382 pass
1383 elif f and f.startswith(tempfile.gettempdir()):
1384 temp_files.append(f)
1385
1386 outZ = create_unique_file(extension="_Z.nz") if ((do_cws or outC) and can_compute_Z) else None
1387 outX = create_unique_file(extension="_X.nz") if ((do_cws or outC) and can_compute_X) else None
1388 if outZ: temp_files.append(outZ)
1389 if outX: temp_files.append(outX)
1390
1391 dist_Z, dist_X = None, None
1392 dmin_z, dmax_z, rw_steps_z = 0, 0, 0
1393 dmin_x, dmax_x, rw_steps_x = 0, 0, 0
1394 cws_Z, cws_X = [], []
1395
1396 # Z-distance: Hx as finH, Hz as finG (or Lz as finL)
1397 if can_compute_Z:
1398 dmin_z, dmax_z, rw_steps_z = run_dist_m4ri(
1399 dist_m4ri_path=dist_m4ri,
1400 method=method,
1401 finH=file_Hx,
1402 finG=file_Hz if file_Lz is None else None,
1403 finL=file_Lz,
1404 finC=finC,
1405 dmin=eff_dmin,
1406 dmax=eff_dmax,
1407 wmin=wmin,
1408 wmax=wmax,
1409 smax=smax,
1410 start=start,
1411 cbeg=cbeg,
1412 cend=cend,
1413 noscan=noscan,
1414 dexp=d_exp,
1415 steps=num_steps,
1416 threads=threads,
1417 timeout=timeout,
1418 dW=dW,
1419 maxC=maxC,
1420 outC=outZ,
1421 seed=seed,
1422 debug=debug
1423 )
1424 dist_Z = dmin_z if (dmin_z == dmax_z or dmax_z == 0) else dmax_z
1425 if (do_cws or outC) and outZ and os.path.exists(outZ):
1426 cws_Z = read_sparse_vectors(outZ)
1427 cws_Z.sort(key=len)
1428
1429 # X-distance: Hz as finH, Hx as finG (or Lx as finL)
1430 if can_compute_X:
1431 dmin_x, dmax_x, rw_steps_x = run_dist_m4ri(
1432 dist_m4ri_path=dist_m4ri,
1433 method=method,
1434 finH=file_Hz,
1435 finG=file_Hx if file_Lx is None else None,
1436 finL=file_Lx,
1437 finC=finC,
1438 dmin=eff_dmin,
1439 dmax=eff_dmax,
1440 wmin=wmin,
1441 wmax=wmax,
1442 smax=smax,
1443 start=start,
1444 cbeg=cbeg,
1445 cend=cend,
1446 noscan=noscan,
1447 dexp=d_exp,
1448 steps=num_steps,
1449 threads=threads,
1450 timeout=timeout,
1451 dW=dW,
1452 maxC=maxC,
1453 outC=outX,
1454 seed=seed,
1455 debug=debug
1456 )
1457 dist_X = dmin_x if (dmin_x == dmax_x or dmax_x == 0) else dmax_x
1458 if (do_cws or outC) and outX and os.path.exists(outX):
1459 cws_X = read_sparse_vectors(outX)
1460 cws_X.sort(key=len)
1461
1462 dX_info = format_bounds_list(dmin_x, dmax_x, rw_steps_x) if can_compute_X else None
1463 dZ_info = format_bounds_list(dmin_z, dmax_z, rw_steps_z) if can_compute_Z else None
1464
1465 if dist_X is not None and dist_Z is not None:
1466 dist = min(dist_Z, dist_X) if (dist_Z > 0 and dist_X > 0) else max(dist_Z, dist_X)
1467 elif dist_X is not None:
1468 dist = dist_X
1469 else:
1470 dist = dist_Z
1471
1472 if outC:
1473 _write_nzlist_file(outC, (cws_X or []) + (cws_Z or []))
1474
1475 res_tuple = (dist, dX_info, dZ_info, cws_X, cws_Z) if do_cws else (dist, dX_info, dZ_info)
1476
1477 if _use_distance_cache and code_key is not None:
1478 prev_steps = cached_entry.get("rw_steps", 0) if cached_entry else 0
1479 prev_dmax = cached_entry.get("dmax", 0) if cached_entry else 0
1480 prev_dmin = cached_entry.get("dmin", 0) if cached_entry else 0
1481
1482 run_steps = (rw_steps_z if can_compute_Z else 0) + (rw_steps_x if can_compute_X else 0)
1483 total_rw_steps = prev_steps + run_steps
1484
1485 curr_dmax = dist if dist > 0 else 0
1486 best_dmax = min(prev_dmax, curr_dmax) if (prev_dmax > 0 and curr_dmax > 0) else (curr_dmax if curr_dmax > 0 else prev_dmax)
1487
1488 curr_dmin = 0
1489 if can_compute_Z and can_compute_X:
1490 curr_dmin = min(dmin_z, dmin_x)
1491 elif can_compute_Z:
1492 curr_dmin = dmin_z
1493 elif can_compute_X:
1494 curr_dmin = dmin_x
1495 best_dmin = max(prev_dmin, curr_dmin)
1496
1497 _distance_cache[code_key] = {
1498 "dist": dist,
1499 "dmin": best_dmin,
1500 "dmax": best_dmax,
1501 "rw_steps": total_rw_steps,
1502 "dmin_X": dmin_x,
1503 "dmax_X": dmax_x,
1504 "rw_steps_X": rw_steps_x,
1505 "dmin_Z": dmin_z,
1506 "dmax_Z": dmax_z,
1507 "rw_steps_Z": rw_steps_z,
1508 "dX": dX_info,
1509 "dZ": dZ_info,
1510 "cws_X": cws_X,
1511 "cws_Z": cws_Z
1512 }
1513 if eff_cache_file:
1514 save_distance_cache(eff_cache_file)
1515 return res_tuple
1516
1517 finally:
1518 for f in temp_files:
1519 if f and os.path.exists(f):
1520 try: os.remove(f)
1521 except OSError: pass
1522
1523
1525 dem: Optional[Any] = None,
1526 circuit: Optional[Any] = None,
1527 dist_m4ri: Optional[str] = None,
1528 method: int = 3,
1529 threads: Optional[int] = None,
1530 timeout: float = 60.0,
1531 num_steps: Optional[int] = None,
1532 d_exp: int = 0,
1533 d_min: int = 0,
1534 d_max: int = 0,
1535 dmin: int = 0,
1536 dmax: int = 0,
1537 wmin: int = 1,
1538 wmax: int = 0,
1539 smax: Optional[int] = None,
1540 start: Optional[int] = None,
1541 cbeg: Optional[int] = None,
1542 cend: Optional[int] = None,
1543 noscan: int = 0,
1544 dW: int = -1,
1545 maxC: int = 0,
1546 pmin: float = 0.0,
1547 finC: Optional[str] = None,
1548 outC: Optional[str] = None,
1549 do_cws: bool = False,
1550 cache_file: Optional[Union[str, Path]] = None,
1551 solver: str = "dist_m4ri",
1552 codedistance_method: str = "UndetectableErrorStim",
1553 codedistance_params: Optional[Dict[str, Any]] = None,
1554 seed: int = 0,
1555 debug: int = 0,
1556 verbose: bool = False,
1557 **kwargs
1558) -> Tuple[Any, ...]:
1559 """
1560 Computes minimum graph/hypergraph distance of a Stim Detector Error Model (DEM).
1561
1562 Args:
1563 dem: stim.DetectorErrorModel object or path to .dem file.
1564 circuit: stim.Circuit object (converted to DEM).
1565 dist_m4ri: Path to dist_m4ri executable (optional).
1566 method: Solver method (1=RW, 2=CC, 3=Bracketing default).
1567 threads: Number of worker threads.
1568 timeout: Execution timeout in seconds.
1569 num_steps: Maximum RW steps.
1570 d_exp: Expected distance estimate.
1571 d_min / dmin: Known lower bound on distance, inclusive.
1572 d_max / dmax: Known upper bound on distance, inclusive.
1573 wmin: Minimum distance of interest (terminate early if cw of weight <= wmin is found in RW or CC, default: 1).
1574 wmax: Maximum weight to search in CC.
1575 smax: Maximum syndrome weight for CC confinement profile.
1576 start / cbeg / cend: Column search range for CC.
1577 noscan: Skip CC scan loop if 1.
1578 dW: Extra weight window above dmin to collect codewords.
1579 maxC: Maximum number of codewords to collect.
1580 pmin: Probability cutoff for error mechanisms in DEM.
1581 finC: Input file with initial codewords.
1582 outC: Output file to save codewords (NZLIST format).
1583 do_cws: Whether to return extracted error mechanisms / codewords.
1584 cache_file: Optional JSON file path for persistent distance caching.
1585 solver: "dist_m4ri" or "codedistance".
1586 codedistance_method: Method if using codedistance library.
1587 codedistance_params: Extra parameters for codedistance library.
1588 seed: Random seed.
1589 debug: Debug level flags.
1590 verbose: Verbose reporting flag.
1591
1592 Returns:
1593 tuple (dist, d_info, cws) if do_cws else (dist, d_info)
1594 """
1595 eff_dmin = dmin if dmin > 0 else d_min
1596 eff_dmax = dmax if dmax > 0 else d_max
1597
1598 if dem is None and circuit is not None:
1599 if hasattr(circuit, 'detector_error_model'):
1600 dem = circuit.detector_error_model(decompose_errors=True)
1601 else:
1602 raise ValueError("Provided circuit object does not have detector_error_model() method.")
1603
1604 if dem is None:
1605 raise ValueError("Either 'dem' or 'circuit' must be provided.")
1606
1607 finC = check_finc_outc(finC, outC, verbose=verbose)
1608
1609 if solver == "codedistance":
1610 if do_cws or outC:
1611 raise ValueError("Codeword extraction is not supported with codedistance solver; use solver='dist_m4ri'.")
1612 codedistance = _get_codedistance()
1613 params = dict(codedistance_params or {})
1614 params.setdefault("filterCircuit", False)
1615 if num_steps is not None and "iterCount" not in params:
1616 params["iterCount"] = num_steps
1617
1618 if circuit is not None:
1619 res = codedistance.circuitDistance(
1620 circuit, method=codedistance_method, params=params,
1621 seed=seed if seed != 0 else None
1622 )
1623 else:
1624 H, L, priors = codedistance.StimDEM2HL(dem)
1625 if "priors" not in params and len(priors) > 0:
1626 params["priors"] = priors
1627 res = codedistance.codeDistance(
1628 H, L, tB=1, method=codedistance_method, params=params,
1629 seed=seed if seed != 0 else None
1630 )
1631 d = res.get("d", -1)
1632 d_info = format_bounds_list(d, d, 0) if d > 0 else [0, 0, 0]
1633 return d, d_info
1634
1635 # Solver is native multithreaded dist_m4ri
1636 global _distance_cache, _use_distance_cache, _distance_cache_file
1637 eff_cache_file = str(Path(cache_file).resolve()) if cache_file is not None else _distance_cache_file
1638 code_key = None
1639 cached_entry = None
1640 if _use_distance_cache:
1641 if eff_cache_file:
1642 load_distance_cache(eff_cache_file)
1643 try:
1644 dem_obj = dem if dem is not None else circuit
1645 dem_state = get_sparse_array_state(dem_obj)
1646 code_key = f"dem:{dem_state}" if pmin <= 0.0 else f"dem:{dem_state}:pmin={pmin}"
1647 cached_entry = _distance_cache.get(code_key)
1648 if cached_entry is not None:
1649 # If exact distance is already proven and not asking for more codewords
1650 if cached_entry.get("dmin", 0) > 0 and cached_entry.get("dmin") == cached_entry.get("dmax"):
1651 if not (do_cws or outC) or (cached_entry.get("cws") and len(cached_entry["cws"]) > 0):
1652 d_info = format_bounds_list(cached_entry.get("dmin", 0), cached_entry.get("dmax", 0), cached_entry.get("rw_steps", 0))
1653 if verbose:
1654 print(f"[dist_m4ri] Cache retrieval: SUCCESS (found cached exact DEM distance for '{code_key}')")
1655 print(f"[dist_m4ri] Cached result: dist={cached_entry['dist']}, bounds={format_bounds_str(d_info)}")
1656 elif debug & 4:
1657 print("[dist_m4ri] Cache hit for DEM distance (exact distance known)!")
1658 cws_res = cached_entry.get("cws", [])
1659 if outC and cws_res:
1660 _write_nzlist_file(outC, cws_res)
1661 return (
1662 cached_entry["dist"], d_info, cws_res
1663 ) if do_cws else (
1664 cached_entry["dist"], d_info
1665 )
1666 if verbose:
1667 print(f"[dist_m4ri] Cache retrieval: PARTIAL (cached DEM bounds: dmin={cached_entry.get('dmin', 0)}, dmax={cached_entry.get('dmax', 0)}; continuing search)")
1668 # Seed bounds from cache
1669 if eff_dmax == 0 and cached_entry.get("dmax", 0) > 0:
1670 eff_dmax = cached_entry["dmax"]
1671 elif eff_dmax > 0 and cached_entry.get("dmax", 0) > 0:
1672 eff_dmax = min(eff_dmax, cached_entry["dmax"])
1673 if eff_dmin <= 1 and cached_entry.get("dmin", 0) > 1:
1674 eff_dmin = cached_entry["dmin"]
1675 elif eff_dmin > 1 and cached_entry.get("dmin", 0) > 1:
1676 eff_dmin = max(eff_dmin, cached_entry["dmin"])
1677 else:
1678 if verbose:
1679 print(f"[dist_m4ri] Cache retrieval: MISS (no entry for '{code_key}')")
1680 except Exception:
1681 code_key = None
1682 cached_entry = None
1683 else:
1684 if verbose:
1685 print("[dist_m4ri] Cache retrieval: DISABLED (cache is turned off)")
1686
1687 temp_files = []
1688 try:
1689 if isinstance(dem, (str, Path)) and os.path.exists(str(dem)):
1690 file_dem = str(dem)
1691 else:
1692 file_dem = create_unique_file(extension=".dem")
1693 temp_files.append(file_dem)
1694 if hasattr(dem, 'flattened'):
1695 dem.flattened().to_file(file_dem)
1696 elif hasattr(dem, 'to_file'):
1697 dem.to_file(file_dem)
1698 else:
1699 with open(file_dem, 'w') as f:
1700 f.write(str(dem))
1701
1702 outC_file = None
1703 if do_cws or outC:
1704 outC_file = create_unique_file(extension="_out.nz")
1705 temp_files.append(outC_file)
1706
1707 dmin_res, dmax_res, rw_steps = run_dist_m4ri(
1708 dist_m4ri_path=dist_m4ri,
1709 method=method,
1710 fdem=file_dem,
1711 finC=finC,
1712 dmin=eff_dmin,
1713 dmax=eff_dmax,
1714 wmin=wmin,
1715 wmax=wmax,
1716 smax=smax if smax is not None else 0,
1717 start=start,
1718 cbeg=cbeg,
1719 cend=cend,
1720 noscan=noscan,
1721 dexp=d_exp,
1722 steps=num_steps,
1723 threads=threads,
1724 timeout=timeout,
1725 pmin=pmin,
1726 dW=dW,
1727 maxC=maxC,
1728 outC=outC_file,
1729 seed=seed,
1730 debug=debug
1731 )
1732
1733 dist = dmin_res if (dmin_res == dmax_res or dmax_res == 0) else dmax_res
1734 cws = []
1735 if (do_cws or outC) and outC_file and os.path.exists(outC_file):
1736 cws = read_sparse_vectors(outC_file)
1737 cws.sort(key=len)
1738 if outC:
1739 _write_nzlist_file(outC, cws)
1740
1741 d_info = format_bounds_list(dmin_res, dmax_res, rw_steps)
1742
1743 if _use_distance_cache and code_key is not None:
1744 prev_steps = cached_entry.get("rw_steps", 0) if cached_entry else 0
1745 prev_dmax = cached_entry.get("dmax", 0) if cached_entry else 0
1746 prev_dmin = cached_entry.get("dmin", 0) if cached_entry else 0
1747 prev_cws = list(cached_entry.get("cws", [])) if cached_entry else []
1748
1749 total_rw_steps = prev_steps + rw_steps
1750 best_dmax = min(prev_dmax, dmax_res) if (prev_dmax > 0 and dmax_res > 0) else (dmax_res if dmax_res > 0 else prev_dmax)
1751 best_dmin = max(prev_dmin, dmin_res)
1752
1753 combined_cws = prev_cws
1754 if cws:
1755 existing_set = {tuple(cw) for cw in combined_cws}
1756 for cw in cws:
1757 if tuple(cw) not in existing_set:
1758 combined_cws.append(cw)
1759 existing_set.add(tuple(cw))
1760 combined_cws.sort(key=len)
1761
1762 d_info = format_bounds_list(best_dmin, best_dmax, total_rw_steps)
1763
1764 _distance_cache[code_key] = {
1765 "dist": dist,
1766 "dmin": best_dmin,
1767 "dmax": best_dmax,
1768 "rw_steps": total_rw_steps,
1769 "d_info": d_info,
1770 "cws": combined_cws
1771 }
1772 if eff_cache_file:
1773 save_distance_cache(eff_cache_file)
1774
1775 if do_cws:
1776 return dist, d_info, cws
1777 return dist, d_info
1778
1779 finally:
1780 for f in temp_files:
1781 if f and os.path.exists(f):
1782 try: os.remove(f)
1783 except OSError: pass
1784
1785
1786def _write_nzlist_file(filepath: str, cws: List[List[int]]) -> None:
1787 """Writes codewords to a text file in NZLIST format (1-based indices)."""
1788 with open(filepath, "w") as f:
1789 f.write("%% NZLIST\n")
1790 f.write(f"% {len(cws)} codewords\n")
1791 for cw in cws:
1792 f.write(f"{len(cw)} " + " ".join(str(idx + 1) for idx in cw) + "\n")
1793
1794
1795def parse_cli_args(argv: List[str]) -> Dict[str, Any]:
1796 """Parses CLI arguments supporting both key=value pairs and standard --flags."""
1797 args: Dict[str, Any] = {
1798 "method": 3,
1799 "finH": None,
1800 "finG": None,
1801 "finL": None,
1802 "fin": None,
1803 "fdem": None,
1804 "Hx": None,
1805 "Hz": None,
1806 "Lx": None,
1807 "Lz": None,
1808 "finC": None,
1809 "outC": None,
1810 "dmin": 0,
1811 "dmax": 0,
1812 "wmin": 1,
1813 "wmax": 0,
1814 "smax": None,
1815 "start": None,
1816 "cbeg": None,
1817 "cend": None,
1818 "css": None,
1819 "dexp": 0,
1820 "steps": None,
1821 "threads": None,
1822 "timeout": 60.0,
1823 "dW": -1,
1824 "maxC": 0,
1825 "pmin": 0.0,
1826 "noscan": 0,
1827 "classical": -1,
1828 "seed": 0,
1829 "debug": 0,
1830 "solver": "dist_m4ri",
1831 "cache_file": "tmp_dist_cache.json",
1832 "use_cache": True,
1833 "do_cws": False,
1834 "verbose": False,
1835 }
1836
1837 i = 0
1838 while i < len(argv):
1839 arg = argv[i]
1840 if not arg:
1841 i += 1
1842 continue
1843
1844 if arg in ("-h", "--help", "help"):
1845 args["help"] = True
1846 i += 1
1847 continue
1848
1849 if arg in ("-v", "--verbose", "verbose", "-verbose"):
1850 args["verbose"] = True
1851 i += 1
1852 continue
1853
1854 if arg in ("--no-cache", "-no-cache", "nocache", "--nocache"):
1855 args["use_cache"] = False
1856 args["cache_file"] = None
1857 i += 1
1858 continue
1859
1860 if arg in ("--cws", "-cws", "cws", "do_cws=1", "--do_cws"):
1861 args["do_cws"] = True
1862 i += 1
1863 continue
1864
1865 key = None
1866 val = None
1867 if "=" in arg:
1868 key, val = arg.split("=", 1)
1869 if key.startswith("--"):
1870 key = key[2:]
1871 elif key.startswith("-"):
1872 key = key[1:]
1873 elif arg.startswith("--") or arg.startswith("-"):
1874 key = arg.lstrip("-")
1875 if i + 1 < len(argv) and not argv[i + 1].startswith("-") and "=" not in argv[i + 1]:
1876 val = argv[i + 1]
1877 i += 1
1878 else:
1879 val = "1"
1880 else:
1881 if os.path.exists(arg):
1882 if arg.endswith(".dem"):
1883 args["fdem"] = arg
1884 elif arg.endswith(".mmx") or arg.endswith(".mtx"):
1885 if args["finH"] is None:
1886 args["finH"] = arg
1887 elif args["finG"] is None and args["finL"] is None:
1888 args["finG"] = arg
1889 i += 1
1890 continue
1891
1892 if key:
1893 key_lower = key.lower()
1894 if key_lower in ("cache", "cache_file", "cachefile"):
1895 if val.lower() in ("0", "none", "false", "off", "no"):
1896 args["use_cache"] = False
1897 args["cache_file"] = None
1898 else:
1899 args["use_cache"] = True
1900 args["cache_file"] = val
1901 elif key_lower in ("fdem", "dem"):
1902 args["fdem"] = val
1903 elif key_lower == "finh":
1904 args["finH"] = val
1905 elif key_lower == "fing":
1906 args["finG"] = val
1907 elif key_lower == "finl":
1908 args["finL"] = val
1909 elif key_lower == "fin":
1910 args["fin"] = val
1911 elif key_lower in ("hx", "finhx"):
1912 args["Hx"] = val
1913 elif key_lower in ("hz", "finhz"):
1914 args["Hz"] = val
1915 elif key_lower in ("lx", "finlx"):
1916 args["Lx"] = val
1917 elif key_lower in ("lz", "finlz"):
1918 args["Lz"] = val
1919 elif key_lower == "finc":
1920 args["finC"] = val
1921 elif key_lower == "outc":
1922 args["outC"] = val
1923 args["do_cws"] = True
1924 elif key_lower in ("method", "m"):
1925 args["method"] = int(val)
1926 elif key_lower in ("dmin", "d_min"):
1927 args["dmin"] = int(val)
1928 elif key_lower in ("dmax", "d_max"):
1929 args["dmax"] = int(val)
1930 elif key_lower in ("wmin", "w_min"):
1931 args["wmin"] = int(val)
1932 elif key_lower in ("wmax", "w_max"):
1933 args["wmax"] = int(val)
1934 elif key_lower == "smax":
1935 args["smax"] = int(val)
1936 elif key_lower == "start":
1937 args["start"] = int(val)
1938 elif key_lower == "cbeg":
1939 args["cbeg"] = int(val)
1940 elif key_lower == "cend":
1941 args["cend"] = int(val)
1942 elif key_lower == "css":
1943 args["css"] = int(val)
1944 elif key_lower in ("dexp", "dest", "d_exp"):
1945 args["dexp"] = int(val)
1946 elif key_lower in ("steps", "num_steps", "nsteps"):
1947 args["steps"] = int(val)
1948 elif key_lower in ("threads", "num_threads", "t"):
1949 args["threads"] = int(val)
1950 elif key_lower in ("timeout", "time"):
1951 args["timeout"] = float(val)
1952 elif key_lower == "dw":
1953 args["dW"] = int(val)
1954 elif key_lower in ("maxc", "max_c"):
1955 args["maxC"] = int(val)
1956 elif key_lower == "pmin":
1957 args["pmin"] = float(val)
1958 elif key_lower == "noscan":
1959 args["noscan"] = int(val)
1960 elif key_lower == "classical":
1961 args["classical"] = int(val)
1962 elif key_lower == "seed":
1963 args["seed"] = int(val)
1964 elif key_lower in ("debug", "dbg"):
1965 args["debug"] = int(val)
1966 elif key_lower == "solver":
1967 args["solver"] = val
1968 elif key_lower in ("verbose", "v"):
1969 args["verbose"] = bool(int(val)) if val.isdigit() else (val.lower() not in ("0", "false", "no", "off"))
1970 elif key_lower == "cws":
1971 args["do_cws"] = bool(int(val)) if val.isdigit() else (val.lower() not in ("0", "false", "no"))
1972
1973 i += 1
1974
1975 # Auto-infer classical mode when not explicitly set (matching src/util_io.c)
1976 if args["classical"] == -1:
1977 if args["finG"] is not None or args["finL"] is not None or args["Hz"] is not None or args["Lz"] is not None or args["fdem"] is not None or args["fin"] is not None:
1978 args["classical"] = 0
1979 elif args["finH"] is not None or args["Hx"] is not None:
1980 args["classical"] = 1
1981
1982 return args
1983
1984
1985def print_cli_help() -> None:
1986 help_text = """dist_m4ri.py: Multithreaded distance calculator Python CLI
1987
1988Usage: dist_m4ri.py [key=val | --flag val ...]
1989
1990Options:
1991 fdem=FILE Detector Error Model input file (.dem)
1992 finH=FILE Parity check matrix input file (.mmx / .mtx)
1993 finG=FILE Generator matrix input file (.mmx / .mtx)
1994 finL=FILE Logical operator matrix input file (.mmx / .mtx)
1995 fin=PREFIX Prefix for check matrices (e.g. try -> tryX.mtx, tryZ.mtx)
1996 Hx=FILE, Hz=FILE CSS check matrices (alternative to finH/finL)
1997 Lx=FILE, Lz=FILE CSS logical operators (optional)
1998 method=N 1=RW, 2=CC, 3=Bracketing (default: 3)
1999 dmin=N Certified lower bound, inclusive (default: 0)
2000 dmax=N Known upper bound, inclusive (default: 0)
2001 wmin=N Minimum distance of interest (terminate early if cw of weight <= wmin is found in RW or CC, default: 1)
2002 wmax=N Maximum weight to search in CC
2003 smax=N Maximum syndrome weight for CC confinement profile
2004 start=N / cbeg=N Starting column index for CC scan
2005 cend=N Ending column index for CC scan
2006 dexp=N Expected distance estimate
2007 steps=N Maximum RW steps (default: 1000 in method 3)
2008 threads=N Worker threads (default: hardware concurrency)
2009 timeout=SEC Execution timeout in seconds (default: 60.0)
2010 dW=N Extra weight window above dmin to collect codewords
2011 maxC=N Maximum number of codewords to collect
2012 finC=FILE Input file with initial codewords (NZLIST format)
2013 outC=FILE File to output non-trivial codewords (NZLIST format)
2014 pmin=PROB Probability threshold for DEM errors
2015 noscan=1 Skip CC scan loop
2016 classical=1 Force classical mode (0 for CSS / quantum)
2017 seed=N Random seed
2018 debug=N Debug bitmask (e.g. 1, 2, 4)
2019 solver=NAME 'dist_m4ri' (default) or 'codedistance'
2020 cache=FILE Persistent JSON cache file (default: tmp_dist_cache.json)
2021 --no-cache / nocache Disable persistent JSON caching
2022 --verbose / -v Output detailed explanations of bounds, steps, and cache status
2023 --cws Collect and output non-trivial codewords
2024"""
2025 print(help_text)
2026
2027
2028def main(argv: Optional[List[str]] = None) -> int:
2029 if argv is None:
2030 argv = sys.argv[1:]
2031
2032 args = parse_cli_args(argv)
2033
2034 if args.get("help") or (not args.get("fdem") and not args.get("finH") and not args.get("Hx") and not args.get("fin")):
2036 return 0
2037
2038 # When finC and outC are identical, empty or non-existent file is silently ignored (with a warning if verbose)
2039 args["finC"] = check_finc_outc(args["finC"], args["outC"], verbose=args["verbose"])
2040
2041 cache_file = args["cache_file"] if args["use_cache"] else None
2042 if not args["use_cache"]:
2044
2045 try:
2046 if args["fdem"]:
2048 dem=args["fdem"],
2049 method=args["method"],
2050 threads=args["threads"],
2051 timeout=args["timeout"],
2052 num_steps=args["steps"],
2053 d_exp=args["dexp"],
2054 dmin=args["dmin"],
2055 dmax=args["dmax"],
2056 wmin=args["wmin"],
2057 wmax=args["wmax"],
2058 smax=args["smax"],
2059 start=args["start"],
2060 cbeg=args["cbeg"],
2061 cend=args["cend"],
2062 noscan=args["noscan"],
2063 dW=args["dW"],
2064 maxC=args["maxC"],
2065 pmin=args["pmin"],
2066 finC=args["finC"],
2067 outC=args["outC"],
2068 do_cws=args["do_cws"] or (args["outC"] is not None),
2069 cache_file=cache_file,
2070 solver=args["solver"],
2071 seed=args["seed"],
2072 debug=args["debug"],
2073 verbose=args["verbose"]
2074 )
2075 if args["do_cws"] or (args["outC"] is not None):
2076 dist, d_info, cws = res
2077 if args["outC"]:
2078 _write_nzlist_file(args["outC"], cws)
2079 else:
2080 dist, d_info = res
2081
2082 if args["verbose"]:
2083 print("=== DEM Distance Results ===")
2084 print(explain_bounds(d_info, method=args["method"], label="DEM"))
2085 print(f" Summary bounds: {format_bounds_str(d_info)}")
2086 print(format_bounds_str(d_info))
2087 return 0
2088
2089 if args["Hx"] is not None or args["Hz"] is not None:
2091 Hx=args["Hx"],
2092 Hz=args["Hz"],
2093 Lx=args["Lx"],
2094 Lz=args["Lz"],
2095 method=args["method"],
2096 threads=args["threads"],
2097 timeout=args["timeout"],
2098 num_steps=args["steps"],
2099 d_exp=args["dexp"],
2100 dmin=args["dmin"],
2101 dmax=args["dmax"],
2102 wmin=args["wmin"],
2103 wmax=args["wmax"],
2104 smax=args["smax"],
2105 start=args["start"],
2106 cbeg=args["cbeg"],
2107 cend=args["cend"],
2108 noscan=args["noscan"],
2109 dW=args["dW"],
2110 maxC=args["maxC"],
2111 finC=args["finC"],
2112 outC=args["outC"],
2113 do_cws=args["do_cws"] or (args["outC"] is not None),
2114 cache_file=cache_file,
2115 solver=args["solver"],
2116 seed=args["seed"],
2117 debug=args["debug"],
2118 verbose=args["verbose"]
2119 )
2120 if args["do_cws"] or (args["outC"] is not None):
2121 dist, dx_info, dz_info, cws_x, cws_z = res
2122 if args["outC"]:
2123 _write_nzlist_file(args["outC"], (cws_x or []) + (cws_z or []))
2124 else:
2125 dist, dx_info, dz_info = res
2126
2127 exact_tag = " (exact)" if (dx_info and dx_info[0] > 0 and dx_info[0] == dx_info[1] and dz_info and dz_info[0] > 0 and dz_info[0] == dz_info[1]) else ""
2128
2129 if args["verbose"]:
2130 print("=== CSS Quantum Code Distance Results ===")
2131 if dx_info:
2132 print("--- X-Component Distance (dX) ---")
2133 print(explain_bounds(dx_info, method=args["method"], label="dX"))
2134 print(f" dX bounds: {format_bounds_str(dx_info)}")
2135 if dz_info:
2136 print("--- Z-Component Distance (dZ) ---")
2137 print(explain_bounds(dz_info, method=args["method"], label="dZ"))
2138 print(f" dZ bounds: {format_bounds_str(dz_info)}")
2139 print("--- Overall CSS Code Distance ---")
2140 print(f" d = min(dX, dZ) = {dist}{exact_tag}")
2141
2142 dx_str = format_bounds_str(dx_info) if dx_info else "none"
2143 dz_str = format_bounds_str(dz_info) if dz_info else "none"
2144 print(f"dX: {dx_str} dZ: {dz_str} (d = {dist}){exact_tag}")
2145 return 0
2146
2147 # Handle fin prefix (e.g. fin=examples/try -> tryX.mtx and tryZ.mtx)
2148 finH = args["finH"]
2149 finG = args["finG"]
2150 if args["fin"]:
2151 if finH is None: finH = f"{args['fin']}X.mtx"
2152 if finG is None and args["finL"] is None: finG = f"{args['fin']}Z.mtx"
2153
2154 # Quantum single-sided distance (finH with finG or finL, or classical=0)
2155 if finH and (finG is not None or args["finL"] is not None or args["classical"] == 0):
2157 H=finH,
2158 G=finG,
2159 L=args["finL"],
2160 method=args["method"],
2161 threads=args["threads"],
2162 timeout=args["timeout"],
2163 num_steps=args["steps"],
2164 d_exp=args["dexp"],
2165 dmin=args["dmin"],
2166 dmax=args["dmax"],
2167 wmin=args["wmin"],
2168 wmax=args["wmax"],
2169 smax=args["smax"],
2170 start=args["start"],
2171 cbeg=args["cbeg"],
2172 cend=args["cend"],
2173 noscan=args["noscan"],
2174 dW=args["dW"],
2175 maxC=args["maxC"],
2176 finC=args["finC"],
2177 outC=args["outC"],
2178 do_cws=args["do_cws"] or (args["outC"] is not None),
2179 return_info=True,
2180 cache_file=cache_file,
2181 solver=args["solver"],
2182 seed=args["seed"],
2183 debug=args["debug"],
2184 verbose=args["verbose"]
2185 )
2186 if args["do_cws"] or (args["outC"] is not None):
2187 dist, d_info, cws = res
2188 if args["outC"]:
2189 _write_nzlist_file(args["outC"], cws)
2190 else:
2191 dist, d_info = res
2192
2193 if args["verbose"]:
2194 print("=== Quantum Code Distance Results (Single-Sided) ===")
2195 print(explain_bounds(d_info, method=args["method"], label="Quantum"))
2196 print(f" Summary bounds: {format_bounds_str(d_info)}")
2197 print(format_bounds_str(d_info))
2198 return 0
2199
2200 # Classical distance (finH only or classical=1)
2201 if finH:
2203 H=finH,
2204 method=args["method"],
2205 threads=args["threads"],
2206 timeout=args["timeout"],
2207 num_steps=args["steps"],
2208 d_exp=args["dexp"],
2209 dmin=args["dmin"],
2210 dmax=args["dmax"],
2211 wmin=args["wmin"],
2212 wmax=args["wmax"],
2213 smax=args["smax"],
2214 start=args["start"],
2215 cbeg=args["cbeg"],
2216 cend=args["cend"],
2217 noscan=args["noscan"],
2218 dW=args["dW"],
2219 maxC=args["maxC"],
2220 finC=args["finC"],
2221 outC=args["outC"],
2222 do_cws=args["do_cws"] or (args["outC"] is not None),
2223 return_info=True,
2224 cache_file=cache_file,
2225 solver=args["solver"],
2226 seed=args["seed"],
2227 debug=args["debug"],
2228 verbose=args["verbose"]
2229 )
2230 if args["do_cws"] or (args["outC"] is not None):
2231 dist, d_info, cws = res
2232 if args["outC"]:
2233 _write_nzlist_file(args["outC"], cws)
2234 else:
2235 dist, d_info = res
2236
2237 if args["verbose"]:
2238 print("=== Classical Code Distance Results ===")
2239 print(explain_bounds(d_info, method=args["method"], label="Classical"))
2240 print(f" Summary bounds: {format_bounds_str(d_info)}")
2241 print(format_bounds_str(d_info))
2242 return 0
2243 except ValueError as e:
2244 sys.stderr.write(f"Error: {e}\n")
2245 return 1
2246
2247 return 0
2248
2249
2250if __name__ == "__main__":
2251 sys.exit(main())
bool __eq__(self, Any other)
Definition dist_m4ri.py:511
__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)
Definition dist_m4ri.py:483
__getitem__(self, int index)
Definition dist_m4ri.py:527
str find_dist_m4ri_binary(Optional[str] custom_path=None)
Definition dist_m4ri.py:398
int main(Optional[List[str]] argv=None)
str create_unique_file(Union[str, Path] directory="tmp", str extension=".tmp")
Definition dist_m4ri.py:347
str format_bounds_str(List[int] bounds)
Definition dist_m4ri.py:189
List[int] format_bounds_list(int dmin, int dmax, int num_rw)
Definition dist_m4ri.py:168
Any __getattr__(str name)
Definition dist_m4ri.py:56
None set_distance_cache_file(Optional[Union[str, Path]] filepath=None)
Definition dist_m4ri.py:82
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, Optional[int] steps=None, Optional[int] threads=None, float timeout=60.0, Optional[int] smax=None, Optional[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, Optional[str] outC=None, int seed=0, int debug=0, Optional[threading.Event] stop_event=None)
Definition dist_m4ri.py:594
Dict[str, Any] get_distance_cache()
Definition dist_m4ri.py:162
str _matrix_to_file(matrix, str extension=".mtx", str temp_dir="tmp")
Definition dist_m4ri.py:666
None _write_nzlist_file(str filepath, List[List[int]] cws)
Dict[str, Any] parse_cli_args(List[str] argv)
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 wmin=1, int wmax=0, Optional[int] smax=None, Optional[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)
Definition dist_m4ri.py:718
Tuple[int, int, int] parse_dist_m4ri_output(str stdout)
Definition dist_m4ri.py:427
None clear_distance_cache(Optional[Union[str, Path]] cache_file=None, bool clear_file=False)
Definition dist_m4ri.py:136
List[List[int]] read_sparse_vectors(str filepath)
Definition dist_m4ri.py:355
str get_sparse_array_state(A)
Definition dist_m4ri.py:318
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)
Definition dist_m4ri.py:253
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 wmin=1, int wmax=0, Optional[int] smax=None, Optional[int] start=None, Optional[int] cbeg=None, Optional[int] cend=None, int noscan=0, int dW=-1, int maxC=0, float pmin=0.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="UndetectableErrorStim", Optional[Dict[str, Any]] codedistance_params=None, int seed=0, int debug=0, bool verbose=False, **kwargs)
None enable_distance_cache()
Definition dist_m4ri.py:150
None save_distance_cache(Optional[Union[str, Path]] filepath=None)
Definition dist_m4ri.py:112
Optional[str] check_finc_outc(Optional[str] finC, Optional[str] outC, bool verbose=False)
Definition dist_m4ri.py:544
_get_codedistance()
Definition dist_m4ri.py:32
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 wmin=1, int wmax=0, Optional[int] smax=None, Optional[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)
Definition dist_m4ri.py:955
Dict[str, Any] load_distance_cache(Optional[Union[str, Path]] filepath=None)
Definition dist_m4ri.py:95
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 wmin=1, int wmax=0, Optional[int] smax=None, Optional[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, **kwargs)
str explain_bounds(List[int] bounds, Optional[int] method=None, str label="")
Definition dist_m4ri.py:197
None print_cli_help()
None disable_distance_cache()
Definition dist_m4ri.py:156