95 lines
3.1 KiB
Python
Executable File
95 lines
3.1 KiB
Python
Executable File
"""E4 v3 — Timing offsets with BLOCK-WISE receiver realignment (R1.8).
|
|
|
|
The BS knows the per-user timing estimates (pilot-based) and realigns each
|
|
user's block region individually inside the single received frame:
|
|
y_al[b_w + t] = y[b_w + t + delta_w], t = 0..d/U-1
|
|
Energy that crossed block boundaries is lost or appears as residual
|
|
interference, exactly as in a real system with per-user timing advance error.
|
|
"""
|
|
import numpy as np
|
|
import torch
|
|
import torch.nn.functional as F
|
|
|
|
import lib
|
|
from lib import (SCENARIOS, UWCA, DEVICE, block_masks, channel, gen_embeddings,
|
|
mean_cos, ofdma_decode, save_json, ser, set_seed)
|
|
|
|
rng = set_seed(42)
|
|
d, U, H = 64, 4, 4
|
|
dpu = d // U
|
|
masks = block_masks(U, d)
|
|
scen = SCENARIOS["HIGH"]
|
|
off_rng = np.random.default_rng(5)
|
|
|
|
|
|
def gen(n):
|
|
return gen_embeddings(n, d, U, rng, scen).to(DEVICE)
|
|
|
|
|
|
model = UWCA(d, U, H).to(DEVICE)
|
|
model.load_state_dict(torch.load(lib.DATA / "e1_uwca_HIGH.pt",
|
|
map_location=DEVICE))
|
|
model.eval()
|
|
|
|
|
|
def realign(y, offs_hat):
|
|
"""Block-wise realignment: shift each user's block back by its offset."""
|
|
n, dd = y.shape
|
|
out = torch.zeros_like(y)
|
|
for u in range(U):
|
|
b0 = u * dpu
|
|
for o in offs_hat[:, u].unique():
|
|
o = int(o.item())
|
|
idx = offs_hat[:, u] == o
|
|
src_end = min(b0 + dpu + o, dd)
|
|
ln = src_end - (b0 + o)
|
|
if ln > 0:
|
|
out[idx, b0:b0 + ln] = y[idx, b0 + o:src_end]
|
|
return out
|
|
|
|
|
|
@torch.no_grad()
|
|
def evaluate(mode, dmax, snr, err_p=0.0, n_mc=200):
|
|
s_acc = c_acc = 0.0
|
|
for _ in range(n_mc):
|
|
E = gen(64)
|
|
offs = None
|
|
if dmax > 0:
|
|
offs = torch.from_numpy(
|
|
off_rng.integers(0, dmax + 1, size=(64, U))).to(DEVICE)
|
|
ch = channel(E, snr_db=snr, offsets=offs)
|
|
y = ch["yI"]
|
|
if dmax > 0 and mode.endswith("cor"):
|
|
offs_hat = offs.clone()
|
|
if err_p > 0:
|
|
flip = torch.from_numpy(
|
|
off_rng.random((64, U)) < err_p).to(DEVICE)
|
|
pm = torch.from_numpy(
|
|
off_rng.choice([-1, 1], size=(64, U))).to(DEVICE)
|
|
offs_hat = (offs_hat + flip.long() * pm).clamp(min=0)
|
|
y = realign(y, offs_hat)
|
|
if mode.startswith("uwca"):
|
|
Eh = model(y, ch["yQ"])
|
|
else:
|
|
Eh = ofdma_decode(y, masks)
|
|
s_acc += ser(Eh, E)
|
|
c_acc += mean_cos(Eh, E)
|
|
return s_acc / n_mc, c_acc / n_mc
|
|
|
|
|
|
dgrid = [0, 1, 2, 4, 8]
|
|
out = {"dmax": dgrid, "snr_eval": [10.0, 20.0], "curves": {}}
|
|
for label, mode, ep in [("uwca_uncorrected", "uwca_unc", 0.0),
|
|
("ofdma_uncorrected", "ofdma_unc", 0.0),
|
|
("uwca_corrected", "uwca_cor", 0.0),
|
|
("ofdma_corrected", "ofdma_cor", 0.0),
|
|
("uwca_corrected_err20", "uwca_cor", 0.2)]:
|
|
cur = {}
|
|
for snr in out["snr_eval"]:
|
|
cur[str(snr)] = [evaluate(mode, dm, snr, ep) for dm in dgrid]
|
|
out["curves"][label] = cur
|
|
print(f"[E4v3] {label} 10dB SER: "
|
|
f"{[round(a[0],3) for a in cur['10.0']]}", flush=True)
|
|
|
|
save_json("e4_v3_async.json", out)
|