import numpy as np, pandas as pd
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
D = "/work/project/project/user"
OUT = f"{D}/analysis/out"
runs = pd.read_csv(f"{D}/data/runs.csv")
runs = runs[(runs.app != "cp2k") & runs.runtime_s.notna()]
wc = [c for c in ["wall_B","wall_C","wall_D","wall_E"] if c in runs]
runs["y"] = runs[wc].median(axis=1).fillna(runs.runtime_s)
preds = pd.read_csv(f"{OUT}/predictions.csv")
metrics = pd.read_csv(f"{OUT}/metrics.csv")
APPS = sorted(runs.app.unique())
COL = dict(zip(APPS, plt.cm.tab10.colors))
fig, ax = plt.subplots(1, 2, figsize=(11, 4.2))
for a in APPS:
s = runs[runs.app == a].sort_values("ncore")
ax[0].plot(s.ncore, s.y, "o-", color=COL[a], label=a)
ax[1].plot(s.ncore, s.PAPI_TOT_CYC / s.y / 1e9, "o-", color=COL[a], label=a)
ax[0].set(xscale="log", yscale="log", xlabel="cores", ylabel="runtime (s)",
title="Measured runtime vs core count")
ax[0].set_xticks([1,2,4,8,16,32,64,128]); ax[0].set_xticklabels([1,2,4,8,16,32,64,128])
ax[0].grid(alpha=.3); ax[0].legend(fontsize=8)
ax[1].axhline(2.25, ls="--", c="k", lw=1, label="peak 2.25 GHz")
ax[1].set(xscale="log", xlabel="cores", ylabel="effective GHz (cycles/runtime)",
title="Rank-0 efficiency collapses for memory-bound codes")
ax[1].set_xticks([1,2,4,8,16,32,64,128]); ax[1].set_xticklabels([1,2,4,8,16,32,64,128])
ax[1].grid(alpha=.3); ax[1].legend(fontsize=8)
fig.tight_layout(); fig.savefig(f"{OUT}/fig_scaling.png", dpi=150); plt.close(fig)
best = preds[(preds.approach == "residual") & (preds.model == "Random Forest")]
fig, ax = plt.subplots(figsize=(5.2, 5))
lim = [3, 300]
ax.plot(lim, lim, "k-", lw=1)
for k, ls in [(1.25, ":"), (1.5, "--")]:
ax.plot(lim, [v*k for v in lim], ls, c="grey", lw=.8)
ax.plot(lim, [v/k for v in lim], ls, c="grey", lw=.8)
for a in APPS:
s = best[best.app == a]
ax.scatter(s.actual_s, s.predicted_s, color=COL[a], label=a, s=38, zorder=3)
ax.set(xscale="log", yscale="log", xlim=lim, ylim=lim,
xlabel="measured runtime (s)", ylabel="predicted runtime (s)",
title="Random Forest (residual), leave-one-app-out\ndashed = 1.5x, dotted = 1.25x")
ax.grid(alpha=.3); ax.legend(fontsize=8)
fig.tight_layout(); fig.savefig(f"{OUT}/fig_pred_vs_actual.png", dpi=150); plt.close(fig)
agg = (metrics.groupby(["approach","model"]).median_factor.median()
.reset_index().sort_values("median_factor"))
fig, ax = plt.subplots(figsize=(7.5, 4.2))
cols = {"analytic":"tab:grey","direct":"tab:red","residual":"tab:green"}
lbl = [f"{r.model}\n({r.approach})" for r in agg.itertuples()]
ax.barh(range(len(agg)), agg.median_factor,
color=[cols[a] for a in agg.approach])
ax.axvline(1.0, c="k", lw=1)
ax.set(yticks=range(len(agg)), xscale="log",
xlabel="median error factor (1.0 = perfect)",
title="Residual framing beats both the analytic model and direct regression")
ax.set_yticklabels(lbl, fontsize=7)
ax.grid(alpha=.3, axis="x")
fig.tight_layout(); fig.savefig(f"{OUT}/fig_approaches.png", dpi=150); plt.close(fig)
imp = pd.read_csv(f"{OUT}/feature_importance.csv", index_col=0).importance.head(10)[::-1]
fig, ax = plt.subplots(figsize=(6.4, 4))
ax.barh(range(len(imp)), imp.values, color="tab:blue")
ax.set(yticks=range(len(imp)), xlabel="importance",
title="Counters explaining the efficiency factor")
ax.set_yticklabels(imp.index, fontsize=8)
ax.grid(alpha=.3, axis="x")
fig.tight_layout(); fig.savefig(f"{OUT}/fig_importance.png", dpi=150); plt.close(fig)
print("wrote 4 figures to", OUT)