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.
107 lines
3.5 KiB
Python
107 lines
3.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Does an orthogonal unit codebook recover the shortfall?
|
|
|
|
diag_interference shows the gap to the single-user M-ary bound is not
|
|
multi-user interference but the geometry of the trained unit codebook,
|
|
whose Gram matrix carries a large root-mean-square off-diagonal where an
|
|
orthogonal set would carry zero. Vu <= L admits an exactly orthogonal
|
|
set, so this measures what installing one buys.
|
|
|
|
Two orthogonal sets are tried, because the choice is not free. The
|
|
Walsh-Hadamard set collides with the keys: the rows are closed under the
|
|
elementwise product, so masking a Hadamard codeword by a Hadamard key
|
|
returns another Hadamard codeword and every user ends up with the same
|
|
candidate set. A random orthogonal set carries no such group structure,
|
|
and masking by a unit-modulus key preserves its orthogonality exactly.
|
|
"""
|
|
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, SSE
|
|
from exp_full import hadamard, base_keys, oma_ser_keylen, MAIN_D
|
|
from diag_interference import ser
|
|
|
|
SNR = [0.0, 10.0, 20.0]
|
|
FRAMES = 400_000
|
|
|
|
|
|
def fixed_model(B, P=4, vu=16, d=64, U=4):
|
|
L.set_seed(1)
|
|
m = SSE(P=P, vu=vu, d=d, users=U).to(DEVICE)
|
|
with torch.no_grad():
|
|
m.B.copy_(B.to(DEVICE))
|
|
m.W.copy_(base_keys(U, d // P).to(DEVICE))
|
|
m.calibrate_power()
|
|
return m
|
|
|
|
|
|
def hadamard_book(vu=16, Lp=MAIN_D // 4):
|
|
B = torch.zeros(vu, Lp)
|
|
B[:, :vu] = torch.tensor(hadamard(vu).copy(), dtype=torch.float32)
|
|
return B
|
|
|
|
|
|
def random_ortho_book(vu=16, Lp=MAIN_D // 4, seed=7):
|
|
g = torch.Generator().manual_seed(seed)
|
|
A = torch.randn(Lp, Lp, generator=g)
|
|
Q, _ = torch.linalg.qr(A)
|
|
return Q[:vu].contiguous()
|
|
|
|
|
|
def report(name, m):
|
|
with torch.no_grad():
|
|
Bn = m.unit_codebook()
|
|
G = Bn @ Bn.T
|
|
off = (G - torch.diag(torch.diag(G))).abs().max()
|
|
row = [name, "%.2e" % off]
|
|
for s in SNR:
|
|
row.append("%.4f" % ser(m, s, FRAMES))
|
|
print("%-22s %-10s %-9s %-9s %-9s" % tuple(row))
|
|
|
|
|
|
def main():
|
|
print("%-22s %-10s %-9s %-9s %-9s"
|
|
% ("unit codebook", "max|off|", "0 dB", "10 dB", "20 dB"))
|
|
report("Walsh-Hadamard", fixed_model(hadamard_book()))
|
|
report("random orthogonal", fixed_model(random_ortho_book()))
|
|
from exp_full import main_model
|
|
report("trained", main_model())
|
|
Lp = MAIN_D // 4
|
|
print("%-22s %-10s %-9s %-9s %-9s"
|
|
% ("OMA, resource matched", "-",
|
|
"%.4f" % oma_ser_keylen(Lp, 0.0),
|
|
"%.4f" % oma_ser_keylen(Lp, 10.0),
|
|
"%.4f" % oma_ser_keylen(Lp, 20.0)))
|
|
|
|
|
|
|
|
|
|
|
|
def solo_check():
|
|
"""Splitting each candidate set from the superposition it must live
|
|
in. Orthogonal codewords are ideal for one user alone and are what
|
|
the single-user bound assumes, but the masked sets of different users
|
|
are then far from orthogonal to each other."""
|
|
print()
|
|
print("%-22s %-12s %-12s" % ("unit codebook", "solo 10 dB", "4-user 10 dB"))
|
|
for name, B in (("random orthogonal", random_ortho_book()),
|
|
("trained (retrain)", None)):
|
|
if B is None:
|
|
from exp_full import main_model
|
|
m = main_model()
|
|
else:
|
|
m = fixed_model(B)
|
|
print("%-22s %-12.4f %-12.4f"
|
|
% (name, ser(m, 10.0, FRAMES, solo=True),
|
|
ser(m, 10.0, FRAMES, solo=False)))
|
|
print("(solo isolates the candidate set from the superposition)")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
solo_check()
|