Files
TOIFAS/code/diag_whygap.py
T
KiHoLee 138897aa8d Ship the learned-key pipeline, without which six figures cannot be rebuilt
The package was missing every script behind the KM (lrn.) curves and
both learned table rows: exp_learned's driver, the merge that folds the
learned rows into sec_compare.csv and refresh_summary.csv, and the
report that reads the learned numbers back. It was also missing
sec_keylen_perm.csv, so Fig. 3 could not be regenerated at all, and the
two diagnostics that answer why a fixed key beats a learned one here and
where a learned mask would win instead.

The learned artifacts themselves are regenerated. They were trained on
the cross-entropy alone, which drifts to disjoint sparse supports: 99
percent of each key's energy on about six of the 64 entries, so a digit
is decided over a sixth of its period and the key set is a choice of
support rather than a dense direction in R^L. They are now the
regularized keys of Section V-C, and check_consistency asserts which of
the two families the figures draw.

Verified from inside this repository: replot_security.py rebuilds all
seven result figures, make_tables.py reproduces both result tables, and
check_consistency.py passes every check that does not need the
manuscript.

The README now lists what ships. Its run list, layout and figure map had
none of the learned pipeline, named two tables the manuscript renders as
prose, and gave Fig. 3 no data file for its permutation curve.
2026-08-28 23:58:01 +09:00

121 lines
4.4 KiB
Python

# -*- coding: utf-8 -*-
"""Why do structured keys beat learned ones on the legitimate error rate?
The gap is 0.053 against 0.064 at 10 dB, and a reader may reasonably
suspect that the learned keys are handicapped, since they alone are
trained under the channel while the structured ones are fixed by
construction. This measures where the gap comes from.
Three questions, one experiment each.
1. Is the gap a channel-adaptation failure? If it were, the two families
would differ by more at some channel qualities than at others. The
ratio across the SNR sweep answers this from data already on disk.
2. Is the structured key a point that training can improve on? Start
training from the Walsh-Hadamard keys with the masks unfrozen and let
Adam move them. If the structured point is a genuine optimum, the
error rate stays or rises; if training is merely under-converged from
its random start, it falls.
3. What does the learned key lose? Two candidates, measured directly:
residual cross-user correlation, which the analysis names as the
first-order leakage and interference term, and departure from unit
modulus, which spreads the key energy unevenly across the entries so
that a digit is decided over an effectively shorter support.
Run: python code/diag_whygap.py
"""
from __future__ import annotations
import csv
import math
import sys
from pathlib import Path
import torch
sys.path.insert(0, str(Path(__file__).resolve().parent))
from exp_full import (MAIN_D, base_keys, get_model, main_model, # noqa: E402
mean_abs_xcorr)
from sse_lib import DATA, DEVICE, eval_ser_sse # noqa: E402
import sse_lib as L # noqa: E402
FRAMES = 300_000
SNR = 10.0
def modulus_stats(W):
"""How far the key entries are from unit modulus, per user.
A unit-modulus key puts the same energy on every entry, so the
signal term does not depend on the key and every entry of the period
carries its share of the decision. The ratio below is the effective
fraction of the L entries the key actually uses, by the
participation ratio (sum a^2)^2 / (L sum a^4) with a the entry
magnitudes. It is one for a unit-modulus key and 1/L for a key that
puts everything on one entry.
"""
a2 = W.pow(2)
pr = a2.sum(dim=1).pow(2) / (W.shape[1] * a2.pow(2).sum(dim=1))
return pr
def report(name, model, rows):
W = model.masks().detach().cpu()
ser = eval_ser_sse(model, [SNR], frames=FRAMES)[0]
kap = mean_abs_xcorr(model.masks().detach())
pr = modulus_stats(W)
print("%-24s SER %.5f kappa-bar %.5f entry use %.3f"
% (name, ser, kap, float(pr.mean())))
rows.append([name, "%.5f" % ser, "%.5f" % kap, "%.4f" % float(pr.mean())])
return ser
def main():
rows = []
print("main configuration d=%d, L=%d, 10 dB, %d frames\n"
% (MAIN_D, MAIN_D // 4, FRAMES))
print("-- the two families as the paper plots them --")
fix = main_model()
s_fix = report("structured (frozen)", fix, rows)
free = get_model(iters=4000)
s_free = report("learned (free start)", free, rows)
print("\n-- question 2: can training improve on the structured key? --")
# same trainer, same iterations, same seed, but the masks start at
# the Walsh-Hadamard point and are free to move
from sse_lib import SSE, set_seed
set_seed(1)
m = SSE(P=4, vu=16, d=MAIN_D, users=4).to(DEVICE)
with torch.no_grad():
m.W.copy_(base_keys(4, MAIN_D // 4).to(DEVICE))
m.W.requires_grad_(True)
L.train_sse(m, iters=4000, batch=256, lr=3e-3, seed=1)
m.calibrate_power()
s_warm = report("learned (Walsh start)", m, rows)
print("\nreading:")
print(" free start %+.1f percent against the structured key"
% (100.0 * (s_free - s_fix) / s_fix))
print(" Walsh start %+.1f percent against the structured key"
% (100.0 * (s_warm - s_fix) / s_fix))
if s_warm > s_fix:
print(" training moves off the structured point and pays for it,")
print(" so the structured key is not a point learning improves on.")
else:
print(" training improves on the structured point, so the gap is")
print(" under-convergence from the random start, not geometry.")
out = DATA / "whygap.csv"
with open(out, "w", newline="") as f:
w = csv.writer(f)
w.writerow(["family", "legit_ser", "kappa_bar", "entry_use"])
w.writerows(rows)
print("\n[csv]", out)
if __name__ == "__main__":
main()