| from __future__ import annotations |
|
|
| from pathlib import Path |
|
|
| import matplotlib |
|
|
| matplotlib.use("Agg") |
|
|
| import matplotlib.pyplot as plt |
|
|
| from engine.controller import Action, ControllerConfig, WhenToSpeakController |
| from engine.traces import SignalTrace, surprising_claim_trace |
|
|
|
|
| ROOT = Path(__file__).resolve().parents[1] |
| DEFAULT_OUTPUT = ROOT / "eval" / "controller_timeline.png" |
| ACTION_COLORS = { |
| Action.BACKCHANNEL: "tab:blue", |
| Action.TAKE_FLOOR: "tab:green", |
| Action.INTERRUPT: "tab:red", |
| } |
|
|
|
|
| def render_timeline( |
| trace: SignalTrace | None = None, |
| output_path: str | Path = DEFAULT_OUTPUT, |
| config: ControllerConfig | None = None, |
| ) -> Path: |
| trace = trace or surprising_claim_trace() |
| controller = WhenToSpeakController(trace.agent_ids, config=config) |
| ticks = [controller.tick(step.signals_by_agent, floor_holder=step.floor_holder) for step in trace.steps] |
|
|
| steps = [tick.step for tick in ticks] |
| primary = trace.agent_ids[0] |
| surprise = [step.signals_by_agent[primary].surprise for step in trace.steps] |
| readiness = [step.signals_by_agent[primary].readiness for step in trace.steps] |
| p_end = [step.signals_by_agent[primary].p_end for step in trace.steps] |
| change = [tick.decisions[primary].change_score for tick in ticks] |
|
|
| fig, axes = plt.subplots(4, 1, figsize=(10, 8), sharex=True) |
| fig.suptitle(f"WhenToSpeak controller timeline: {trace.name}", fontsize=13) |
|
|
| axes[0].plot(steps, surprise, marker="o", color="tab:orange", label="surprise/NLL") |
| axes[0].set_ylabel("surprise") |
| axes[0].legend(loc="upper right") |
|
|
| axes[1].plot(steps, change, marker="o", color="tab:purple", label="change score") |
| axes[1].set_ylabel("change") |
| axes[1].legend(loc="upper right") |
|
|
| axes[2].plot(steps, readiness, marker="o", color="tab:green", label="readiness") |
| axes[2].plot(steps, p_end, marker=".", linestyle="--", color="tab:gray", label="p_end") |
| axes[2].set_ylabel("probability") |
| axes[2].set_ylim(-0.05, 1.05) |
| axes[2].legend(loc="upper right") |
|
|
| for agent_id in trace.agent_ids: |
| urges = [tick.decisions[agent_id].urge for tick in ticks] |
| axes[3].plot(steps, urges, marker="o", label=f"{agent_id} urge") |
| axes[3].axhline(controller.config.tau, color="black", linestyle="--", linewidth=1, label="tau") |
| axes[3].set_ylabel("urge") |
| axes[3].set_xlabel("step") |
| axes[3].legend(loc="upper right") |
|
|
| for tick in ticks: |
| for agent_id, decision in tick.decisions.items(): |
| if decision.action == Action.SILENT: |
| continue |
| color = ACTION_COLORS[decision.action] |
| axes[3].scatter([tick.step], [decision.urge], color=color, s=90, zorder=5) |
| axes[3].annotate( |
| decision.action.value, |
| (tick.step, decision.urge), |
| textcoords="offset points", |
| xytext=(0, 8), |
| ha="center", |
| fontsize=8, |
| color=color, |
| ) |
|
|
| for axis in axes: |
| axis.grid(True, alpha=0.25) |
|
|
| output = Path(output_path) |
| output.parent.mkdir(parents=True, exist_ok=True) |
| fig.tight_layout() |
| fig.savefig(output, dpi=160) |
| plt.close(fig) |
| return output |
|
|
|
|
| def main() -> None: |
| output = render_timeline() |
| print(f"Wrote {output}") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|