Leplanner / code /scripts /summarize_results.py
nottygian's picture
Push scripts
dc9f917 verified
Raw
History Blame Contribute Delete
4.26 kB
"""Summarize eval runs into the tables the design doc asks for.
Reads the jsonl written by ``scripts/eval_controller.py``. The central claim
is matching CEM's success at a fraction of the predictor evaluations, so cost
is reported as predictor rows per episode alongside wall-clock: CEM batches
its samples, so the two ratios differ by an order of magnitude and only
stating both is honest.
Rows are compared against the CEM baseline *at the same execution length*.
Comparing against CEM at receding-horizon 1 flatters the controller, because
the fixed-terminal objective makes every planner procrastinate under short
execution.
"""
import argparse
import json
from pathlib import Path
def refinements(row):
k = row.get('refinements')
if k is not None:
return k
if row['planner'].startswith('controller'):
return int(row['planner'].split('_K')[-1])
return None
def variant(row):
"""Which trained checkpoint a row came from."""
ckpt = (row.get('checkpoint') or '').replace('\\', '/')
for name in ('terminal_only', 'no_support',
'ah_hold0.5', 'ah_hold0.0', 'ah_hold1.0'):
if name in ckpt:
return name
return 'main'
def label(row):
tag = row['planner']
if row.get('receding_horizon', 1) != 1:
tag += f'+exec{row["receding_horizon"]}'
v = variant(row)
if v != 'main':
tag += f'[{v}]'
return tag
def main():
p = argparse.ArgumentParser()
p.add_argument('--results', default='data/runs/eval/results.jsonl')
args = p.parse_args()
rows = [
json.loads(x)
for x in Path(args.results).read_text().splitlines()
if x.strip()
]
if not rows:
print('no results yet')
return
# baseline per execution length, so cost ratios are never computed across
# schedules that make different numbers of solver calls
cem_by_exec = {
r.get('receding_horizon', 1): r
for r in rows if r['planner'].startswith('cem')
}
head = (
f'{"planner":<34}{"exec":>5}{"success%":>10}{"first_d":>9}'
f'{"rows/ep":>10}{"sec/ep":>8}'
)
print(head)
print('-' * len(head))
for r in rows:
d = r.get('first_terminal_distance')
print(
f'{label(r):<34}{r.get("receding_horizon", 1):>5}'
f'{r["success_rate"]:>10.1f}'
f'{(f"{d:.4f}" if d is not None else "-"):>9}'
f'{r["predictor_rows_per_episode"]:>10.0f}'
f'{r["seconds_per_episode"]:>8.2f}'
)
# --- K x execution grid, per trained variant ---------------------------
grids = {}
for r in rows:
if not r['planner'].startswith('controller'):
continue
grids.setdefault(variant(r), {})[
(refinements(r), r.get('receding_horizon', 1))
] = r['success_rate']
for name, grid in grids.items():
ks = sorted({k for k, _ in grid})
ms = sorted({m for _, m in grid})
print(f'\nsuccess% [{name}] — refinements K (rows) x execution (cols)')
print(f'{"K":>3}' + ''.join(f'{m:>8}' for m in ms))
for k in ks:
cells = ''.join(
f'{grid[(k, m)]:>8.1f}' if (k, m) in grid else f'{"-":>8}'
for m in ms
)
print(f'{k:>3}{cells}')
# --- matched-execution comparison --------------------------------------
print('\nvs CEM at the same execution length:')
for r in rows:
if not r['planner'].startswith('controller'):
continue
base = cem_by_exec.get(r.get('receding_horizon', 1))
if base is None:
continue
rows_x = base['predictor_rows_per_episode'] / max(
r['predictor_rows_per_episode'], 1e-9
)
time_x = base['seconds_per_episode'] / max(
r['seconds_per_episode'], 1e-9
)
print(
f' {label(r):<36}{r["success_rate"]:>6.1f}% vs '
f'{base["success_rate"]:>5.1f}% '
f'{rows_x:>7.0f}x fewer rows {time_x:>5.1f}x faster'
)
if __name__ == '__main__':
main()