Skip to content

Commit 5929c58

Browse files
committed
updates to python prototypes and optimize the cc trellis prototype
1 parent 53ca8da commit 5929c58

10 files changed

Lines changed: 5165 additions & 514 deletions

src/py/astar/multipass_beam_decoder.py

Lines changed: 1189 additions & 0 deletions
Large diffs are not rendered by default.

src/py/astar/plot_log.py

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import sys
2+
import os
3+
import matplotlib.pyplot as plt
4+
import numpy as np
5+
6+
def analyze_log(filename):
7+
min_masses = []
8+
errors = []
9+
10+
current_errors = 0
11+
pending_error_diff = None
12+
13+
# Parse the log file line by line
14+
with open(filename, 'r') as f:
15+
for line in f:
16+
parts = line.split()
17+
if not parts:
18+
continue
19+
20+
if parts[0] == "num_shots":
21+
# Find 'num_errors' and grab the value two indices over (past the '=')
22+
idx = parts.index("num_errors")
23+
errs = int(parts[idx + 2])
24+
25+
# Calculate errors for this specific shot and store it as pending
26+
pending_error_diff = errs - current_errors
27+
current_errors = errs
28+
29+
elif parts[0] == "branch_masses":
30+
# Parse out the values from obs0=... and obs1=...
31+
obs0 = float(parts[1].split("=")[1])
32+
obs1 = float(parts[2].split("=")[1])
33+
norm = obs0 + obs1
34+
if norm == 0:
35+
obs0 = 0.5
36+
obs1 = 0.5
37+
else:
38+
obs0 /= norm
39+
obs1 /= norm
40+
41+
# Only append if we just successfully parsed a num_shots line
42+
if pending_error_diff is not None:
43+
min_masses.append(min(obs0, obs1))
44+
errors.append(pending_error_diff)
45+
46+
# Reset pending diff to ensure we don't double-count
47+
pending_error_diff = None
48+
49+
min_masses = np.array(min_masses)
50+
errors = np.array(errors)
51+
52+
if len(min_masses) == 0:
53+
print("No valid shot data found in the file.")
54+
return
55+
56+
# To calculate how error rates change based on our cutoff,
57+
# we sort the shots from most certain (lowest min_mass) to least certain.
58+
sorted_idx = np.argsort(min_masses)
59+
sorted_masses = min_masses[sorted_idx]
60+
sorted_errors = errors[sorted_idx]
61+
62+
N = len(sorted_masses)
63+
64+
# K represents the number of shots we *accept* (1 to N)
65+
K_arr = np.arange(1, N + 1)
66+
67+
# Cumulative errors in the accepted subset of shots
68+
accepted_errors = np.cumsum(sorted_errors)
69+
70+
# Error rate = (errors in accepted subset) / (number of accepted shots)
71+
error_rates = accepted_errors / K_arr
72+
73+
# Rejection rate = (number of rejected shots) / (total shots)
74+
rejection_rates = (N - K_arr) / N
75+
76+
# ------------------
77+
# Pre-process for Log Scale Histogram
78+
# ------------------
79+
# Find the smallest non-zero mass. If everything is 0, default to 1e-10
80+
if np.any(min_masses > 0):
81+
min_nonzero = np.min(min_masses[min_masses > 0])
82+
# Set exact 0s to half the minimum non-zero value so they fall in the leftmost bin
83+
epsilon = min_nonzero / 2.0
84+
else:
85+
epsilon = 1e-10
86+
87+
# Replace 0s with epsilon
88+
masses_for_hist = np.where(min_masses == 0, epsilon, min_masses)
89+
90+
# Safely get max mass to define bin edges
91+
max_mass = np.max(masses_for_hist)
92+
if max_mass == epsilon:
93+
max_mass = epsilon * 10 # Fallback in case all values were 0
94+
95+
# Generate 50 logarithmically spaced bins
96+
log_bins = np.logspace(np.log10(epsilon), np.log10(max_mass), 50)
97+
98+
# ------------------
99+
# Create the Figures
100+
# ------------------
101+
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
102+
103+
# Plot 1: Distribution of min masses (Log Scale X)
104+
axes[0].hist(masses_for_hist, bins=log_bins, color='skyblue', edgecolor='black')
105+
axes[0].set_xscale('log')
106+
axes[0].set_xlabel('Min Mass (Log Scale, 0s in leftmost bin)')
107+
axes[0].set_ylabel('Frequency')
108+
axes[0].set_title('Distribution of Min Masses')
109+
110+
# Plot 2: Logical error rate vs Min Mass Cutoff
111+
axes[1].plot(sorted_masses, error_rates, color='purple', lw=2)
112+
axes[1].set_xlabel('Min Mass Cutoff (Threshold)')
113+
axes[1].set_ylabel('Logical Error Rate (Accepted Shots)')
114+
axes[1].set_title('Error Rate vs Min Mass Cutoff')
115+
axes[1].grid(True, linestyle='--', alpha=0.7)
116+
117+
# Plot 3: Logical error rate vs Rejection rate
118+
axes[2].plot(rejection_rates, error_rates, color='red', lw=2)
119+
axes[2].set_xlabel('Rejection Rate')
120+
axes[2].set_ylabel('Logical Error Rate (Accepted Shots)')
121+
axes[2].set_title('Error Rate vs Rejection Rate')
122+
axes[2].grid(True, linestyle='--', alpha=0.7)
123+
axes[2].set_xlim(0, 1)
124+
125+
plt.tight_layout()
126+
127+
# Generate output filename based on input filename
128+
base_name = os.path.splitext(os.path.basename(filename))[0]
129+
out_filename = f"{base_name}_analysis.png"
130+
131+
# Save to disk instead of displaying
132+
plt.savefig(out_filename, dpi=300, bbox_inches='tight')
133+
print(f"Success! Plot saved to disk as: {out_filename}")
134+
135+
if __name__ == "__main__":
136+
if len(sys.argv) < 2:
137+
print(f"Usage: python {sys.argv[0]} <log_file.txt>")
138+
else:
139+
analyze_log(sys.argv[1])

0 commit comments

Comments
 (0)