Leakage, semantic and robustness experiments from the revision
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Information-theoretic security metrics for the main configuration.
|
||||
|
||||
The evaluation so far reported only the eavesdropper SER. This stage adds
|
||||
the quantities a physical-layer-security reader expects, all computed
|
||||
from the SAME Monte Carlo the SER curves use, so no new modelling
|
||||
assumption enters.
|
||||
|
||||
Every metric is derived from the empirical joint law of the transmitted
|
||||
digit and the DECISION each receiver makes. That decision is a
|
||||
deterministic function of the received frame, so the data-processing
|
||||
inequality makes each leakage number a LOWER bound on the true
|
||||
I(s_u; y_E): what the modelled correlation eavesdropper actually
|
||||
extracts. Reported per frame, an index carries P digits, so the frame
|
||||
quantities are P times the per-digit ones under the independent-digit
|
||||
source the evaluation uses.
|
||||
|
||||
I(s;s_hat) mutual information between the digit and the decision
|
||||
H(s|s_hat) equivocation, and its ratio to log2(V)
|
||||
TV distinguishing advantage, the average total variation
|
||||
between the decision law given a digit and its marginal
|
||||
R_s secrecy rate, the legitimate information rate minus the
|
||||
eavesdropper one, per frame
|
||||
|
||||
Run under WSL. Writes data/infotheory.csv.
|
||||
"""
|
||||
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))
|
||||
import sse_lib as L
|
||||
from sse_lib import DATA, DEVICE, rayleigh_gain, snr_to_sigma2
|
||||
from exp_full import main_model, eve_wrong_mask
|
||||
from exp_refresh import kdf_invariant, install
|
||||
|
||||
FRAMES = 400_000
|
||||
CHUNK = 40_000
|
||||
SNRS = [0.0, 5.0, 10.0, 15.0, 20.0]
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def confusion_refreshed(m, snr_db, sub_key, base_keys, base_book,
|
||||
blocks=64, frames=FRAMES, seed=4242):
|
||||
"""The same joint counts when the key is redrawn from the invariance
|
||||
group every block, against an eavesdropper holding one fixed
|
||||
substitute. Each block contributes frames/blocks frames."""
|
||||
torch.manual_seed(seed + int(10 * snr_db))
|
||||
C = torch.zeros(m.vu, m.vu, dtype=torch.float64, device=DEVICE)
|
||||
per = max(CHUNK // 4, frames // blocks)
|
||||
for b in range(blocks):
|
||||
sg, cp, up = kdf_invariant(5150, b, m.users, m.L)
|
||||
install(m, sg * base_keys[up], base_book, colperm=cp)
|
||||
C += confusion(m, snr_db, sub_key=sub_key, frames=per,
|
||||
seed=seed + 97 * b)
|
||||
install(m, base_keys, base_book)
|
||||
return C
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def confusion(m, snr_db, sub_key=None, frames=FRAMES, seed=777):
|
||||
"""Empirical joint counts of (transmitted digit, decided digit) for
|
||||
user 0, pooled over the P periods. sub_key None means the legitimate
|
||||
receiver; otherwise the eavesdropper substitutes that key."""
|
||||
torch.manual_seed(seed + int(10 * snr_db))
|
||||
C = torch.zeros(m.vu, m.vu, dtype=torch.float64, device=DEVICE)
|
||||
keys = m.masks() if sub_key is None else sub_key.to(DEVICE)
|
||||
done = 0
|
||||
while done < frames:
|
||||
n = min(CHUNK, frames - done)
|
||||
dig = torch.randint(m.vu, (n, m.users, m.P), device=DEVICE)
|
||||
Bn = m.unit_codebook()
|
||||
e = Bn[dig] / math.sqrt(m.P)
|
||||
y = (e * m.masks()[None, :, None, :]).sum(dim=1) / m.c
|
||||
h = rayleigh_gain((n, 1), device=DEVICE)
|
||||
sig = snr_to_sigma2(torch.full((n,), snr_db), m.d).to(DEVICE).sqrt()
|
||||
rx = h[:, :, None, None] * y[:, None] \
|
||||
+ sig[:, None, None, None] * torch.randn(n, 1, m.P, m.L,
|
||||
device=DEVICE)
|
||||
r = rx / h[:, :, None, None].clamp_min(1e-6)
|
||||
cand = Bn[None, :, :] * keys[:1, None, :]
|
||||
dec = torch.einsum("nupl,uvl->nupv", r, cand).argmax(-1)[:, 0]
|
||||
idx = dig[:, 0].reshape(-1) * m.vu + dec.reshape(-1)
|
||||
C += torch.bincount(idx, minlength=m.vu * m.vu).reshape(
|
||||
m.vu, m.vu).to(torch.float64)
|
||||
done += n
|
||||
return C
|
||||
|
||||
|
||||
def metrics(C, P, V):
|
||||
"""Mutual information, equivocation and distinguishing advantage from
|
||||
a joint count matrix, all in bits."""
|
||||
J = C / C.sum()
|
||||
px, py = J.sum(1), J.sum(0)
|
||||
nz = J > 0
|
||||
mi = float((J[nz] * (J[nz] / (px[:, None] * py[None, :])[nz]).log2()).sum())
|
||||
hx = float(-(px[px > 0] * px[px > 0].log2()).sum())
|
||||
equiv = hx - mi # H(digit | decision)
|
||||
# distinguishing advantage: E_s || p(dec|s) - p(dec) ||_TV
|
||||
cond = J / px[:, None].clamp_min(1e-300)
|
||||
tv = float((px * 0.5 * (cond - py[None, :]).abs().sum(1)).sum())
|
||||
return {"mi_digit": mi, "equiv_digit": equiv,
|
||||
"mi_frame": P * mi, "equiv_frame": P * equiv,
|
||||
"equiv_ratio": P * equiv / math.log2(V), "tv": tv}
|
||||
|
||||
|
||||
def main():
|
||||
m = main_model()
|
||||
m.eval()
|
||||
ew = eve_wrong_mask(m.users, m.L, seed=20260813)
|
||||
base_keys = m.W.detach().clone().cpu()
|
||||
base_book = m.B.detach().clone().cpu()
|
||||
rows = []
|
||||
for snr in SNRS:
|
||||
lg = metrics(confusion(m, snr), m.P, m.V)
|
||||
ev = metrics(confusion(m, snr, sub_key=ew), m.P, m.V)
|
||||
rf = metrics(confusion_refreshed(m, snr, ew, base_keys, base_book),
|
||||
m.P, m.V)
|
||||
rs = max(0.0, lg["mi_frame"] - ev["mi_frame"])
|
||||
rs_r = max(0.0, lg["mi_frame"] - rf["mi_frame"])
|
||||
rows.append((snr,
|
||||
"%.4f" % lg["mi_frame"], "%.6f" % ev["mi_frame"],
|
||||
"%.6f" % ev["equiv_ratio"], "%.6f" % ev["tv"],
|
||||
"%.4f" % rs,
|
||||
"%.6f" % rf["mi_frame"], "%.6f" % rf["equiv_ratio"],
|
||||
"%.6f" % rf["tv"], "%.4f" % rs_r))
|
||||
print("%5.1f dB legit %6.3f | fixed key: MI %.3f TV %.3f Rs %6.3f "
|
||||
"| refreshed: MI %.4f TV %.4f Rs %6.3f"
|
||||
% (snr, lg["mi_frame"], ev["mi_frame"], ev["tv"], rs,
|
||||
rf["mi_frame"], rf["tv"], rs_r), flush=True)
|
||||
out = DATA / "infotheory.csv"
|
||||
with open(out, "w", newline="") as f:
|
||||
w = csv.writer(f)
|
||||
w.writerow(["snr_db", "mi_legit_bits",
|
||||
"mi_eve_fixed_bits", "equiv_ratio_fixed", "tv_fixed",
|
||||
"secrecy_rate_fixed_bits",
|
||||
"mi_eve_refresh_bits", "equiv_ratio_refresh",
|
||||
"tv_refresh", "secrecy_rate_refresh_bits"])
|
||||
w.writerows(rows)
|
||||
print("[csv]", out)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,175 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Semantic leakage: does a wrong index still carry the meaning?
|
||||
|
||||
The symbol error rate counts any wrong index as a total failure, which
|
||||
is the right accounting for a bit pipe and the wrong one for a semantic
|
||||
pipe: a token decoded as a near synonym has leaked the meaning even
|
||||
though the index is wrong. This stage measures what the SER cannot see,
|
||||
on two semantic scales.
|
||||
|
||||
codeword cosine cos(e_shat, e_s) between the embedding a receiver
|
||||
reconstructs and the transmitted one, uniform indices
|
||||
BERT cosine cos of the BERT input embeddings of the decoded and
|
||||
the transmitted token, on the AG News stream, which
|
||||
is semantic similarity in the space the vocabulary
|
||||
was built for
|
||||
|
||||
Each is reported for the legitimate receiver, the outsider and the
|
||||
insider, against the chance level of two independently drawn tokens.
|
||||
A scheme leaks semantically if the adversary's similarity sits above
|
||||
that chance level.
|
||||
|
||||
Run under WSL. Writes data/semantic.csv.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import math
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
import sse_lib as L
|
||||
from sse_lib import DATA, DEVICE, rayleigh_gain, snr_to_sigma2
|
||||
from exp_full import main_model, eve_wrong_mask
|
||||
|
||||
FRAMES = 200_000
|
||||
CHUNK = 20_000
|
||||
SNRS = [0.0, 10.0, 20.0]
|
||||
REAL_SNRS = [0.0, 10.0, 20.0, 28.0]
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def decide(m, dig, snr_db, keys, gen=None):
|
||||
"""Decisions of a receiver holding `keys`, for the frames carrying
|
||||
`dig`. Returns the decided digits of user 0."""
|
||||
n = dig.shape[0]
|
||||
Bn = m.unit_codebook()
|
||||
e = Bn[dig] / math.sqrt(m.P)
|
||||
y = (e * m.masks()[None, :, None, :]).sum(dim=1) / m.c
|
||||
h = rayleigh_gain((n, 1), device=DEVICE)
|
||||
sig = snr_to_sigma2(torch.full((n,), snr_db), m.d).to(DEVICE).sqrt()
|
||||
rx = h[:, :, None, None] * y[:, None] \
|
||||
+ sig[:, None, None, None] * torch.randn(n, 1, m.P, m.L,
|
||||
device=DEVICE)
|
||||
r = rx / h[:, :, None, None].clamp_min(1e-6)
|
||||
cand = Bn[None, :, :] * keys[:1, None, :]
|
||||
return torch.einsum("nupl,uvl->nupv", r, cand).argmax(-1)[:, 0]
|
||||
|
||||
|
||||
def frame_embedding(m, digits):
|
||||
"""The d-dimensional embedding an index maps to, digits (N,P)."""
|
||||
Bn = m.unit_codebook()
|
||||
return (Bn[digits] / math.sqrt(m.P)).reshape(digits.shape[0], -1)
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def codeword_cosine(m, snr_db, keys, seed):
|
||||
"""Mean cosine between the reconstructed and the true embedding."""
|
||||
torch.manual_seed(seed + int(10 * snr_db))
|
||||
tot, done = 0.0, 0
|
||||
while done < FRAMES:
|
||||
n = min(CHUNK, FRAMES - done)
|
||||
dig = torch.randint(m.vu, (n, m.users, m.P), device=DEVICE)
|
||||
dec = decide(m, dig, snr_db, keys)
|
||||
c = F.cosine_similarity(frame_embedding(m, dec),
|
||||
frame_embedding(m, dig[:, 0]), dim=1)
|
||||
tot += float(c.sum())
|
||||
done += n
|
||||
return tot / done
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def codeword_chance(m, seed=99):
|
||||
"""Cosine between two independently drawn indices."""
|
||||
torch.manual_seed(seed)
|
||||
a = torch.randint(m.vu, (FRAMES, m.P), device=DEVICE)
|
||||
b = torch.randint(m.vu, (FRAMES, m.P), device=DEVICE)
|
||||
return float(F.cosine_similarity(frame_embedding(m, a),
|
||||
frame_embedding(m, b), dim=1).mean())
|
||||
|
||||
|
||||
def load_bert_embeddings():
|
||||
"""BERT input embedding matrix, the space AG News tokens live in."""
|
||||
from transformers import AutoModel
|
||||
mdl = AutoModel.from_pretrained("bert-base-uncased")
|
||||
return mdl.get_input_embeddings().weight.detach().to(DEVICE)
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def real_semantic(m, emb, ids_all, snr_db, keys, seed):
|
||||
"""Mean BERT cosine between the decoded and the transmitted token of
|
||||
user 0. ids_all is (N,U): every user carries its OWN stream, so an
|
||||
insider decoding user 0 gains nothing from its own traffic."""
|
||||
torch.manual_seed(seed + int(10 * snr_db))
|
||||
n = ids_all.shape[0]
|
||||
dig = torch.stack([(ids_all // (m.vu ** p)) % m.vu
|
||||
for p in range(m.P)], -1).to(DEVICE) # (N,U,P)
|
||||
tot, done = 0.0, 0
|
||||
while done < n:
|
||||
k = min(CHUNK, n - done)
|
||||
dec = decide(m, dig[done:done + k], snr_db, keys)
|
||||
rec = sum(dec[:, p] * (m.vu ** p) for p in range(m.P))
|
||||
true = ids_all[done:done + k, 0].to(DEVICE)
|
||||
rec = rec.clamp(max=emb.shape[0] - 1)
|
||||
tot += float(F.cosine_similarity(emb[rec], emb[true], dim=1).sum())
|
||||
done += k
|
||||
return tot / n
|
||||
|
||||
|
||||
def main():
|
||||
m = main_model()
|
||||
m.eval()
|
||||
ew = eve_wrong_mask(m.users, m.L, seed=20260813)
|
||||
insider = m.masks()[1:2].detach() # user 2 attacking user 1
|
||||
rows = []
|
||||
|
||||
chance = codeword_chance(m)
|
||||
print("codeword chance cosine %.4f" % chance, flush=True)
|
||||
for snr in SNRS:
|
||||
lg = codeword_cosine(m, snr, m.masks(), 5150)
|
||||
ev = codeword_cosine(m, snr, ew.to(DEVICE), 5151)
|
||||
ins = codeword_cosine(m, snr, insider, 5152)
|
||||
rows.append(("codeword", snr, "%.4f" % lg, "%.4f" % ev,
|
||||
"%.4f" % ins, "%.4f" % chance))
|
||||
print("codeword %4.0f dB legit %.4f outsider %.4f insider %.4f"
|
||||
% (snr, lg, ev, ins), flush=True)
|
||||
|
||||
# real token streams in the BERT embedding space
|
||||
try:
|
||||
from exp_real_sec import load_streams
|
||||
streams, _bounds, _vocab = load_streams()
|
||||
nmin = min(len(x) for x in streams)
|
||||
ids = torch.stack([torch.as_tensor(x[:nmin], dtype=torch.long)
|
||||
for x in streams], dim=1)[:100_000] # (N,U)
|
||||
emb = load_bert_embeddings()
|
||||
rnd = torch.randint(0, emb.shape[0], (ids.shape[0],))
|
||||
ch = float(F.cosine_similarity(emb[ids[:, 0].to(DEVICE)],
|
||||
emb[rnd.to(DEVICE)], dim=1).mean())
|
||||
print("BERT chance cosine %.4f" % ch, flush=True)
|
||||
for snr in REAL_SNRS:
|
||||
lg = real_semantic(m, emb, ids, snr, m.masks(), 5160)
|
||||
ev = real_semantic(m, emb, ids, snr, ew.to(DEVICE), 5161)
|
||||
ins = real_semantic(m, emb, ids, snr, insider, 5162)
|
||||
rows.append(("bert", snr, "%.4f" % lg, "%.4f" % ev,
|
||||
"%.4f" % ins, "%.4f" % ch))
|
||||
print("bert %4.0f dB legit %.4f outsider %.4f insider %.4f"
|
||||
% (snr, lg, ev, ins), flush=True)
|
||||
except Exception as exc: # keep the codeword rows
|
||||
print("[skip] real-token semantic stage: %s: %s"
|
||||
% (type(exc).__name__, exc), flush=True)
|
||||
|
||||
out = DATA / "semantic.csv"
|
||||
with open(out, "w", newline="") as f:
|
||||
w = csv.writer(f)
|
||||
w.writerow(["space", "snr_db", "legit", "outsider", "insider",
|
||||
"chance"])
|
||||
w.writerows(rows)
|
||||
print("[csv]", out)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,148 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Two robustness sweeps the evaluation was missing.
|
||||
|
||||
Users. Every other stage fixes U=4. The structured family admits U up to
|
||||
L-1, and as U approaches L the frame fills with cross-user patterns, so
|
||||
this sweep asks what the load costs the legitimate users and whether the
|
||||
confidentiality survives it.
|
||||
|
||||
Channel estimation. Every other stage equalizes with the exact gain.
|
||||
Here the receiver divides by an estimate h+e with e zero mean and
|
||||
variance sigma_e^2 relative to the gain, so the residual phase and
|
||||
amplitude error enters the correlation the same way a key mismatch
|
||||
would, and the question is how much of the legitimate margin it costs.
|
||||
|
||||
Run under WSL. Writes data/users.csv and data/csi.csv.
|
||||
"""
|
||||
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))
|
||||
import sse_lib as L
|
||||
from sse_lib import DATA, DEVICE, rayleigh_gain, snr_to_sigma2, eval_ser_sse
|
||||
from exp_full import (main_model, base_keys, get_model, eve_wrong_mask,
|
||||
eval_ser_eve, mean_abs_xcorr, MAIN_D)
|
||||
|
||||
SNR = 10.0
|
||||
FRAMES = 400_000
|
||||
CHUNK = 40_000
|
||||
USERS = [2, 4, 8, 16, 32, 48]
|
||||
CSI = [0.0, 1e-3, 1e-2, 3e-2, 1e-1]
|
||||
PHASE = [0.0, 0.02, 0.05, 0.10, 0.20] # residual phase error, radians rms
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def ser_with_csi_error(m, snr_db, nmse, frames=FRAMES, seed=606):
|
||||
"""Legitimate SER when the receiver equalizes with a noisy estimate."""
|
||||
torch.manual_seed(seed + int(1e4 * nmse))
|
||||
wrong = tot = 0
|
||||
while tot < frames:
|
||||
n = min(CHUNK, frames - tot)
|
||||
dig = torch.randint(m.vu, (n, m.users, m.P), device=DEVICE)
|
||||
Bn = m.unit_codebook()
|
||||
e = Bn[dig] / math.sqrt(m.P)
|
||||
y = (e * m.masks()[None, :, None, :]).sum(dim=1) / m.c
|
||||
h = rayleigh_gain((n, m.users), device=DEVICE)
|
||||
sig = snr_to_sigma2(torch.full((n,), snr_db), m.d).to(DEVICE).sqrt()
|
||||
rx = h[:, :, None, None] * y[:, None] \
|
||||
+ sig[:, None, None, None] * torch.randn(n, m.users, m.P, m.L,
|
||||
device=DEVICE)
|
||||
# estimate with a zero-mean error of the stated relative variance
|
||||
hhat = h + math.sqrt(nmse) * h.abs() * torch.randn_like(h)
|
||||
r = rx / hhat[:, :, None, None].clamp_min(1e-6)
|
||||
cand = Bn[None, :, :] * m.masks()[:, None, :]
|
||||
dec = torch.einsum("nupl,uvl->nupv", r, cand).argmax(-1)
|
||||
wrong += int((dec != dig).any(dim=-1).sum())
|
||||
tot += n * m.users
|
||||
return wrong / tot
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def ser_with_phase_error(m, snr_db, rms, frames=FRAMES, seed=707):
|
||||
"""Legitimate SER under a residual phase error. Entries 2n-1 and 2n
|
||||
are the I and Q of one complex channel use, so an uncompensated
|
||||
phase rotates that pair. Unlike an amplitude error, this is not a
|
||||
common scale and the argmax is not invariant to it."""
|
||||
torch.manual_seed(seed + int(1e3 * rms))
|
||||
wrong = tot = 0
|
||||
half = m.L // 2
|
||||
while tot < frames:
|
||||
n = min(CHUNK, frames - tot)
|
||||
dig = torch.randint(m.vu, (n, m.users, m.P), device=DEVICE)
|
||||
Bn = m.unit_codebook()
|
||||
e = Bn[dig] / math.sqrt(m.P)
|
||||
y = (e * m.masks()[None, :, None, :]).sum(dim=1) / m.c
|
||||
h = rayleigh_gain((n, m.users), device=DEVICE)
|
||||
sig = snr_to_sigma2(torch.full((n,), snr_db), m.d).to(DEVICE).sqrt()
|
||||
noise = torch.randn(n, m.users, m.P, m.L, device=DEVICE)
|
||||
rx = h[:, :, None, None] * y[:, None] + sig[:, None, None, None] * noise
|
||||
r = rx / h[:, :, None, None].clamp_min(1e-6)
|
||||
if rms > 0: # rotate each I/Q pair
|
||||
th = rms * torch.randn(n, m.users, 1, half, device=DEVICE)
|
||||
v = r.reshape(n, m.users, m.P, half, 2)
|
||||
i, q = v[..., 0], v[..., 1]
|
||||
c_, s_ = th.cos(), th.sin() # broadcast over periods
|
||||
r = torch.stack([i * c_ - q * s_, i * s_ + q * c_],
|
||||
dim=-1).reshape(n, m.users, m.P, m.L)
|
||||
cand = Bn[None, :, :] * m.masks()[:, None, :]
|
||||
dec = torch.einsum("nupl,uvl->nupv", r, cand).argmax(-1)
|
||||
wrong += int((dec != dig).any(dim=-1).sum())
|
||||
tot += n * m.users
|
||||
return wrong / tot
|
||||
|
||||
|
||||
def main():
|
||||
# --- users -------------------------------------------------------
|
||||
rows = []
|
||||
print("user load at %g dB, L=%d" % (SNR, MAIN_D // 4), flush=True)
|
||||
for U in USERS:
|
||||
Lp = MAIN_D // 4
|
||||
if U > Lp - 1:
|
||||
print(" U=%d exceeds L-1, skipped" % U, flush=True)
|
||||
continue
|
||||
m = get_model(d=MAIN_D, U=U, iters=4000, freeze_W=base_keys(U, Lp))
|
||||
m.eval()
|
||||
lg = eval_ser_sse(m, [SNR], frames=FRAMES)[0]
|
||||
ew = eve_wrong_mask(U, Lp, seed=20260813)
|
||||
ev = eval_ser_eve(m, ew, [SNR], frames=FRAMES)[0]
|
||||
xc = mean_abs_xcorr(m.masks().detach())
|
||||
rows.append((U, "%.6f" % lg, "%.6f" % ev, "%.6f" % xc))
|
||||
print(" U=%2d legit %.4f eve %.5f xcorr %.2e"
|
||||
% (U, lg, ev, xc), flush=True)
|
||||
with open(DATA / "users.csv", "w", newline="") as f:
|
||||
w = csv.writer(f)
|
||||
w.writerow(["users", "legit_ser", "eve_ser", "mask_xcorr"])
|
||||
w.writerows(rows)
|
||||
print("[csv]", DATA / "users.csv", flush=True)
|
||||
|
||||
# --- channel estimation error ------------------------------------
|
||||
m = main_model()
|
||||
m.eval()
|
||||
rows = []
|
||||
print("channel estimation error at %g dB" % SNR, flush=True)
|
||||
for nmse in CSI:
|
||||
s = ser_with_csi_error(m, SNR, nmse)
|
||||
rows.append(("%g" % nmse, "%.6f" % s))
|
||||
print(" nmse %-6g legit %.4f" % (nmse, s), flush=True)
|
||||
print("residual phase error at %g dB" % SNR, flush=True)
|
||||
prows = []
|
||||
for rms in PHASE:
|
||||
s_ = ser_with_phase_error(m, SNR, rms)
|
||||
prows.append(("%g" % rms, "%.6f" % s_))
|
||||
print(" phase rms %-5g legit %.4f" % (rms, s_), flush=True)
|
||||
with open(DATA / "csi.csv", "w", newline="") as f:
|
||||
w = csv.writer(f)
|
||||
w.writerow(["impairment", "level", "legit_ser"])
|
||||
w.writerows([("amplitude_nmse",) + r for r in rows]
|
||||
+ [("phase_rms_rad",) + r for r in prows])
|
||||
print("[csv]", DATA / "csi.csv")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+10
-10
@@ -34,16 +34,16 @@ FIG.mkdir(exist_ok=True)
|
||||
plt.rcParams.update({
|
||||
"font.family": "serif",
|
||||
"font.serif": ["DejaVu Serif", "Times New Roman"],
|
||||
# The manuscript includes each result figure at 0.85 of a 3.455 in
|
||||
# column while the canvas is 3.15 in, a printed scale of 0.933. Every
|
||||
# The manuscript includes each result figure at 0.70 of a 3.455 in
|
||||
# column while the canvas is 3.15 in, a printed scale of 0.768. Every
|
||||
# size below is therefore pre-divided by that scale so the PRINTED
|
||||
# sizes are 9 pt labels, 8 pt ticks and a 6.6 pt legend. Change the
|
||||
# sizes are 8 pt labels, 7 pt ticks and a 5.8 pt legend. Change the
|
||||
# include width and these must change with it.
|
||||
"font.size": 9.7,
|
||||
"axes.labelsize": 9.7,
|
||||
"legend.fontsize": 7.1,
|
||||
"xtick.labelsize": 8.6,
|
||||
"ytick.labelsize": 8.6,
|
||||
"font.size": 10.4,
|
||||
"axes.labelsize": 10.4,
|
||||
"legend.fontsize": 7.6,
|
||||
"xtick.labelsize": 9.2,
|
||||
"ytick.labelsize": 9.2,
|
||||
"axes.grid": True,
|
||||
"grid.linestyle": "--",
|
||||
"grid.linewidth": 0.4,
|
||||
@@ -53,7 +53,7 @@ plt.rcParams.update({
|
||||
"figure.figsize": (3.15, 2.25), # shorter canvas: same printed width and font size, less page height
|
||||
"pdf.fonttype": 42,
|
||||
})
|
||||
AXES_RECT = dict(left=0.205, right=0.970, top=0.955, bottom=0.215)
|
||||
AXES_RECT = dict(left=0.215, right=0.970, top=0.955, bottom=0.225)
|
||||
|
||||
C_LEGIT = "#c0392b"
|
||||
C_EVE = "#2c5fa8"
|
||||
@@ -204,7 +204,7 @@ def main_legit(snr_db="10"):
|
||||
def place_legend(ax, cands=("lower left", "upper left", "center left",
|
||||
"center right", "lower center", "upper right",
|
||||
"upper center", "center", "lower right"),
|
||||
sizes=(7.1, 6.8, 6.6), ncol=1):
|
||||
sizes=(7.6, 7.2, 6.8, 6.4, 6.0), ncol=1):
|
||||
"""Choose the location and font size whose box the fewest curve points
|
||||
fall inside, scored on rendered geometry rather than guessed from the
|
||||
data. The size sweep is what makes a long label set placeable: a
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
impairment,level,legit_ser
|
||||
amplitude_nmse,0,0.053300
|
||||
amplitude_nmse,0.001,0.052325
|
||||
amplitude_nmse,0.01,0.053213
|
||||
amplitude_nmse,0.03,0.052790
|
||||
amplitude_nmse,0.1,0.052969
|
||||
phase_rms_rad,0,0.053000
|
||||
phase_rms_rad,0.02,0.052800
|
||||
phase_rms_rad,0.05,0.052500
|
||||
phase_rms_rad,0.1,0.053800
|
||||
phase_rms_rad,0.2,0.056600
|
||||
|
@@ -0,0 +1,6 @@
|
||||
snr_db,mi_legit_bits,mi_eve_fixed_bits,equiv_ratio_fixed,tv_fixed,secrecy_rate_fixed_bits,mi_eve_refresh_bits,equiv_ratio_refresh,tv_refresh,secrecy_rate_refresh_bits
|
||||
0.0,9.9561,0.653799,0.959136,0.186184,9.3023,0.022776,0.998576,0.036130,9.9334
|
||||
5.0,13.3042,1.036897,0.935192,0.234795,12.2673,0.039079,0.997557,0.047628,13.2651
|
||||
10.0,14.9300,1.344006,0.915999,0.266709,13.5860,0.055213,0.996548,0.056616,14.8748
|
||||
15.0,15.6143,1.531415,0.904285,0.284074,14.0829,0.066127,0.995866,0.061766,15.5482
|
||||
20.0,15.8604,1.631648,0.898021,0.292605,14.2287,0.070091,0.995618,0.063096,15.7903
|
||||
|
@@ -0,0 +1,8 @@
|
||||
space,snr_db,legit,outsider,insider,chance
|
||||
codeword,0.0,0.7866,-0.0164,0.0008,-0.0000
|
||||
codeword,10.0,0.9730,-0.0238,0.0000,-0.0000
|
||||
codeword,20.0,0.9972,-0.0262,-0.0001,-0.0000
|
||||
bert,0.0,0.7051,0.2576,0.2482,0.2579
|
||||
bert,10.0,0.9619,0.2498,0.2451,0.2579
|
||||
bert,20.0,0.9962,0.2471,0.2447,0.2579
|
||||
bert,28.0,0.9991,0.2466,0.2447,0.2579
|
||||
|
@@ -0,0 +1,7 @@
|
||||
users,legit_ser,eve_ser,mask_xcorr
|
||||
2,0.026755,0.999999,0.000000
|
||||
4,0.053062,0.999707,0.000000
|
||||
8,0.106523,0.999414,0.000000
|
||||
16,0.256325,0.999948,0.000000
|
||||
32,0.946055,0.999983,0.000000
|
||||
48,0.997737,0.999983,0.000000
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user