269 lines
10 KiB
Python
269 lines
10 KiB
Python
"""Regenerate all paper figures from the CSVs in ../data with
|
|
publication-quality layout (no legend/curve overlap, consistent styling,
|
|
conventional-scheme baselines included).
|
|
|
|
This is the canonical figure generator; experiment scripts write the CSVs.
|
|
"""
|
|
import csv
|
|
import os
|
|
import numpy as np
|
|
import matplotlib
|
|
matplotlib.use("Agg")
|
|
import matplotlib.pyplot as plt
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
FIG = os.path.join(HERE, "..", "fig")
|
|
DATA = os.path.join(HERE, "..", "data")
|
|
|
|
plt.rcParams.update({
|
|
"font.size": 8.5,
|
|
"axes.labelsize": 8.5,
|
|
"legend.fontsize": 6.5,
|
|
"xtick.labelsize": 8,
|
|
"ytick.labelsize": 8,
|
|
"lines.linewidth": 1.15,
|
|
"lines.markersize": 3.2,
|
|
})
|
|
|
|
|
|
FIGW, FIGH = 2.9, 2.25
|
|
AXRECT = [0.185, 0.18, 0.77, 0.7444] # exact 8:6 axes box, identical everywhere
|
|
|
|
|
|
def new_fig():
|
|
"""Canvas and axes rectangle identical for every figure, so every plot
|
|
box renders at exactly the same size in the paper."""
|
|
fig = plt.figure(figsize=(FIGW, FIGH))
|
|
ax = fig.add_axes(AXRECT)
|
|
return fig, ax
|
|
|
|
|
|
def load(name):
|
|
with open(os.path.join(DATA, name)) as f:
|
|
return list(csv.DictReader(f))
|
|
|
|
|
|
def savefig(fig, name):
|
|
fig.savefig(os.path.join(FIG, name))
|
|
print("saved", name)
|
|
|
|
|
|
S = {"OMA": ("0.45", ":", "v"), "NOMA": ("tab:brown", ":", "P"),
|
|
"SR": ("tab:red", "--", "s"), "SC": ("tab:green", "-.", "^"),
|
|
"LMMSE": ("tab:blue", "-", "o"), "DR": ("k", "-", "d")}
|
|
LBL = {"OMA": "OMA", "NOMA": "NOMA-SIC", "SR": "SR",
|
|
"SC": "SC", "LMMSE": "LMMSE", "DR": "Proposed DR"}
|
|
ORDER = ["OMA", "NOMA", "SR", "SC", "LMMSE", "DR"]
|
|
|
|
# ---------------------------------------------------------------- E1 beta
|
|
rows = load("e1_beta.csv")
|
|
betas = [float(r["beta"]) for r in rows]
|
|
|
|
fig, ax = new_fig()
|
|
for n in ORDER:
|
|
c, ls, mk = S[n]
|
|
ax.semilogy(betas, [float(r[f"{n}_nmse"]) for r in rows], ls, color=c,
|
|
marker=mk, label=LBL[n])
|
|
ax.semilogy(betas, [float(r["LMMSE_cf"]) for r in rows], 'x',
|
|
color="tab:blue", ms=6.5, mew=1.5, ls="none",
|
|
label="LMMSE closed form")
|
|
ax.semilogy(betas, [float(r["SR_cf"]) for r in rows], '+', color="tab:red",
|
|
ms=7.5, mew=1.5, ls="none", label="SR closed form")
|
|
ax.set_xlabel(r"affinity $\beta$")
|
|
ax.set_ylabel("NMSE")
|
|
ax.set_ylim(6e-2, 8e6)
|
|
ax.grid(True, which="both", alpha=0.3)
|
|
ax.legend(loc="upper left", ncol=2, columnspacing=0.7, handletextpad=0.4,
|
|
labelspacing=0.3)
|
|
savefig(fig, "fig_e1_beta_nmse.pdf")
|
|
|
|
fig, ax = new_fig()
|
|
for n in ORDER:
|
|
c, ls, mk = S[n]
|
|
ax.plot(betas, [float(r[f"{n}_cos"]) for r in rows], ls, color=c,
|
|
marker=mk, label=LBL[n])
|
|
ax.set_xlabel(r"affinity $\beta$")
|
|
ax.set_ylabel("mean cosine recovery")
|
|
ax.set_ylim(0.0, 1.05)
|
|
ax.grid(alpha=0.3)
|
|
ax.legend(loc="upper left", ncol=2, columnspacing=0.7, handletextpad=0.4,
|
|
labelspacing=0.3)
|
|
savefig(fig, "fig_e1_beta_cos.pdf")
|
|
|
|
# ---------------------------------------------------------------- E1 snr
|
|
rows = load("e1_snr.csv")
|
|
snrs = [float(r["snr"]) for r in rows]
|
|
fig, ax = new_fig()
|
|
for n in ORDER:
|
|
c, ls, mk = S[n]
|
|
ax.semilogy(snrs, [max(float(r[f"{n}_ser"]), 1e-4) for r in rows], ls,
|
|
color=c, marker=mk, label=LBL[n])
|
|
ax.set_xlabel("per-user SNR (dB)")
|
|
ax.set_ylabel("semantic error rate")
|
|
ax.set_ylim(8e-4, 2.5)
|
|
ax.grid(True, which="both", alpha=0.3)
|
|
ax.legend(loc="lower left", ncol=1, fontsize=6.1, handletextpad=0.4,
|
|
labelspacing=0.25, borderpad=0.3)
|
|
savefig(fig, "fig_e1_snr.pdf")
|
|
|
|
# ---------------------------------------------- E2 spectrum (multi-curve)
|
|
rows_s = load("e2_spectrum_multi.csv")
|
|
fig, ax = new_fig()
|
|
for n_cal, c, ls in ((100, "tab:orange", "-."), (400, "tab:green", "--"),
|
|
(1600, "tab:blue", "-")):
|
|
pts = [(int(r["idx"]), float(r["eig"])) for r in rows_s
|
|
if int(r["N"]) == n_cal]
|
|
ax.semilogy([p[0] for p in pts],
|
|
np.maximum([p[1] for p in pts], 1e-12), ls, color=c,
|
|
lw=1.15, label=f"$N{{=}}{n_cal}$")
|
|
ax.axvline(128, color="k", ls=":", lw=0.9)
|
|
ax.annotate(r"$d_c=128$", xy=(128, 1e-6), xytext=(150, 3e-7), fontsize=7.5,
|
|
arrowprops=dict(arrowstyle="-", lw=0.6, color="0.3"))
|
|
ax.set_xlabel("eigenvalue index")
|
|
ax.set_ylabel(r"eigenvalue of $\hat{\mathbf{\Sigma}}$")
|
|
ax.set_ylim(1e-8, 3e-1)
|
|
ax.grid(True, which="both", alpha=0.3)
|
|
ax.legend(loc="upper right", labelspacing=0.3)
|
|
savefig(fig, "fig_e2_spectrum.pdf")
|
|
|
|
# ---------------------------------------------- E2 subspace (multi-curve)
|
|
rows_n = load("e2_subspace_multi.csv")
|
|
fig, ax = new_fig()
|
|
for Um, c, mk, ls in ((2, "tab:orange", "s", "-."),
|
|
(4, "tab:blue", "o", "-"),
|
|
(8, "tab:green", "^", "--")):
|
|
pts = [(int(r["N"]), float(r["err"])) for r in rows_n
|
|
if int(r["U"]) == Um]
|
|
ax.loglog([p[0] for p in pts], [p[1] for p in pts], ls, color=c,
|
|
marker=mk, label=f"$U{{=}}{Um}$")
|
|
ax.set_xlabel("paired calibration samples $N$")
|
|
ax.set_ylabel("subspace recovery error")
|
|
ax.grid(True, which="both", alpha=0.3)
|
|
ax.legend(loc="upper right", labelspacing=0.3)
|
|
savefig(fig, "fig_e2_subspace.pdf")
|
|
|
|
# ---------------------------------------------------------------- E2 ladder
|
|
rows = load("e2_ladder.csv")
|
|
snrs = [float(r["snr"]) for r in rows]
|
|
S2 = {"OMA": ("0.45", ":", "v", "OMA"),
|
|
"NOMA": ("tab:brown", ":", "P", "NOMA-SIC"),
|
|
"LMMSE": ("tab:blue", "-", "o", "LMMSE"),
|
|
"DR-spec": ("tab:orange", "--", "s", "DR + spectral"),
|
|
"DR-adapt": ("tab:purple", "-", "^", "DR + adapter"),
|
|
"DR-oracle": ("k", ":", "d", "DR + oracle basis")}
|
|
fig, ax = new_fig()
|
|
for k, (c, ls, mk, lb) in S2.items():
|
|
ax.semilogy(snrs, [max(float(r[f"{k}_ser"]), 1e-4) for r in rows], ls,
|
|
color=c, marker=mk, label=lb)
|
|
ax.set_xlabel("per-user SNR (dB)")
|
|
ax.set_ylabel("semantic error rate")
|
|
ax.set_ylim(3e-3, 2.7)
|
|
ax.grid(True, which="both", alpha=0.3)
|
|
ax.legend(loc="lower left", labelspacing=0.3)
|
|
savefig(fig, "fig_e2_ladder.pdf")
|
|
|
|
# ---------------------------------------------------------------- E3 time
|
|
rows = load("e3_timeseries.csv")
|
|
T = len(rows)
|
|
t = np.arange(T)
|
|
U = 4
|
|
METHODS = ["OMA", "NOMA", "SR", "SC", "LMMSE", "DR-static", "DR-tracked",
|
|
"DR-genie"]
|
|
S3 = {"DR-genie": ("k", ":"), "DR-tracked": ("tab:purple", "-"),
|
|
"DR-static": ("tab:orange", "--"), "SR": ("tab:red", "--"),
|
|
"SC": ("tab:green", "-."), "LMMSE": ("tab:blue", "-"),
|
|
"OMA": ("0.45", ":"), "NOMA": ("tab:brown", ":")}
|
|
|
|
|
|
def roll(x, w=15):
|
|
"""Moving average with edge-truncated windows (no zero-padding bias:
|
|
endpoints average only the samples that exist)."""
|
|
x = np.asarray(x, float)
|
|
num = np.convolve(x, np.ones(w), mode="same")
|
|
den = np.convolve(np.ones_like(x), np.ones(w), mode="same")
|
|
return num / den
|
|
|
|
|
|
fig = plt.figure(figsize=(2.9, 4.2))
|
|
_bh = 0.77 * 2.9 * 0.75 / 4.2 # same physical box height as new_fig
|
|
axes = [fig.add_axes([0.185, 0.549, 0.77, _bh]),
|
|
fig.add_axes([0.185, 0.095, 0.77, _bh])]
|
|
axes[0].tick_params(labelbottom=False)
|
|
from matplotlib.lines import Line2D
|
|
for u in range(U):
|
|
axes[0].plot(t, [float(r[f"a{u}"]) for r in rows], lw=1.1, color=f"C{u}")
|
|
axes[0].plot(t, [float(r[f"ahat{u}"]) for r in rows], lw=0.9, ls="--",
|
|
color=f"C{u}", alpha=0.75)
|
|
axes[0].set_ylabel("share coefficient $a_u(t)$")
|
|
axes[0].set_ylim(0, 1.22)
|
|
axes[0].legend(handles=[
|
|
Line2D([], [], color="k", ls="-", lw=1.1, label="true"),
|
|
Line2D([], [], color="k", ls="--", lw=0.9, label="tracked")],
|
|
loc="upper right", ncol=2, columnspacing=0.8)
|
|
axes[0].grid(alpha=0.3)
|
|
for m in METHODS:
|
|
c, ls = S3[m]
|
|
axes[1].semilogy(t, np.clip(roll([float(r[f"{m}_ser"]) for r in rows]),
|
|
1e-3, None), ls, color=c, label=m)
|
|
axes[1].set_xlabel("time slot $t$")
|
|
axes[1].set_ylabel("semantic error rate")
|
|
axes[1].set_ylim(2e-2, 30)
|
|
axes[1].grid(True, which="both", alpha=0.3)
|
|
axes[1].legend(loc="upper center", ncol=3, columnspacing=0.7,
|
|
handletextpad=0.4, labelspacing=0.3, fontsize=6)
|
|
savefig(fig, "fig_e3_time.pdf")
|
|
|
|
# ---------------------------------------------------------------- E3 speed
|
|
rows = load("e3_speed.csv")
|
|
speeds = [float(r["speed"]) for r in rows]
|
|
marks = {"DR-genie": "d", "DR-tracked": "^", "DR-static": "s",
|
|
"SR": "v", "SC": "x", "LMMSE": "o", "OMA": "1", "NOMA": "P"}
|
|
fig, ax = new_fig()
|
|
for m in METHODS:
|
|
c, ls = S3[m]
|
|
ax.semilogy(speeds, [max(float(r[f"{m}_ser"]), 1e-4) for r in rows], ls,
|
|
color=c, marker=marks[m], label=m)
|
|
ax.set_xlabel("user speed (m/slot)")
|
|
ax.set_ylabel("mean semantic error rate")
|
|
ax.set_ylim(0.1, 40)
|
|
ax.grid(True, which="both", alpha=0.3)
|
|
ax.legend(loc="upper center", ncol=3, columnspacing=0.6, handletextpad=0.3,
|
|
handlelength=1.4, labelspacing=0.25, fontsize=5.8, borderpad=0.3)
|
|
savefig(fig, "fig_e3_speed.pdf")
|
|
|
|
# ---------------------------------------------------------------- E4
|
|
rows = load("e4_mismatch.csv")
|
|
fig, ax = new_fig()
|
|
for snr_db, c, mk in ((5, "tab:red", "s"), (10, "tab:blue", "o"),
|
|
(15, "tab:green", "^")):
|
|
pts = [(float(r["delta"]), float(r["cos"])) for r in rows
|
|
if int(r["snr"]) == snr_db]
|
|
ax.plot([p[0] for p in pts], [p[1] for p in pts], "-", color=c,
|
|
marker=mk, label=f"{snr_db} dB")
|
|
ax.set_xlabel(r"affinity estimation error $\delta$")
|
|
ax.set_ylabel("mean cosine recovery")
|
|
ax.set_ylim(0.40, 1.0)
|
|
ax.grid(alpha=0.3)
|
|
ax.legend(loc="upper center", ncol=3, columnspacing=0.9, handletextpad=0.4,
|
|
borderpad=0.3)
|
|
savefig(fig, "fig_e4_mismatch.pdf")
|
|
|
|
# ---------------------------------------------------------------- E5
|
|
rows = load("e5_learned.csv")
|
|
betas5 = [float(r["beta"]) for r in rows]
|
|
fig, ax = new_fig()
|
|
S5 = {"Learned": ("tab:red", "--", "s", "learned attention"),
|
|
"LMMSE": ("tab:blue", "-", "o", "LMMSE"),
|
|
"DR": ("k", "-", "d", "Proposed DR")}
|
|
for k, (c, ls, mk, lb) in S5.items():
|
|
ax.semilogy(betas5, [max(float(r[f"{k}_ser"]), 1e-3) for r in rows], ls,
|
|
color=c, marker=mk, label=lb)
|
|
ax.set_xlabel(r"affinity $\beta$")
|
|
ax.set_ylabel("semantic error rate")
|
|
ax.set_ylim(8e-3, 3.2)
|
|
ax.grid(True, which="both", alpha=0.3)
|
|
ax.legend(loc="lower left", labelspacing=0.3)
|
|
savefig(fig, "fig_e5_learned.pdf")
|
|
|
|
print("all figures regenerated")
|