The lower of two coinciding curves is the wide one, the random-guess reference is dotted everywhere and never borrows a scheme colour, the three collection SNRs in the known-plaintext figure differ by face and width rather than by dash alone, and the legend floor now prints at six points with an assertion that fails if any figure drags it lower. Both table generators list the proposal first.
112 lines
4.1 KiB
Python
112 lines
4.1 KiB
Python
"""Generate the LaTeX rows of every result table from the CSVs, so
|
|
that every table in the paper is reproducible from data/ (TIFS mandate).
|
|
Prints the tabular bodies; paste into main.tex without edits.
|
|
"""
|
|
from __future__ import annotations
|
|
import csv
|
|
import json
|
|
import math
|
|
from pathlib import Path
|
|
|
|
DATA = Path(__file__).resolve().parents[1] / "data"
|
|
|
|
NAME = {
|
|
"proposed": r"\textbf{KM (str.)}",
|
|
"proposed_learned": r"\textbf{KM (lrn.)}",
|
|
"public_mask": "Public masks",
|
|
"perm_key": r"Permutation key~\cite{chen2025shufflingtifs}",
|
|
"index_cipher": "Index cipher",
|
|
"oma_plain": "OMA (no encryption)",
|
|
"random": "Random",
|
|
"hadamard": "Structured",
|
|
"learned": "Learned, plain",
|
|
"learned_reg": r"Learned, regularized~\eqref{eq:regloss}",
|
|
"invariant_learned": r"\textbf{Invariant, KM (lrn.)}",
|
|
}
|
|
RECEIVER = {
|
|
"legit": "Legitimate", "oma": "OMA",
|
|
"insider": "Insider", "eve": "Outsider",
|
|
}
|
|
|
|
|
|
def f3(x: str) -> str:
|
|
"""Three decimals, or an em-dash for a value that does not apply."""
|
|
try:
|
|
v = float(x)
|
|
except (TypeError, ValueError):
|
|
return "--"
|
|
return "--" if math.isnan(v) else f"{v:.3f}"
|
|
|
|
|
|
def f4(x: str) -> str:
|
|
"""Four decimals, for a column whose values sit against the
|
|
random-guess level and would otherwise all print as 1.000 while the
|
|
body quotes their distance from it in units of 1e-4."""
|
|
try:
|
|
v = float(x)
|
|
except (TypeError, ValueError):
|
|
return "--"
|
|
return "--" if math.isnan(v) else f"{v:.4f}"
|
|
|
|
|
|
def cell(x: str, bold: bool, wide: bool = False) -> str:
|
|
s = f4(x) if wide else f3(x)
|
|
if s == "--":
|
|
return "--"
|
|
return rf"$\mathbf{{{s}}}$" if bold else f"${s}$"
|
|
|
|
|
|
def compare_table():
|
|
print("% Table: scheme comparison (from sec_compare.csv)")
|
|
rows = list(csv.DictReader(open(DATA / "sec_compare.csv")))
|
|
# 9.3: the proposal first, as every figure legend lists it
|
|
order = ["proposed", "proposed_learned", "public_mask", "perm_key",
|
|
"index_cipher", "oma_plain"]
|
|
rows.sort(key=lambda r: order.index(r["scheme"]))
|
|
# stage_E does not jam the orthogonal reference, because the jammer an
|
|
# OMA user faces is targeted at public slots rather than mask-matched
|
|
# or mask-blind. stage_L measures that case, so the cell comes from
|
|
# there instead of being left empty.
|
|
jam = {float(r["jsr_db"]): r
|
|
for r in csv.DictReader(open(DATA / "sec_jam_cmp.csv"))}
|
|
oma_jam = jam[0.0]["oma_targeted"]
|
|
for r in rows:
|
|
b = r["scheme"].startswith("proposed")
|
|
if r["scheme"] == "oma_plain" and f3(r["jam0_ser"]) == "--":
|
|
r["jam0_ser"] = oma_jam
|
|
# four decimals would still print 1.0000 here, so the column
|
|
# stays at three and the caption names the chance level
|
|
cells = [cell(r[k], b) for k in
|
|
("eve_out", "eve_in", "jam0_ser")]
|
|
print(f"{NAME[r['scheme']]} & " + " & ".join(cells) + r" \\")
|
|
|
|
|
|
def maskfam_table():
|
|
print("% Table: key families (from sec_maskfam.csv)")
|
|
for r in csv.DictReader(open(DATA / "sec_maskfam.csv")):
|
|
# the structured family is the main configuration, so its row is
|
|
# emphasized the same way the proposed row is in the comparison
|
|
b = r["family"] == "hadamard"
|
|
cells = [cell(r[k], b) for k in
|
|
("legit_ser", "eve_ser", "mask_xcorr")]
|
|
name = NAME[r["family"]]
|
|
if b:
|
|
name = r"\textbf{" + name + "}"
|
|
print(f"{name} & " + " & ".join(cells) + r" \\")
|
|
|
|
|
|
def refresh_tables():
|
|
print("% Table: key refresh (from refresh_summary.csv)")
|
|
for r in csv.DictReader(open(DATA / "refresh_summary.csv")):
|
|
b = r["scheme"].startswith("Invariant")
|
|
name = (r"\textbf{" + r["scheme"] + "}") if b else r["scheme"]
|
|
f = (lambda t: r"\mathbf{" + t + "}") if b else (lambda t: t)
|
|
print(f"{name} & ${f(format(float(r['legit']), '.3f'))}$ & "
|
|
f"${f(format(float(r['eve']), '.4f'))}$ & "
|
|
# the unit lives in the header, not in every cell
|
|
f"${f(format(float(r['entropy_bits']), '.1f'))}$" + r" \\")
|
|
if __name__ == "__main__":
|
|
compare_table(); print()
|
|
maskfam_table(); print()
|
|
refresh_tables()
|