SDK-Sovereign / scripts /make_plots.py
ishansurdi's picture
Implement Phase 8 plots and Phase 9 play demo plumbing
8aca626
Raw History Blame Contribute Delete
5.54 kB
"""Rebuild Phase 8 plots from eval_results.json without re-running the models."""
from __future__ import annotations
import argparse
import json
import os
import statistics
from collections import defaultdict
from pathlib import Path
from typing import Any, Iterable
def _load_eval_results(path: Path) -> dict[str, list[dict[str, Any]]]:
"""Load baseline/trained eval results from disk."""
payload = json.loads(path.read_text(encoding="utf-8"))
baseline = payload.get("baseline")
trained = payload.get("trained")
if not isinstance(baseline, list) or not isinstance(trained, list):
raise ValueError("eval_results.json must contain 'baseline' and 'trained' lists")
return {"baseline": baseline, "trained": trained}
def _safe_mean(values: Iterable[float]) -> float:
"""Return the arithmetic mean or zero for an empty iterable."""
values = list(values)
return statistics.mean(values) if values else 0.0
def _write_placeholder_curve(plot_path: Path, role: str) -> None:
"""Write an honest placeholder plot when WandB metrics are unavailable."""
import matplotlib.pyplot as plt
plt.figure(figsize=(8, 4))
plt.text(
0.5,
0.5,
f"No training curve available for {role}.\nRun Phase 7 with WandB or export metrics first.",
ha="center",
va="center",
fontsize=12,
)
plt.axis("off")
plt.title(f"GRPO training - {role}")
plt.tight_layout()
plt.savefig(plot_path, dpi=150, bbox_inches="tight")
plt.close()
def build_plots(eval_results_path: Path, plots_dir: Path) -> list[Path]:
"""Generate the six PRD plot artifacts from saved evaluation results."""
import matplotlib.pyplot as plt
import numpy as np
results = _load_eval_results(eval_results_path)
baseline_results = results["baseline"]
trained_results = results["trained"]
plots_dir.mkdir(parents=True, exist_ok=True)
created: list[Path] = []
b_rate = sum(bool(item.get("success")) for item in baseline_results) / max(len(baseline_results), 1)
t_rate = sum(bool(item.get("success")) for item in trained_results) / max(len(trained_results), 1)
pass_rate_path = plots_dir / "pass_rate_baseline_vs_trained.png"
plt.figure(figsize=(6, 4))
plt.bar(["Baseline (untrained)", "Trained (two LoRAs)"], [b_rate, t_rate], color=["#bbbbbb", "#1f77b4"])
plt.ylabel("Pass rate (all tests passed)")
plt.title("SDK-Sovereign - pass rate")
plt.ylim(0, 1)
for index, value in enumerate([b_rate, t_rate]):
plt.text(index, value + 0.02, f"{value:.0%}", ha="center", fontweight="bold")
plt.tight_layout()
plt.savefig(pass_rate_path, dpi=150, bbox_inches="tight")
plt.close()
created.append(pass_rate_path)
mean_reward_path = plots_dir / "mean_reward.png"
plt.figure(figsize=(6, 4))
plt.bar(
["Baseline", "Trained"],
[_safe_mean(item.get("total_reward", 0.0) for item in baseline_results), _safe_mean(item.get("total_reward", 0.0) for item in trained_results)],
color=["#bbbbbb", "#1f77b4"],
)
plt.ylabel("Mean episode reward")
plt.axhline(0, color="k", lw=0.5)
plt.title("Mean total reward per episode")
plt.tight_layout()
plt.savefig(mean_reward_path, dpi=150, bbox_inches="tight")
plt.close()
created.append(mean_reward_path)
for role in ("lead", "auditor"):
curve_path = plots_dir / f"reward_curve_{role}.png"
_write_placeholder_curve(curve_path, role)
created.append(curve_path)
b_per: defaultdict[str, list[bool]] = defaultdict(list)
t_per: defaultdict[str, list[bool]] = defaultdict(list)
for item in baseline_results:
b_per[str(item.get("repo", "unknown_repo"))].append(bool(item.get("success")))
for item in trained_results:
t_per[str(item.get("repo", "unknown_repo"))].append(bool(item.get("success")))
repos = sorted(set(b_per) | set(t_per))
per_repo_path = plots_dir / "per_repo_pass_rate.png"
plt.figure(figsize=(8, 4))
x = np.arange(len(repos))
width = 0.35
plt.bar(x - width / 2, [_safe_mean(b_per[repo]) for repo in repos], width, label="Baseline", color="#bbbbbb")
plt.bar(x + width / 2, [_safe_mean(t_per[repo]) for repo in repos], width, label="Trained", color="#1f77b4")
plt.xticks(x, repos, rotation=15)
plt.ylabel("Pass rate")
plt.legend()
plt.title("Pass rate by repo")
plt.tight_layout()
plt.savefig(per_repo_path, dpi=150, bbox_inches="tight")
plt.close()
created.append(per_repo_path)
completion_turns_path = plots_dir / "completion_turns.png"
b_turns = [int(item.get("turns", 0)) for item in baseline_results if item.get("success")]
t_turns = [int(item.get("turns", 0)) for item in trained_results if item.get("success")]
plt.figure(figsize=(7, 4))
plt.hist([b_turns, t_turns], bins=list(range(1, 9)), label=["Baseline", "Trained"], color=["#bbbbbb", "#1f77b4"], edgecolor="white")
plt.xlabel("Turns to completion (successful episodes only)")
plt.ylabel("Count")
plt.legend()
plt.title("Distribution of completion turns")
plt.tight_layout()
plt.savefig(completion_turns_path, dpi=150, bbox_inches="tight")
plt.close()
created.append(completion_turns_path)
return created
def main() -> None:
"""CLI entrypoint for regenerating plots from saved evaluation JSON."""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--eval-results", default="eval_results.json", help="Path to eval_results.json")
parser.add_argument("--plots-dir", default="plots", help="Directory for generated PNGs")
args = parser.parse_args()
created = build_plots(Path(args.eval_results), Path(args.plots_dir))
print("Generated plots:")
for path in created:
size = os.path.getsize(path)
print(f"- {path} ({size} bytes)")
if __name__ == "__main__":
main()