diff --git a/viz/to_json.py b/viz/to_json.py index d28671c..a092980 100644 --- a/viz/to_json.py +++ b/viz/to_json.py @@ -1,3 +1,4 @@ +# viz/to_json.py # Copyright 2025 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,13 +18,20 @@ import re import numpy as np +INT_RE = re.compile(r'-?\d+') + +def parse_int_list(text): + """Extract all integers from a string, tolerant to spaces/commas/brackets.""" + return [int(x) for x in INT_RE.findall(text)] + def parse_implicit_list(line, prefix): if not line.startswith(prefix): raise ValueError(f"Expected line to start with '{prefix}', got: {line}") list_part = line[len(prefix):].strip().rstrip(',') if not list_part: return [] - return [int(x.strip()) for x in list_part.split(',') if x.strip()] + # Be tolerant: accept "1, 2, 3" or "1 2 3" + return parse_int_list(list_part) def parse_logfile(filepath): detector_coords = {} @@ -38,21 +46,37 @@ def parse_logfile(filepath): line = lines[i].strip() if not any(line.startswith(s) for s in ['Error', 'Detector', 'activated_errors', 'activated_detectors']): - continue + i += 1 + continue if line.startswith("Detector D"): - match = re.match(r'Detector D(\d+) coordinate \(([-\d.]+), ([-\d.]+), ([-\d.]+)\)', line) + # Example: "Detector D123 coordinate (1.0, 2.0, 3.0)" + match = re.match( + r'Detector D(\d+)\s+coordinate\s*\(\s*([-\d.]+)\s*,\s*([-\d.]+)\s*,\s*([-\d.]+)\s*\)', + line + ) if match: idx = int(match.group(1)) coord = tuple(float(match.group(j)) for j in range(2, 5)) detector_coords[idx] = coord elif line.startswith("Error{"): - match = re.search(r'Symptom\{([^\}]+)\}', line) - if match: - dets = match.group(1).split() - det_indices = [int(d[1:]) for d in dets if d.startswith('D')] - error_to_detectors.append(det_indices) + # New format: Error{..., symptom=Symptom{detectors=[75 89 93 100], observables=[...]}} + # Fallback: old format with "D###" tokens inside Symptom{...} + dets = [] + + m_detlist = re.search(r'detectors=\[([^\]]*)\]', line) + if m_detlist: + dets = parse_int_list(m_detlist.group(1)) + else: + # Old fallback: scrape Symptom{...} and look for D### + m_sym = re.search(r'Symptom\{([^}]*)\}', line) + if m_sym: + tokens = m_sym.group(1).split() + dets = [int(t[1:]) for t in tokens if t.startswith('D') and t[1:].isdigit()] + + # Store (even if empty—we keep the index alignment with errors) + error_to_detectors.append(dets) elif line.startswith("activated_errors"): try: @@ -62,34 +86,39 @@ def parse_logfile(filepath): activated_errors = parse_implicit_list(error_line, "activated_errors =") activated_dets = parse_implicit_list(det_line, "activated_detectors =") - frame = { + frames.append({ "activated": activated_dets, "activated_errors": activated_errors - } - frames.append(frame) - i += 1 + }) + + # We consumed two lines in this block + i += 2 + continue # skip the unconditional i+=1 below (already advanced) except Exception as e: print(f"\n⚠️ Error parsing frame at lines {i}-{i+1}: {e}") print(f" {lines[i].strip()}") print(f" {lines[i+1].strip() if i+1 < len(lines) else ''}") + i += 1 if not detector_coords: raise RuntimeError("No detectors parsed!") + # Center detector coordinates coords_array = np.array(list(detector_coords.values())) mean_coord = coords_array.mean(axis=0) for k in detector_coords: detector_coords[k] = (np.array(detector_coords[k]) - mean_coord).tolist() + # Error coordinates as mean of their detectors (if known) error_coords = {} - for i, det_list in enumerate(error_to_detectors): + for ei, det_list in enumerate(error_to_detectors): try: pts = np.array([detector_coords[d] for d in det_list if d in detector_coords]) if len(pts) > 0: - error_coords[i] = pts.mean(axis=0).tolist() + error_coords[ei] = pts.mean(axis=0).tolist() except KeyError as e: - print(f"⚠️ Skipping error {i}: unknown detector {e}") + print(f"⚠️ Skipping error {ei}: unknown detector {e}") error_to_detectors_dict = {str(i): dets for i, dets in enumerate(error_to_detectors)}