Files
TOIFAS/code/diag_interference.py
T
KiHoLee 8f26bf9bc9 Main configuration d=256, L=64: all data, figures and checks re-run
Every OMA reference takes the L/16 combining gain so the comparison
stays resource matched, four hardcoded copies of the configuration are
replaced by MAIN_D or the main curve, and stage_J's K-by-L Gaussian
draw becomes its exact scalar Beta equivalent.
2026-08-18 14:40:48 +09:00

88 lines
3.1 KiB
Python

# -*- coding: utf-8 -*-
"""Where does the legitimate advantage over OMA go?
The legitimate curve sits above the single-user M-ary bound, and this
script splits the distance into its two possible causes: residual
multi-user interference, which orthogonal keys need not remove because
masking is elementwise, and the distance the trained unit codebook falls
short of an orthogonal set. Run it against whichever configuration
exp_full.MAIN_D currently names.
"""
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 DEVICE, snr_to_sigma2, rayleigh_gain
from exp_full import main_model, oma_ser_keylen
SNR_DB = 10.0
FRAMES = 400_000
CH = 40_000
def ser(m, snr_db, frames, solo=False):
"""SER of user 0. With solo=True the other users transmit nothing,
while the power normalizer c is left at its four-user value so that
user 0 keeps exactly the energy it has in the real system."""
tot = wrong = 0
with torch.no_grad():
while tot < frames:
n = min(CH, 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)
mk = m.masks()
x = e * mk[None, :, None, :]
if solo:
x = x[:, :1]
y = x.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, :, :] * mk[:1, None, :]
sc = torch.einsum("nupl,uvl->nupv", r, cand)
bad = (sc.argmax(-1)[:, 0] != dig[:, 0]).any(dim=-1)
wrong += int(bad.sum())
tot += n
return wrong / tot
def main():
m = main_model()
with torch.no_grad():
Bn = m.unit_codebook()
G = Bn @ Bn.T
off = G - torch.diag(torch.diag(G))
mk = m.masks()
Gm = mk @ mk.T / m.L
offm = Gm - torch.diag(torch.diag(Gm))
print("main configuration: d=%d P=%d L=%d Vu=%d U=%d"
% (m.d, m.P, m.L, m.vu, m.users))
print("key cross-correlation, max |off-diagonal| : %.2e"
% offm.abs().max())
print("codebook Gram, max |off-diagonal| : %.4f"
% off.abs().max())
print("codebook Gram, rms off-diagonal : %.4f"
% off.pow(2).sum().div(m.vu * (m.vu - 1)).sqrt())
print("(an orthogonal set of %d codewords in %d dims would read 0)"
% (m.vu, m.L))
print()
four = ser(m, SNR_DB, FRAMES, solo=False)
solo = ser(m, SNR_DB, FRAMES, solo=True)
print("user-0 SER, all four users transmitting : %.4f" % four)
print("user-0 SER, other users silent : %.4f" % solo)
print("OMA, resource matched (closed form) : %.4f"
% oma_ser_keylen(m.L, SNR_DB))
if __name__ == "__main__":
main()