Files
TOIFAS/code/make_tables.py
T
KiHoLee de066fc09a Carry the caption, label and figure fixes into the release
The table generator uses the legend form of the scheme name, the system
figure groups blocks with dashed outlines, and the figure PDFs are
rebuilt from the current data.
2026-08-29 12:24:39 +09:00

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",
"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()