Files
TOIFAS/code/replot_security.py
T
KiHoLee 37392bc38f Keyed masking for secure multi-user semantic communication
Reproducibility package for the TIFS submission: transmit and receive
core, security stages (eavesdropper, jamming, key families, attack
difficulty, known-plaintext), real BERT token streams, closed-form
verification, and the scripts that regenerate every figure and table
from the released CSVs.
2026-08-13 21:01:32 +09:00

254 lines
8.2 KiB
Python

"""Canonical replot script for paper 11: regenerates every result figure
from ../data/*.csv and writes paper-ready PDFs to ../fig/. No experiment
is rerun. All result plots share one canvas and axes rectangle (8:6 box).
Label dictionary is fixed here and copied verbatim into tables and prose.
fig_sec_snr.pdf : legitimate vs eavesdropper SER vs SNR (Fig. 2)
fig_sec_keylen.pdf : SER vs key length L (Fig. 3)
fig_sec_jam.pdf : target-user SER vs JSR (Fig. 4)
fig_sec_sens.pdf : Eve SER vs key correlation (Fig. 5)
fig_sec_brute.pdf : Eve SER vs number of key guesses (Fig. 6)
"""
from __future__ import annotations
from pathlib import Path
import csv
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
ROOT = Path(__file__).resolve().parents[1]
DATA = ROOT / "data"
FIG = ROOT / "fig"
FIG.mkdir(exist_ok=True)
plt.rcParams.update({
"font.family": "serif",
"font.serif": ["DejaVu Serif", "Times New Roman"],
"font.size": 9,
"axes.labelsize": 9,
"legend.fontsize": 6.6,
"xtick.labelsize": 8,
"ytick.labelsize": 8,
"axes.grid": True,
"grid.linestyle": "--",
"grid.linewidth": 0.4,
"grid.alpha": 0.6,
"lines.linewidth": 1.3,
"lines.markersize": 3.4,
"figure.figsize": (3.15, 2.36),
"pdf.fonttype": 42,
})
AXES_RECT = dict(left=0.185, right=0.965, top=0.955, bottom=0.195)
C_LEGIT = "#c0392b"
C_EVE = "#2c5fa8"
C_OMA = "#7f8c8d"
C_CH = "#95a5a6"
C_MATCH = "#8e44ad"
C_PUB = "#16a085"
# fixed label dictionary: tables and prose copy these strings verbatim
LBL = {
"legit": "Legitimate",
"oma": "OMA",
"eve_pub": "Eve, public masks",
"eve_key": "Eve, wrong key",
"chance": "Random guess",
"jam_m": "Matched jammer (public masks)",
"jam_b": "Blind jammer (proposed)",
"nojam": "No jammer",
}
def load(name):
with open(DATA / name) as f:
return list(csv.DictReader(f))
def col(rows, k, f=float):
return [f(r[k]) for r in rows]
def save(fig, name):
"""Write the figure and assert that no axis label is clipped.
A long y label, or wide minor tick labels such as 6x10^-1 on a log
axis that spans less than a decade, silently pushes the label off
the canvas under the fixed axes rectangle. Reading the plotting code
cannot reveal this, so the check is made on the rendered geometry.
"""
fig.subplots_adjust(**AXES_RECT)
fig.canvas.draw()
fbox = fig.get_window_extent()
for ax in fig.axes:
for lbl in (ax.yaxis.label, ax.xaxis.label):
if not lbl.get_text():
continue
b = lbl.get_window_extent()
if (b.x0 < fbox.x0 or b.y0 < fbox.y0
or b.x1 > fbox.x1 or b.y1 > fbox.y1):
raise RuntimeError(
f"{name}: axis label '{lbl.get_text()}' is clipped "
f"(label {b} outside figure {fbox}); shorten the "
f"label or widen the margin")
fig.savefig(FIG / f"{name}.pdf")
plt.close(fig)
print("[OK]", name)
def fig_snr():
r = load("sec_snr.csv")
x = col(r, "snr_db")
fig, ax = plt.subplots()
ax.semilogy(x, col(r, "legit"), color=C_LEGIT, marker="o", ls="-",
label=LBL["legit"])
ax.semilogy(x, col(r, "oma"), color=C_OMA, marker="^", ls=":",
label=LBL["oma"])
ax.semilogy(x, col(r, "eve_public"), color=C_PUB, marker="v",
ls="none", markersize=5.2, markerfacecolor="none",
label=LBL["eve_pub"])
ax.semilogy(x, col(r, "eve_wrong"), color=C_EVE, marker="s", ls="--",
label=LBL["eve_key"])
ax.plot(x, col(r, "chance"), color=C_CH, ls="-.", lw=0.9,
label=LBL["chance"])
ax.set_xlabel("SNR (dB)")
ax.set_ylabel("SER")
ax.set_xlim(min(x), max(x))
ax.legend(loc="lower left")
save(fig, "fig_sec_snr")
def fig_keylen():
r = load("sec_keylen.csv")
x = col(r, "L", int)
fig, ax = plt.subplots()
ax.semilogy(x, col(r, "legit_ser"), color=C_LEGIT, marker="o", ls="-",
label=LBL["legit"])
ax.semilogy(x, col(r, "oma"), color=C_OMA, marker="^", ls=":",
label=LBL["oma"])
ax.semilogy(x, col(r, "eve_ser"), color=C_EVE, marker="s", ls="--",
label=LBL["eve_key"])
ax.set_xlabel("Key length $L$")
ax.set_ylabel("SER")
ax.set_xscale("log", base=2)
ax.legend(loc="center right", bbox_to_anchor=(0.98, 0.72))
save(fig, "fig_sec_keylen")
def fig_jam():
# the target-user SER spans 0.3 to 1.0, less than one decade, so a
# linear axis is used: a log axis here produces wide minor tick
# labels (6x10^-1) that crowd out the y label under the fixed
# axes rectangle
r = load("sec_jam.csv")
x = col(r, "jsr_db")
fig, ax = plt.subplots()
ax.plot(x, col(r, "matched"), color=C_MATCH, marker="P", ls="--",
label=LBL["jam_m"])
ax.plot(x, col(r, "blind"), color=C_LEGIT, marker="o", ls="-",
label=LBL["jam_b"])
nojam = col(r, "nojam")[0]
ax.axhline(nojam, color=C_OMA, ls=":", lw=0.9, label=LBL["nojam"])
ax.set_xlabel("JSR (dB)")
ax.set_ylabel("SER")
ax.set_xlim(min(x), max(x))
ax.set_ylim(0.2, 1.02)
ax.legend(loc="lower right")
save(fig, "fig_sec_jam")
def fig_sens():
r = load("sec_sens.csv")
x = col(r, "rho")
fig, ax = plt.subplots()
ax.plot(x, col(r, "eve_ser"), color=C_EVE, marker="s", ls="-",
label=LBL["eve_key"])
chance = 1.0 - (1.0 / 16.0) ** 4
ax.axhline(chance, color=C_CH, ls="-.", lw=0.9, label=LBL["chance"])
ax.set_xlabel(r"Key correlation $\kappa$")
ax.set_ylabel("Eavesdropper SER")
ax.set_xlim(0, 1)
ax.legend(loc="lower left")
save(fig, "fig_sec_sens")
def fig_brute():
r = load("sec_brute.csv")
fig, ax = plt.subplots()
sty = {8: ("#c0392b", "o"), 16: ("#2c5fa8", "s"),
32: ("#16a085", "v"), 64: ("#8e44ad", "P")}
for Lp in [8, 16, 32, 64]:
rows = [row for row in r if int(row["L"]) == Lp]
ks = [float(row["K"]) for row in rows]
ser = [float(row["eve_ser"]) for row in rows]
c, mk = sty[Lp]
ax.semilogx(ks, ser, color=c, marker=mk, ls="-",
label=f"$L={Lp}$")
ax.set_xlabel("Number of key guesses $K$")
ax.set_ylabel("Eavesdropper SER")
ax.legend(loc="lower left")
save(fig, "fig_sec_brute")
def fig_real():
r = load("real_sec_ter.csv")
x = col(r, "snr_db")
fig, ax = plt.subplots()
ax.semilogy(x, col(r, "ter_legit"), color=C_LEGIT, marker="o", ls="-",
label=LBL["legit"])
ax.semilogy(x, col(r, "ter_oma"), color=C_OMA, marker="^", ls=":",
label=LBL["oma"])
ax.semilogy(x, col(r, "ter_insider"), color=C_PUB, marker="v", ls="-.",
label="Insider")
ax.semilogy(x, col(r, "ter_eve"), color=C_EVE, marker="s", ls="--",
label=LBL["eve_key"])
ax.set_xlabel("SNR (dB)")
ax.set_ylabel("TER")
ax.set_xlim(min(x), max(x))
ax.legend(loc="lower left")
save(fig, "fig_sec_real")
def fig_kpa():
r = load("kpa.csv")
fig, ax = plt.subplots()
sty = {0.0: ("#c0392b", "o"), 10.0: ("#2c5fa8", "s"),
20.0: ("#16a085", "v")}
for snr, (c, mk) in sty.items():
rows = [row for row in r if float(row["snr_db"]) == snr]
n = [float(row["n_frames"]) for row in rows]
ser = [float(row["eve_ser"]) for row in rows]
ax.semilogx(n, ser, color=c, marker=mk, ls="-",
label=f"{int(snr)} dB")
ax.axhline(0.304, color=C_OMA, ls=":", lw=0.9, label=LBL["legit"])
ax.set_xlabel("Known-plaintext frames $N$")
ax.set_ylabel("Eavesdropper SER")
ax.set_xscale("log", base=2)
ax.legend(loc="upper right")
save(fig, "fig_sec_kpa")
def main():
fig_snr()
fig_keylen()
fig_jam()
try:
fig_sens()
fig_brute()
except FileNotFoundError:
print("[skip] attack-difficulty CSVs not present yet")
try:
fig_real()
except FileNotFoundError:
print("[skip] real-token CSV not present yet")
try:
fig_kpa()
except FileNotFoundError:
print("[skip] known-plaintext CSV not present yet")
print("[done] figures in", FIG)
if __name__ == "__main__":
main()