1+ # viz/to_json.py
12# Copyright 2025 Google LLC
23#
34# Licensed under the Apache License, Version 2.0 (the "License");
1718import re
1819import numpy as np
1920
21+ INT_RE = re .compile (r'-?\d+' )
22+
23+ def parse_int_list (text ):
24+ """Extract all integers from a string, tolerant to spaces/commas/brackets."""
25+ return [int (x ) for x in INT_RE .findall (text )]
26+
2027def parse_implicit_list (line , prefix ):
2128 if not line .startswith (prefix ):
2229 raise ValueError (f"Expected line to start with '{ prefix } ', got: { line } " )
2330 list_part = line [len (prefix ):].strip ().rstrip (',' )
2431 if not list_part :
2532 return []
26- return [int (x .strip ()) for x in list_part .split (',' ) if x .strip ()]
33+ # Be tolerant: accept "1, 2, 3" or "1 2 3"
34+ return parse_int_list (list_part )
2735
2836def parse_logfile (filepath ):
2937 detector_coords = {}
@@ -38,21 +46,37 @@ def parse_logfile(filepath):
3846 line = lines [i ].strip ()
3947
4048 if not any (line .startswith (s ) for s in ['Error' , 'Detector' , 'activated_errors' , 'activated_detectors' ]):
41- continue
49+ i += 1
50+ continue
4251
4352 if line .startswith ("Detector D" ):
44- match = re .match (r'Detector D(\d+) coordinate \(([-\d.]+), ([-\d.]+), ([-\d.]+)\)' , line )
53+ # Example: "Detector D123 coordinate (1.0, 2.0, 3.0)"
54+ match = re .match (
55+ r'Detector D(\d+)\s+coordinate\s*\(\s*([-\d.]+)\s*,\s*([-\d.]+)\s*,\s*([-\d.]+)\s*\)' ,
56+ line
57+ )
4558 if match :
4659 idx = int (match .group (1 ))
4760 coord = tuple (float (match .group (j )) for j in range (2 , 5 ))
4861 detector_coords [idx ] = coord
4962
5063 elif line .startswith ("Error{" ):
51- match = re .search (r'Symptom\{([^\}]+)\}' , line )
52- if match :
53- dets = match .group (1 ).split ()
54- det_indices = [int (d [1 :]) for d in dets if d .startswith ('D' )]
55- error_to_detectors .append (det_indices )
64+ # New format: Error{..., symptom=Symptom{detectors=[75 89 93 100], observables=[...]}}
65+ # Fallback: old format with "D###" tokens inside Symptom{...}
66+ dets = []
67+
68+ m_detlist = re .search (r'detectors=\[([^\]]*)\]' , line )
69+ if m_detlist :
70+ dets = parse_int_list (m_detlist .group (1 ))
71+ else :
72+ # Old fallback: scrape Symptom{...} and look for D###
73+ m_sym = re .search (r'Symptom\{([^}]*)\}' , line )
74+ if m_sym :
75+ tokens = m_sym .group (1 ).split ()
76+ dets = [int (t [1 :]) for t in tokens if t .startswith ('D' ) and t [1 :].isdigit ()]
77+
78+ # Store (even if empty—we keep the index alignment with errors)
79+ error_to_detectors .append (dets )
5680
5781 elif line .startswith ("activated_errors" ):
5882 try :
@@ -62,34 +86,39 @@ def parse_logfile(filepath):
6286 activated_errors = parse_implicit_list (error_line , "activated_errors =" )
6387 activated_dets = parse_implicit_list (det_line , "activated_detectors =" )
6488
65- frame = {
89+ frames . append ( {
6690 "activated" : activated_dets ,
6791 "activated_errors" : activated_errors
68- }
69- frames .append (frame )
70- i += 1
92+ })
93+
94+ # We consumed two lines in this block
95+ i += 2
96+ continue # skip the unconditional i+=1 below (already advanced)
7197 except Exception as e :
7298 print (f"\n ⚠️ Error parsing frame at lines { i } -{ i + 1 } : { e } " )
7399 print (f" { lines [i ].strip ()} " )
74100 print (f" { lines [i + 1 ].strip () if i + 1 < len (lines ) else '' } " )
101+
75102 i += 1
76103
77104 if not detector_coords :
78105 raise RuntimeError ("No detectors parsed!" )
79106
107+ # Center detector coordinates
80108 coords_array = np .array (list (detector_coords .values ()))
81109 mean_coord = coords_array .mean (axis = 0 )
82110 for k in detector_coords :
83111 detector_coords [k ] = (np .array (detector_coords [k ]) - mean_coord ).tolist ()
84112
113+ # Error coordinates as mean of their detectors (if known)
85114 error_coords = {}
86- for i , det_list in enumerate (error_to_detectors ):
115+ for ei , det_list in enumerate (error_to_detectors ):
87116 try :
88117 pts = np .array ([detector_coords [d ] for d in det_list if d in detector_coords ])
89118 if len (pts ) > 0 :
90- error_coords [i ] = pts .mean (axis = 0 ).tolist ()
119+ error_coords [ei ] = pts .mean (axis = 0 ).tolist ()
91120 except KeyError as e :
92- print (f"⚠️ Skipping error { i } : unknown detector { e } " )
121+ print (f"⚠️ Skipping error { ei } : unknown detector { e } " )
93122
94123 error_to_detectors_dict = {str (i ): dets for i , dets in enumerate (error_to_detectors )}
95124
0 commit comments