#!/usr/bin/env python3 """ Read any percentile out of the raw lazy/super-lazy-eval histograms persisted by eval.c's _PersistRawHistogramsToFile (called automatically every time a DUMP_RESIDUAL_SAMPLES build runs `calibrate residual`). Usage: python3 hist_percentile.py [percentiles...] python3 hist_percentile.py residual_raw_histograms_guess2_30k.txt python3 hist_percentile.py residual_raw_histograms_guess2_30k.txt 0.93 0.97 0.985 With no percentile arguments, prints a default useful spread for every bucket of all three histograms (residual, sl_residual, king). With percentile arguments (0.0-1.0), prints just those, letting you probe any exact percentile the fixed comment-block charts in eval.c didn't happen to include -- no re-running the 30k pool required, as long as this file (or a fresher one) still exists. File format (one line per histogram/bucket): ... Bin i covers [RESID_HIST_MIN + i*RESID_HIST_BIN_WIDTH, RESID_HIST_MIN + (i+1)*RESID_HIST_BIN_WIDTH). These constants are echoed in the file's own header comment and also hardcoded below (must match eval.c's #defines -- they haven't changed since this was built, but check if eval.c's RESID_HIST_* macros ever do). """ import sys BIN_WIDTH = 20 MIN_VAL = -900 DEFAULT_PERCENTILES = [0.01, 0.02, 0.05, 0.10, 0.15, 0.50, 0.85, 0.90, 0.95, 0.98, 0.99] def load(path): hists = {} with open(path) as f: for line in f: if line.startswith('#') or not line.strip(): continue parts = line.split() name, bucket = parts[0], int(parts[1]) mn, mx = int(parts[2]), int(parts[3]) bins = list(map(int, parts[4:])) hists[(name, bucket)] = (mn, mx, bins) return hists def percentile(bins, total, p): target = p * total running = 0 for i, c in enumerate(bins): running += c if running >= target: return MIN_VAL + i * BIN_WIDTH + BIN_WIDTH // 2 return MIN_VAL + len(bins) * BIN_WIDTH def main(): if len(sys.argv) < 2: print(__doc__) sys.exit(1) path = sys.argv[1] percentiles = [float(x) for x in sys.argv[2:]] or DEFAULT_PERCENTILES hists = load(path) names = sorted(set(name for name, _ in hists.keys())) for name in names: print(f"--- {name} ---") for b in range(8): if (name, b) not in hists: continue mn, mx, bins = hists[(name, b)] total = sum(bins) if total == 0: print(f" bucket {b}: no samples") continue vals = " ".join(f"p{p*100:g}={percentile(bins, total, p)}" for p in percentiles) print(f" bucket {b}: n={total} min={mn} {vals} max={mx}") if __name__ == "__main__": main()