Add a mangled-macro scan and a ratio inset for the OMA comparison
check_texhealth.py flags control characters and bare macro stubs left by a shell heredoc, a class of corruption that LaTeX compiles without complaint. It skips when main.tex is absent, as in this package. fig_sec_snr now carries an inset with the OMA-to-proposed SER ratio, since the 1 to 9 percent advantage is invisible across two decades of log axis, and save() now guards inset overlap as it guards the legend. check_consistency covers the L=8 crossover, the inset ratio span, the stated secret sizes, and the Fig. 5 curve coincidence: 29 assertions.
This commit is contained in:
@@ -127,6 +127,34 @@ chk("legit leads OMA at every point",
|
||||
for s in st["recovery"]),
|
||||
"checked %d points" % len(st["recovery"]))
|
||||
|
||||
# --- the room argument of Fig. 2 and its evidence in Fig. 3 -----------
|
||||
kl = {int(r["L"]): r for r in rows("sec_keylen.csv")}
|
||||
r8 = kl[8]
|
||||
chk("L=8 crowding, proposal behind OMA",
|
||||
round(float(r8["legit_ser"]), 3) == 0.949
|
||||
and round(float(r8["oma"]), 3) == 0.685,
|
||||
"%.3f vs %.3f" % (float(r8["legit_ser"]), float(r8["oma"])))
|
||||
chk("0.949 and 0.685 in tex", "0.949" in tex and "0.685" in tex,
|
||||
"searched tex", needs_tex=True)
|
||||
|
||||
# the Fig. 2 inset plots this ratio, so its stated span must hold
|
||||
sr = rows("sec_snr.csv")
|
||||
rt = [float(r["oma"]) / float(r["legit"]) for r in sr]
|
||||
chk("inset ratio spans 1.01 to 1.09", 1.005 < min(rt) and max(rt) < 1.095,
|
||||
"%.3f to %.3f" % (min(rt), max(rt)))
|
||||
|
||||
# the three secrets named in the setup
|
||||
chk("secret sizes UL=64, perm 64, pad 16",
|
||||
all(t in tex for t in ["$UL=64$ key entries",
|
||||
"one permutation of $64$ positions",
|
||||
"$16$ pad\nbits per user"]),
|
||||
"searched tex", needs_tex=True)
|
||||
|
||||
# Fig. 5 shows the permutation curve tracking the mask curve
|
||||
sc = rows("sec_sens_cmp.csv")
|
||||
dv = max(abs(float(r["ser_mask"]) - float(r["ser_perm"])) for r in sc)
|
||||
chk("permutation tracks mask in Fig. 5", dv < 0.06, "max gap %.3f" % dv)
|
||||
|
||||
# --- abstract ---------------------------------------------------------
|
||||
a = (tex.split(r"\begin{abstract}")[1].split(r"\end{abstract}")[0].strip()
|
||||
if HAVE_TEX else "")
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Guard against mangled TeX control sequences.
|
||||
|
||||
Shell heredocs silently turn a backslash escape into the control
|
||||
character it names, so \times becomes a tab followed by "imes" and \ref
|
||||
becomes a carriage return followed by "ef". LaTeX compiles both without
|
||||
an error and prints the wreckage, so the build log cannot catch this.
|
||||
This scan can.
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
TEX = Path(__file__).resolve().parents[1] / "main.tex"
|
||||
CTRL = {"\t": "TAB", "\r": "CR", "\x08": "BS", "\x0c": "FF",
|
||||
"\x07": "BEL", "\x0b": "VT", "\x00": "NUL"}
|
||||
# a macro name shorn of its first letter, which is what the escape ate
|
||||
STUBS = ["ef{", "abel{", "ite{", "extbf{", "extit{", "ext{", "imes",
|
||||
"rac{", "eft(", "ight)", "ho_", "elta", "psilon", "ambda",
|
||||
"igma", "ewline", "otag", "uad", "nderline", "ag{", "egin{",
|
||||
"nd{", "aption{", "ilde{", "ar{", "at{", "ec{"]
|
||||
PAT = re.compile("(?<![" + chr(92)*2 + "A-Za-z0-9])(" +
|
||||
"|".join(re.escape(x) for x in STUBS) + ")")
|
||||
|
||||
|
||||
def main():
|
||||
if not TEX.exists():
|
||||
print(" SKIP tex health :: main.tex not in this package")
|
||||
return 0
|
||||
lines = TEX.read_text(encoding="utf-8").split("\n")
|
||||
hits = []
|
||||
for i, line in enumerate(lines, 1):
|
||||
for ch, name in CTRL.items():
|
||||
if ch in line:
|
||||
hits.append("CTRL %s line %d: %r" % (name, i, line[:90]))
|
||||
for m in PAT.finditer(line):
|
||||
seg = line[max(0, m.start() - 30):m.start() + 30]
|
||||
hits.append("STUB %r line %d: %r" % (m.group(1), i, seg))
|
||||
for h in hits:
|
||||
print(" FAIL " + h)
|
||||
if hits:
|
||||
print("tex health: %d suspicious sequences" % len(hits))
|
||||
return 1
|
||||
print(" PASS tex health :: no mangled control sequences")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
+30
-2
@@ -86,7 +86,7 @@ def col(rows, k, f=float):
|
||||
return [f(r[k]) for r in rows]
|
||||
|
||||
|
||||
def save(fig, name):
|
||||
def save(fig, name, insets=()):
|
||||
"""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
|
||||
@@ -129,6 +129,20 @@ def save(fig, name):
|
||||
raise RuntimeError(
|
||||
f"{name}: a data curve passes under the legend "
|
||||
f"box; move the legend or shrink it")
|
||||
for ins in insets:
|
||||
ib = ins.get_window_extent()
|
||||
for a in fig.axes:
|
||||
if a is ins:
|
||||
continue
|
||||
for line in a.get_lines():
|
||||
xy = line.get_xydata()
|
||||
if len(xy) == 0:
|
||||
continue
|
||||
for px, py in a.transData.transform(xy):
|
||||
if ib.x0 <= px <= ib.x1 and ib.y0 <= py <= ib.y1:
|
||||
raise RuntimeError(
|
||||
f"{name}: a data curve passes under the inset "
|
||||
f"panel; move or shrink the inset")
|
||||
fig.savefig(FIG / f"{name}.pdf")
|
||||
plt.close(fig)
|
||||
print("[OK]", name)
|
||||
@@ -154,7 +168,21 @@ def fig_snr():
|
||||
ax.set_ylabel("SER")
|
||||
ax.set_xlim(min(x), max(x))
|
||||
ax.legend(loc="lower left")
|
||||
save(fig, "fig_sec_snr")
|
||||
|
||||
# the gap is a coding gain of a few percent, invisible against two
|
||||
# decades of SER, so an inset reports it as a ratio
|
||||
lg, om = col(r, "legit"), col(r, "oma")
|
||||
ins = ax.inset_axes([0.57, 0.58, 0.39, 0.25])
|
||||
ins.plot(x, [o / l for l, o in zip(lg, om)], color=C_OMA, lw=1.0,
|
||||
marker="^", ms=2.4, markevery=2)
|
||||
ins.axhline(1.0, color="0.55", lw=0.6, ls="--")
|
||||
ins.set_xlim(min(x), max(x))
|
||||
ins.set_ylim(0.995, 1.105)
|
||||
ins.set_yticks([1.00, 1.05, 1.10])
|
||||
ins.set_xticks([0, 10, 20])
|
||||
ins.tick_params(labelsize=5.2, length=1.8, pad=1.0)
|
||||
ins.set_title("OMA / proposed SER", fontsize=5.6, pad=1.5)
|
||||
save(fig, "fig_sec_snr", insets=[ins])
|
||||
|
||||
|
||||
def fig_keylen():
|
||||
|
||||
Reference in New Issue
Block a user