"""
Figure 4: Cross-platform AUPRC comparison — simulation vs Raspberry Pi.
Purpose
-------
Shows that simulation AUPRC rankings and values closely match those measured
on physical Raspberry Pi hardware, validating the simulation methodology.
Four communication strategies are compared: float32 ρ=1 (baseline),
float16 ρ=1, float32 ρ=3, and joint adaptive.
Data sources
------------
- Simulation: eval report JSONs for N01, N02, N04, H16 (paired scenarios).
- Pi: hardcoded from physical deployment results (3 seeds each, mean ± std).
Pairing rationale
-----------------
float32 ρ=1 : P1 ↔ N01
float16 ρ=1 : P2 ↔ N02
float32 ρ=3 : P3 ↔ N04
joint adaptive : P4 ↔ H16 (both converge to int8 + ρ=3)
Output
------
results/graphics/fig4_cross_platform.pdf (vector, for LaTeX)
results/graphics/fig4_cross_platform.png (raster preview, dpi=200)
Usage
-----
uv run python src/data/plot_cross_platform.py
"""
import json
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import numpy as np
import statistics as _st
from pathlib import Path
# --- LaTeX-compatible style -------------------------------------------------
plt.rcParams.update({
"font.family": "serif",
"font.serif": ["Times New Roman", "DejaVu Serif"],
"font.size": 9,
"axes.titlesize": 9,
"axes.labelsize": 9,
"xtick.labelsize": 8,
"ytick.labelsize": 8,
"legend.fontsize": 8,
"lines.linewidth": 1.2,
"axes.linewidth": 0.7,
"pdf.fonttype": 42,
"ps.fonttype": 42,
})
# --- Paths ------------------------------------------------------------------
RESULTS_DIR = Path(__file__).parent.parent.parent / "results"
SESSION = "2026-05-10_01-43-06"
SEEDS = [42, 52, 62]
OUT_PDF = RESULTS_DIR / "graphics" / "fig4_cross_platform.pdf"
OUT_PNG = RESULTS_DIR / "graphics" / "fig4_cross_platform.png"
# --- Strategy definitions ---------------------------------------------------
STRATEGIES = [
("float32, ρ=1\n(baseline)", "N01"),
("float16, ρ=1", "N02"),
("float32, ρ=3", "N04"),
("Joint\nAdaptive", "H16"),
]
# Pi AUPRC values: (mean, std) from physical deployment, 3 seeds each
PI_AUPRC = {
"N01": (0.6381, 0.0042),
"N02": (0.6434, 0.0044),
"N04": (0.6479, 0.0006),
"H16": (0.6482, 0.0011),
}
# Colorblind-safe palette (Wong 2011)
COLOR_SIM = "#6682F5" # periwinkle (simulation)
COLOR_PI = "#F57582" # coral (real hardware)
# --- Data loading -----------------------------------------------------------
def load_sim_auprc(scenario_id: str) -> tuple[float, float]:
"""Load mean and std AUPRC across seeds from eval report JSONs."""
values = []
for seed in SEEDS:
f = RESULTS_DIR / SESSION / f"{scenario_id}_seed{seed}_eval_report.json"
if not f.exists():
continue
d = json.loads(f.read_text())
values.append(d["weighted_overall"]["auprc"])
if not values:
raise FileNotFoundError(f"No eval reports found for {scenario_id}")
mean = _st.mean(values)
std = _st.stdev(values) if len(values) > 1 else 0.0
return mean, std
# --- Drawing ----------------------------------------------------------------
def draw() -> None:
fig, ax = plt.subplots(figsize=(3.5, 2.4))
n = len(STRATEGIES)
x = np.arange(n, dtype=float)
bar_w = 0.32
sim_means, sim_stds = [], []
pi_means, pi_stds = [], []
for label, sid in STRATEGIES:
sm, ss = load_sim_auprc(sid)
pm, ps = PI_AUPRC[sid]
sim_means.append(sm); sim_stds.append(ss)
pi_means.append(pm); pi_stds.append(ps)
print(f" {sid}: sim={sm:.4f}±{ss:.4f} pi={pm:.4f}±{ps:.4f}")
ax.bar(x - bar_w / 2, sim_means, width=bar_w, color=COLOR_SIM,
label="Simulation", yerr=sim_stds, capsize=2,
error_kw={"elinewidth": 0.8, "ecolor": "#444", "capthick": 0.8},
zorder=3)
ax.bar(x + bar_w / 2, pi_means, width=bar_w, color=COLOR_PI,
label="Raspberry Pi", yerr=pi_stds, capsize=2,
error_kw={"elinewidth": 0.8, "ecolor": "#444", "capthick": 0.8},
zorder=3)
ax.set_xticks(x)
ax.set_xticklabels([s for s, _ in STRATEGIES], fontsize=7.5)
ax.set_ylabel("AUPRC")
all_means = sim_means + pi_means
all_stds = sim_stds + pi_stds
ymin = min(m - s for m, s in zip(all_means, all_stds)) - 0.003
ymax = max(m + s for m, s in zip(all_means, all_stds)) + 0.003
ax.set_ylim(ymin, ymax)
ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda v, _: f"{v:.3f}"))
ax.grid(axis="y", linestyle=":", linewidth=0.6, alpha=0.5, zorder=0)
ax.set_axisbelow(True)
ax.spines[["top", "right"]].set_visible(False)
handles = [
mpatches.Patch(facecolor=COLOR_SIM, label="Simulation"),
mpatches.Patch(facecolor=COLOR_PI, label="Raspberry Pi"),
]
ax.legend(handles=handles, loc="lower right", frameon=True,
framealpha=0.9, edgecolor="#cccccc",
handlelength=1.0, borderpad=0.4, labelspacing=0.2)
fig.tight_layout(pad=0.5)
fig.savefig(OUT_PDF, format="pdf", bbox_inches="tight")
fig.savefig(OUT_PNG, dpi=200, bbox_inches="tight")
print(f"PDF → {OUT_PDF}")
print(f"PNG → {OUT_PNG}")
if __name__ == "__main__":
draw()