| |
| from __future__ import annotations |
|
|
| from dataclasses import dataclass |
| from pathlib import Path |
|
|
| import matplotlib.pyplot as plt |
|
|
|
|
| ROOT = Path(__file__).resolve().parents[1] |
| PLOTS_DIR = ROOT / "artifacts" / "plots" |
|
|
| OURS_COLOR = "#1f77b4" |
| BASELINE_COLOR = "#9aa0a6" |
|
|
|
|
| @dataclass(frozen=True) |
| class Baseline: |
| name: str |
| seconds: float |
|
|
|
|
| def _save(fig: plt.Figure, stem: str) -> None: |
| PLOTS_DIR.mkdir(parents=True, exist_ok=True) |
| fig.tight_layout() |
| fig.savefig(PLOTS_DIR / f"{stem}.png", dpi=300, bbox_inches="tight") |
| fig.savefig(PLOTS_DIR / f"{stem}.pdf", bbox_inches="tight") |
| plt.close(fig) |
|
|
|
|
| def main() -> int: |
| |
| |
| mchatbot_total_s = 90.0 |
| breakdown = [ |
| ("JSON schema extraction (LLM)", 35.0), |
| ("UPPAAL XML compilation", 5.0), |
| ("Repair loop (≤3 iters)", 40.0), |
| ("TCTL translation + verification", 10.0), |
| ] |
| if abs(sum(v for _, v in breakdown) - mchatbot_total_s) > 1e-9: |
| raise SystemExit("Breakdown does not sum to MChatbot total runtime.") |
|
|
| baselines = [ |
| Baseline("GPT-5.3", 2.0), |
| Baseline("Grok-4", 3.0), |
| Baseline("Claude-Haiku", 4.0), |
| ] |
|
|
| fig, (ax_total, ax_break) = plt.subplots( |
| 1, |
| 2, |
| figsize=(11.2, 4.2), |
| gridspec_kw={"width_ratios": [1.0, 1.4]}, |
| ) |
|
|
| |
| total_names = ["MChatbot (ours)"] + [b.name for b in baselines] |
| total_vals = [mchatbot_total_s] + [b.seconds for b in baselines] |
| total_colors = [OURS_COLOR] + [BASELINE_COLOR] * len(baselines) |
|
|
| bars = ax_total.bar(total_names, total_vals, color=total_colors) |
| ax_total.set_title("Total runtime (avg / query)") |
| ax_total.set_ylabel("Seconds") |
| ax_total.grid(axis="y", alpha=0.25) |
| ax_total.set_axisbelow(True) |
| ax_total.tick_params(axis="x", rotation=10) |
|
|
| for b, v in zip(bars, total_vals): |
| ax_total.text( |
| b.get_x() + b.get_width() / 2, |
| b.get_height() + max(0.8, 0.02 * max(total_vals)), |
| f"{v:.0f}s", |
| ha="center", |
| va="bottom", |
| fontsize=10, |
| ) |
|
|
| |
| labels = [k for k, _ in breakdown] |
| vals = [v for _, v in breakdown] |
| stack_colors = ["#4c78a8", "#72b7b2", "#f58518", "#54a24b"] |
|
|
| bottom = 0.0 |
| for (label, v), c in zip(breakdown, stack_colors, strict=True): |
| ax_break.bar(["MChatbot (ours)"], [v], bottom=bottom, color=c, label=label) |
| ax_break.text( |
| 0, |
| bottom + v / 2, |
| f"{v:.0f}s", |
| ha="center", |
| va="center", |
| fontsize=10, |
| color="white" if c in {"#4c78a8", "#f58518"} else "black", |
| fontweight="bold", |
| ) |
| bottom += v |
|
|
| ax_break.set_title("MChatbot breakdown (avg / query)") |
| ax_break.set_ylabel("Seconds") |
| ax_break.set_ylim(0, mchatbot_total_s * 1.15) |
| ax_break.grid(axis="y", alpha=0.25) |
| ax_break.set_axisbelow(True) |
| ax_break.legend(loc="upper left", frameon=True, fontsize=9) |
|
|
| fig.suptitle("Time analysis: MChatbot vs direct-LLM baselines", fontsize=13, fontweight="bold") |
| _save(fig, "time_analysis_runtime_breakdown") |
| print(f"Wrote plots to: {PLOTS_DIR.resolve()}") |
| return 0 |
|
|
|
|
| if __name__ == "__main__": |
| plt.rcParams.update( |
| { |
| "font.size": 11, |
| "axes.titlesize": 12, |
| "axes.labelsize": 11, |
| "figure.dpi": 120, |
| } |
| ) |
| raise SystemExit(main()) |
|
|
|
|