commit b8b853e62ebdf28700c09d7759f560190a68288b Author: KiHoLee Date: Thu Aug 13 21:13:27 2026 +0900 EDMA reproducibility package: simulation code, result data, and manuscript figures diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8ca34b2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +__pycache__/ +*.py[cod] +out/ +.ipynb_checkpoints/ +.DS_Store +Thumbs.db diff --git a/README.md b/README.md new file mode 100644 index 0000000..cd1d1aa --- /dev/null +++ b/README.md @@ -0,0 +1,77 @@ +# EDMA — Affinity-Aware Embedding Division Multiple Access + +Reproducibility package for + +> K.-H. Lee, H.-H. Choi, and J.-R. Lee, "Affinity-Aware Embedding +> Division Multiple Access for Multi-User Semantic Communications," +> submitted to *IEEE Transactions on Vehicular Technology*, 2026. + +This repository contains the simulation code, the raw result data, and +the figure files behind every numerical claim in the paper. It is +private during peer review and will be made public upon publication. + +## Layout + +| Folder | Contents | +|---|---| +| `code/` | Simulation and plotting scripts (Python, CPU only) | +| `data/` | Raw results written by the scripts, one CSV per experiment | +| `fig/` | Figure PDFs included in the manuscript | + +## Requirements + +Python 3.10 or later with `numpy` and `matplotlib`. The Fig. 4 +experiment additionally uses `torch` (CPU build is sufficient). No GPU +is required. Every script fixes the seed 2026 and writes its raw output +to `data/`, so plotting is decoupled from simulation. + +## Reproducing the figures + +Run the scripts from inside `code/`. + +| Figure | Content | Script | Data | +|---|---|---|---| +| Fig. 2 | Per-user MSE and self-interference floor | `revision_sims.py E1` | `floor_validation.csv` | +| Fig. 3 | Effective sum rate at the CLIP affinity | `revision_sims.py E7a` | `rate_corrected.csv` | +| Fig. 4 | Cosine recovery on real BERT+ViT pairs | `fig_real_merged.py`, then `refine_matched.py`; replot with `replot_merged.py` | `bertvit_merged.csv` | +| Fig. 5 | Realizable versus genie-aided SIC | `revision_sims.py E2`; replot with `replot_sic.py` | `sic_comparison.csv` | +| Fig. 6 | Affinity sweep and crossover | `revision_sims.py E7a` | `beta_sweep_corrected.csv` | +| Fig. 7 | Multi-user scaling | `revision_sims.py E7c` | `multiuser_corrected.csv` | + +Fig. 1 is a system diagram and has no simulation behind it. + +Quantities quoted in the text but not plotted come from the same +driver: `revision_sims.py E0` writes `theorem_check.csv` (Theorem 1 +constants), `E4` writes `csi_error.csv` (imperfect-CSI robustness), and +`E5` writes `mask_family_rev.csv` (Walsh–Hadamard versus Haar masks). +`revision_sims.py` with no argument runs every experiment. + +## Verifying the analysis + +`verify_math.py` re-derives every closed-form expression in the paper +numerically and prints one PASS/FAIL line per item, covering the +per-realization Gram identity, Theorem 1 and its self-interference +constants, the effective-SINR corollary, the MAC-consistency +proposition, the wideband limit, both crossover conditions, the +affinity-mismatch bound, the CSI-invariance identity, the multi-user +inverse formula, and the Walsh–Hadamard construction. It depends only +on `numpy`. + +## Conventions + +The scripts follow the manuscript exactly: unit per-block transmit +energy `E_b = 1` per user, `rho = E_b / sigma_n^2` as the per-block +SNR with per-symbol SNR `rho/d`, complex block-Rayleigh gains unless +the evaluation point `h_u = 1` is stated, real unit-norm embeddings, +and masks drawn fresh from the Haar mixture on every realization. + +`fig_real_merged.py` also produces columns for a retrained +attention-based receiver. Those columns are kept in `bertvit_merged.csv` +for completeness but are not used by any figure in the paper. +`revision_sims.py E6` covers a high-affinity combining mode that is +outside the scope of this paper. + +## Citation and license + +Citation details and a license will be added when the paper is +published. diff --git a/code/fig_real_merged.py b/code/fig_real_merged.py new file mode 100644 index 0000000..17fdf5e --- /dev/null +++ b/code/fig_real_merged.py @@ -0,0 +1,333 @@ +""" +Merged real-data comparison figure (replaces separate Figs 4 and 5). +=================================================================== +Evaluates ALL schemes on the cached real BERT (text) + ViT (image) +embedding pairs (16 pairs, d = 768, measured mean affinity ~0.028) +under the manuscript's complex block-Rayleigh channel: + + r = h1 M1 e1 + h2 M2 e2 + n, n ~ CN(0, sigma^2 I), h_u ~ CN(0,1), + per-block energy E_b = 1, rho = 1/sigma^2 (per-block SNR). + +Schemes: + 1. EDMA : per-realisation Haar-mixture masks with the per-pair + measured beta_i, closed-form demux (13). + 2. OMA : equivalent-bandwidth model, noise std x sqrt(2). + 3. Genie SIC : perfect removal of the other user's waveform. + 4. Attention : retrained reproduction of the learned predecessor, + d = 768, trained on parametric pairs at the measured + mean affinity with Rayleigh channels and + channel-equalised matched-filter inputs + x_u = Re(M_u^T r / h_u); evaluated on the REAL pairs. + 5. ToDMA-adapted: OMP sparse coding of the real embedding (T = 16 + atoms, V = 1024), T slots x L = 48 signatures, + per-slot OMP detection on the complex observation, + genie association, true coefficients granted. + +Outputs: fig/fig_bertvit_merged.pdf, data/bertvit_merged.csv. +200 fading realisations per pair -> 3,200 Monte-Carlo samples per SNR. +Seed fixed. +""" +from __future__ import annotations +import csv +import math +import pickle +import time +from pathlib import Path +import numpy as np +import torch +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt + +ROOT = Path(__file__).resolve().parents[1] +DATA = ROOT / "data" +FIG = ROOT / "fig" + +plt.rcParams.update({ + "font.family": "serif", + "font.serif": ["DejaVu Serif", "Times New Roman"], + "font.size": 9, "axes.labelsize": 9, "legend.fontsize": 6.6, + "xtick.labelsize": 8, "ytick.labelsize": 8, + "axes.grid": True, "grid.linestyle": "--", "grid.linewidth": 0.4, + "grid.alpha": 0.6, "lines.linewidth": 1.4, "lines.markersize": 4.0, + "figure.figsize": (3.15, 2.36), "pdf.fonttype": 42, +}) +AXES_RECT = dict(left=0.205, right=0.965, top=0.955, bottom=0.185) + +SEED = 2026 +rng = np.random.default_rng(SEED) +torch.manual_seed(SEED) + +D = 768 +SNRS = np.arange(0.0, 31.0, 5.0) +NFADE = 200 # fading realisations per pair + + +def haar(d): + G = rng.standard_normal((d, d)) + Q, R = np.linalg.qr(G) + return Q * np.sign(np.diag(R)) + + +def unit(v): + return v / np.linalg.norm(v) + + +def cosine(a, b): + return float(abs(np.vdot(a, b)) / (np.linalg.norm(a) * np.linalg.norm(b))) + + +def load_pairs(): + a, b = pickle.load(open(DATA / "bert_vit_cached.pkl", "rb")) + a = np.stack([unit(x - x.mean()) for x in a]) + b = np.stack([unit(x - x.mean()) for x in b]) + betas = np.abs((a * b).sum(1)) + print(f"[pairs] {len(a)} cached BERT/ViT pairs, d={a.shape[1]}, " + f"beta mean {betas.mean():.4f} std {betas.std():.4f}") + return a, b, betas + + +# ------------------------------------------------------------------ +# attention model: trained at the measured mean affinity, d=768, +# Rayleigh channels, channel-equalised MF inputs +# ------------------------------------------------------------------ +EPS_EQ = 0.1 # regularised equalisation h*/(|h|^2+EPS_EQ): + # caps deep-fade amplification for the learned readout + + +def train_attention(beta0, epochs=150, steps=20, batch=48, lr=5e-4, + l1=1.0, l2=0.5, l3=0.5): + print(f"=== training attention reproduction (d={D}, beta={beta0:.3f}, " + f"{epochs} epochs, Rayleigh) ===", flush=True) + gen = torch.Generator().manual_seed(SEED) + g0 = math.sqrt(1.0 - beta0**2) + + def torch_pairs(n): + e1 = torch.nn.functional.normalize( + torch.randn(n, D, generator=gen), dim=1) + w = torch.randn(n, D, generator=gen) + w = w - (w * e1).sum(1, keepdim=True) * e1 + w = torch.nn.functional.normalize(w, dim=1) + return e1, beta0 * e1 + g0 * w + + M1 = torch.nn.Parameter(torch.linalg.qr( + torch.randn(D, D, generator=gen))[0]) + M2 = torch.nn.Parameter(beta0 * M1.detach() + + g0 * torch.linalg.qr( + torch.randn(D, D, generator=gen))[0]) + Q1 = torch.nn.Parameter(torch.randn(D, D, generator=gen) / math.sqrt(D)) + Q2 = torch.nn.Parameter(torch.randn(D, D, generator=gen) / math.sqrt(D)) + opt = torch.optim.Adam([M1, M2, Q1, Q2], lr=lr) + eye = torch.eye(D) + + t0 = time.time() + for ep in range(epochs): + for _ in range(steps): + e1, e2 = torch_pairs(batch) + snr_db = 5.0 + 20.0 * torch.rand(batch, 1, generator=gen) + sig = 10 ** (-snr_db / 20.0) + hr = torch.randn(batch, 2, generator=gen) + hi = torch.randn(batch, 2, generator=gen) + # complex channel on real signals; equalised MF real part: + # x_u = Re(M_u^T r / h_u); build via real/imag components + s1 = e1 @ M1.T + s2 = e2 @ M2.T + nr = sig * torch.randn(batch, D, generator=gen) / math.sqrt(2) + ni = sig * torch.randn(batch, D, generator=gen) / math.sqrt(2) + rr = (hr[:, :1] * s1 + hr[:, 1:2] * s2) / math.sqrt(2) + nr + ri = (hi[:, :1] * s1 + hi[:, 1:2] * s2) / math.sqrt(2) + ni + outs = [] + for u, (Mu, Qu) in enumerate(((M1, Q1), (M2, Q2))): + hu_r = hr[:, u:u+1] / math.sqrt(2) + hu_i = hi[:, u:u+1] / math.sqrt(2) + mag = hu_r**2 + hu_i**2 + EPS_EQ + xr = (rr @ Mu) + xi = (ri @ Mu) + xu = (xr * hu_r + xi * hu_i) / mag # Re(h* r'/(|h|^2+eps)) + sc = (xu @ Qu.T) / math.sqrt(D) + outs.append(D * torch.softmax(sc, dim=1) * xu) + gram = ((M1.T @ M1 - eye)**2).mean() \ + + ((M2.T @ M2 - eye)**2).mean() \ + + ((M1.T @ M2 - beta0 * eye)**2).mean() + mse = ((outs[0] - e1)**2).mean() + ((outs[1] - e2)**2).mean() + cs = torch.nn.functional.cosine_similarity( + outs[0], e1, dim=1).mean() \ + + torch.nn.functional.cosine_similarity( + outs[1], e2, dim=1).mean() + loss = l1 * gram + l2 * mse + l3 * (2.0 - cs) + opt.zero_grad(); loss.backward() + torch.nn.utils.clip_grad_norm_([M1, M2, Q1, Q2], 1.0) + opt.step() + if (ep + 1) % 50 == 0: + print(f" epoch {ep+1}: loss {float(loss.detach()):.4f}", + flush=True) + print(f" trained in {time.time()-t0:.0f}s, " + f"{4*D*D/1e6:.2f}M parameters") + return (M1.detach().numpy(), M2.detach().numpy(), + Q1.detach().numpy(), Q2.detach().numpy()) + + +def att_apply(model, r, h1, h2): + M1, M2, Q1, Q2 = model + outs = [] + for u, (Mu, Qu, hu) in enumerate(((M1, Q1, h1), (M2, Q2, h2))): + xu = np.real(np.conj(hu) * (Mu.T @ r)) / (abs(hu)**2 + EPS_EQ) + sc = (Qu @ xu) / math.sqrt(D) + sc = sc - sc.max() + w = np.exp(sc); w /= w.sum() + outs.append(D * w * xu) + return outs + + +# ------------------------------------------------------------------ +# ToDMA-adapted on real embeddings (complex channel) +# ------------------------------------------------------------------ +def todma_prepare(V=1024, T=16): + L = D // T + Dict = rng.standard_normal((V, D)) + Dict /= np.linalg.norm(Dict, axis=1, keepdims=True) + Sig = rng.standard_normal((V, L)) + Sig /= np.linalg.norm(Sig, axis=1, keepdims=True) + amp = math.sqrt(1.0 / T) # E_b = 1 per user per block + return Dict, Sig, amp, T, L + + +def omp_code(Dict, e, T): + resid = e.copy(); idx = [] + for _ in range(T): + corr = np.abs(Dict @ resid) + if idx: + corr[idx] = -1 + k = int(corr.argmax()); idx.append(k) + A = Dict[idx].T + coef, *_ = np.linalg.lstsq(A, e, rcond=None) + resid = e - A @ coef + return idx, coef + + +def todma_run(tod, codes, h, sig, noise_slots): + Dict, Sig, amp, T, L = tod + U = len(codes) + det = [set() for _ in range(U)] + for t in range(T): + y = sum(h[u] * amp * Sig[codes[u][0][t]] for u in range(U)) \ + + sig * noise_slots[t] + resid = y.copy(); support = [] + for _ in range(U): + corr = np.abs(Sig @ resid.conj()) + if support: + corr[support] = -1 + kk = int(corr.argmax()); support.append(kk) + Ah = (Sig[support].T * amp).astype(complex) + coef, *_ = np.linalg.lstsq(Ah, y, rcond=None) + resid = y - Ah @ coef + sset = set(support) + for u in range(U): + if codes[u][0][t] in sset: + det[u].add(codes[u][0][t]) + recs = [] + for u in range(U): + idx, coef = codes[u] + keep = [i for i, tid in enumerate(idx) if tid in det[u]] + recs.append(sum(coef[i] * Dict[idx[i]] for i in keep) + if keep else None) + return recs + + +# ------------------------------------------------------------------ +def main(): + A, B, betas = load_pairs() + npairs = len(A) + model = train_attention(float(betas.mean())) + tod = todma_prepare() + codes = [(omp_code(tod[0], A[i], tod[3]), + omp_code(tod[0], B[i], tod[3])) for i in range(npairs)] + print("[todma] sparse codes prepared") + + keys = ("edma", "oma", "genie", "att", "att_x", "todma") + res = {k: np.zeros(len(SNRS)) for k in keys} + cnt = {k: np.zeros(len(SNRS)) for k in keys} + t0 = time.time() + for i in range(npairs): + e1, e2, bi = A[i], B[i], float(betas[i]) + gi = 1.0 - bi**2 + c1, c2 = codes[i] + for f in range(NFADE): + U1, U2 = haar(D), haar(D) + M1 = U1 + M2 = bi * U1 + math.sqrt(gi) * U2 + h = (rng.standard_normal(2) + 1j * rng.standard_normal(2)) \ + / math.sqrt(2) + h1, h2 = h + r0 = h1 * (M1 @ e1) + h2 * (M2 @ e2) + n = (rng.standard_normal(D) + 1j * rng.standard_normal(D)) \ + / math.sqrt(2) + n2 = (rng.standard_normal(D) + 1j * rng.standard_normal(D)) \ + / math.sqrt(2) + nslots = [(rng.standard_normal(tod[4]) + + 1j * rng.standard_normal(tod[4])) / math.sqrt(2) + for _ in range(tod[3])] + # attention scheme transmits with ITS OWN trained masks + r0a = h1 * (model[0] @ e1) + h2 * (model[1] @ e2) + for k, s in enumerate(SNRS): + sig = 10 ** (-s / 20.0) + r = r0 + sig * n + t1 = M1.T @ r / h1; t2 = M2.T @ r / h2 + g1 = (t1 - bi * (h2 / h1) * t2) / gi + g2 = (t2 - bi * (h1 / h2) * t1) / gi + res["edma"][k] += 0.5 * (cosine(g1, e1) + cosine(g2, e2)) + o1 = e1 + math.sqrt(2) * sig * n / h1 + o2 = e2 + math.sqrt(2) * sig * n2 / h2 + res["oma"][k] += 0.5 * (cosine(o1, e1) + cosine(o2, e2)) + ge1 = M1.T @ (r - h2 * (M2 @ e2)) / h1 + ge2 = M2.T @ (r - h1 * (M1 @ e1)) / h2 + res["genie"][k] += 0.5 * (cosine(ge1, e1) + cosine(ge2, e2)) + a1, a2 = att_apply(model, r0a + sig * n, h1, h2) + res["att"][k] += 0.5 * (cosine(a1, e1) + cosine(a2, e2)) + res["att_x"][k] += 0.5 * (cosine(a1, e2) + cosine(a2, e1)) + for kk in ("edma", "oma", "genie", "att", "att_x"): + cnt[kk][k] += 1 + if f < 40: # ToDMA heavier: 40 fading draws + recs = todma_run(tod, (c1, c2), (h1, h2), sig, nslots) + got = [cosine(recs[j], (e1, e2)[j]) + for j in range(2) if recs[j] is not None] + if got: + res["todma"][k] += float(np.mean(got)) + cnt["todma"][k] += 1 + print(f" pair {i+1}/{npairs} done ({time.time()-t0:.0f}s)", + flush=True) + for k in keys: + res[k] /= np.maximum(cnt[k], 1) + + fig, ax = plt.subplots() + ax.plot(SNRS, res["edma"], "o-", color="C3", label="EDMA (closed form)") + ax.plot(SNRS, res["att"], "s--", color="C0", + label="Attention-based (retrained)") + ax.plot(SNRS, res["todma"], "d-.", color="C4", label="ToDMA-adapted") + ax.plot(SNRS, res["oma"], "v:", color="C1", label="OMA") + ax.plot(SNRS, res["genie"], "-", color="gray", lw=1.0, + label="Genie-aided SIC bound") + ax.set_xlabel("Per-block SNR $\\rho$ [dB]") + ax.set_ylabel("Mean cosine similarity") + ax.set_xlim(SNRS[0], SNRS[-1]); ax.set_ylim(0, 0.85) + ax.legend(loc="upper left") + fig.subplots_adjust(**AXES_RECT) + fig.savefig(FIG / "fig_bertvit_merged.pdf") + plt.close(fig) + print(f"[OK] wrote {FIG/'fig_bertvit_merged.pdf'}") + + with open(DATA / "bertvit_merged.csv", "w", newline="") as fcsv: + w = csv.writer(fcsv) + w.writerow(["snr_db"] + list(keys)) + for k, s in enumerate(SNRS): + w.writerow([s] + [res[key][k] for key in keys]) + print(f"[OK] wrote {DATA/'bertvit_merged.csv'}") + for k, s in enumerate(SNRS): + print(f" {s:4.0f} dB EDMA {res['edma'][k]:.3f} " + f"ATT {res['att'][k]:.3f} (x {res['att_x'][k]:.3f}) " + f"ToDMA {res['todma'][k]:.3f} OMA {res['oma'][k]:.3f} " + f"genie {res['genie'][k]:.3f}") + + +if __name__ == "__main__": + main() diff --git a/code/refine_matched.py b/code/refine_matched.py new file mode 100644 index 0000000..0c0a852 --- /dev/null +++ b/code/refine_matched.py @@ -0,0 +1,187 @@ +""" +Capacity-matched EDMA refinement (parameter budget equal to the +attention scheme: 4 d^2 = 2.36M at d = 768). +================================================================ +Four-head averaged gated refinement applied to the closed-form +demultiplexer output: + + out = (1/4) sum_k D softmax(Q_k x / sqrt(D)) .* x, + +with Q_1..Q_4 in R^{D x D} (4 d^2 parameters, exactly the +attention scheme's budget). The single-gate 0.59M refiner is the +special case of four identical heads, so the family contains it +by construction. Same training recipe: demux outputs from +parametric pairs at beta = 0.028, Haar pool 32, Rayleigh +channels, complex noise, training SNR uniform in [5, 25] dB, +Adam 5e-4 with gradient clipping, batch 48, 200 epochs. + +Evaluation on the real BERT/ViT pairs with fresh Haar masks and +200 fading draws per pair. Appends column `edma_ref2` to +data/bertvit_merged.csv and prints all-curve numbers. +""" +from __future__ import annotations +import csv +import math +import time +import numpy as np +import torch + +from fig_real_merged import load_pairs, cosine, SNRS, NFADE, D, DATA + +SEED = 2026 +rng = np.random.default_rng(SEED + 31) +torch.manual_seed(SEED + 31) +BETA0 = 0.028 +G0 = 1.0 - BETA0**2 + + +def haar_t(gen): + Q, R = torch.linalg.qr(torch.randn(D, D, generator=gen)) + return Q * torch.sign(torch.diagonal(R)) + + +def train_refiner2(epochs=220, steps=20, batch=48, lr=5e-4, + l2=0.5, l3=0.5, pool=32): + """Stage 1 trains a single gate (the proven 0.59M recipe); stage 2 + warm-starts four heads from it plus small perturbations and + fine-tunes at a reduced learning rate, so the capacity-matched + family starts at the single-gate solution it contains.""" + print(f"=== training capacity-matched refinement (4-head gate, " + f"4d^2 = {4*D*D/1e6:.2f}M params, warm-started) ===", + flush=True) + gen = torch.Generator().manual_seed(SEED + 31) + masks = [] + for _ in range(pool): + U1, U2 = haar_t(gen), haar_t(gen) + masks.append((U1.numpy(), (BETA0 * U1 + + math.sqrt(G0) * U2).numpy())) + Q0 = torch.nn.Parameter(torch.randn(D, D, generator=gen) + / math.sqrt(D)) + params = [Q0] + opt = torch.optim.Adam(params, lr=lr) + stage2_at = 120 # epochs of single-gate pre-training + + def forward(x): + outs = [D * torch.softmax((x @ Qk.T) / math.sqrt(D), dim=1) * x + for Qk in params] + return sum(outs) / len(params) + + t0 = time.time() + for ep in range(epochs): + if ep == stage2_at: + base = params[0].detach() + params = [torch.nn.Parameter( + base.clone() + 0.02 * torch.randn(D, D, generator=gen) + / math.sqrt(D)) for _ in range(4)] + opt = torch.optim.Adam(params, lr=2e-4) + print(f" [warm start] 4 heads initialised from the trained " + f"gate at epoch {ep}", flush=True) + for _ in range(steps): + xs, ts = [], [] + for _ in range(batch): + e1 = torch.nn.functional.normalize( + torch.randn(D, generator=gen), dim=0).numpy() + w = torch.randn(D, generator=gen).numpy() + w = w - (w @ e1) * e1 + w = w / np.linalg.norm(w) + e2 = BETA0 * e1 + math.sqrt(G0) * w + M1, M2 = masks[int(torch.randint(pool, (1,), + generator=gen))] + snr = float(5.0 + 20.0 * torch.rand(1, generator=gen)) + sig = 10 ** (-snr / 20.0) + h = (torch.randn(2, generator=gen).numpy() + + 1j * torch.randn(2, generator=gen).numpy()) \ + / math.sqrt(2) + nc = (torch.randn(D, generator=gen).numpy() + + 1j * torch.randn(D, generator=gen).numpy()) \ + / math.sqrt(2) + rc = h[0] * (M1 @ e1) + h[1] * (M2 @ e2) + sig * nc + t1 = M1.T @ rc / h[0] + t2 = M2.T @ rc / h[1] + g1 = (t1 - BETA0 * (h[1] / h[0]) * t2) / G0 + xs.append(torch.tensor(np.real(g1), dtype=torch.float32)) + ts.append(torch.tensor(e1, dtype=torch.float32)) + x = torch.stack(xs); t = torch.stack(ts) + out = forward(x) + mse = ((out - t)**2).mean() + cs = torch.nn.functional.cosine_similarity(out, t, dim=1).mean() + loss = l2 * mse + l3 * (1.0 - cs) + opt.zero_grad(); loss.backward() + torch.nn.utils.clip_grad_norm_(params, 1.0) + opt.step() + if (ep + 1) % 50 == 0: + print(f" epoch {ep+1}: loss {float(loss.detach()):.4f} " + f"(cos {float(cs.detach()):.3f})", flush=True) + print(f" trained in {time.time()-t0:.0f}s") + return [p.detach().numpy() for p in params] + + +def refine2(P, g): + x = np.real(g) + + def gate(Q, v): + sc = (Q @ v) / math.sqrt(D) + sc = sc - sc.max() + w = np.exp(sc); w /= w.sum() + return D * w * v + + return sum(gate(Qk, x) for Qk in P) / 4.0 + + +def main(): + A, B, betas = load_pairs() + P = train_refiner2() + ref = np.zeros(len(SNRS)); cnt = 0 + t0 = time.time() + for i in range(len(A)): + e1, e2, bi = A[i], B[i], float(betas[i]) + gi = 1.0 - bi**2 + for f in range(NFADE): + G1 = rng.standard_normal((D, D)) + Qh, Rh = np.linalg.qr(G1) + U1 = Qh * np.sign(np.diag(Rh)) + G2 = rng.standard_normal((D, D)) + Qh, Rh = np.linalg.qr(G2) + U2 = Qh * np.sign(np.diag(Rh)) + M1 = U1 + M2 = bi * U1 + math.sqrt(gi) * U2 + h = (rng.standard_normal(2) + 1j * rng.standard_normal(2)) \ + / math.sqrt(2) + h1, h2 = h + r0 = h1 * (M1 @ e1) + h2 * (M2 @ e2) + n = (rng.standard_normal(D) + 1j * rng.standard_normal(D)) \ + / math.sqrt(2) + for k, s in enumerate(SNRS): + sig = 10 ** (-s / 20.0) + r = r0 + sig * n + t1 = M1.T @ r / h1; t2 = M2.T @ r / h2 + g1 = (t1 - bi * (h2 / h1) * t2) / gi + g2 = (t2 - bi * (h1 / h2) * t1) / gi + ref[k] += 0.5 * (cosine(refine2(P, g1), e1) + + cosine(refine2(P, g2), e2)) + cnt += 1 + print(f" pair {i+1}/{len(A)} done ({time.time()-t0:.0f}s)", + flush=True) + ref /= cnt + + rows = list(csv.DictReader(open(DATA / "bertvit_merged.csv"))) + names = list(rows[0].keys()) + if "edma_ref2" not in names: + names.append("edma_ref2") + for k, r in enumerate(rows): + r["edma_ref2"] = f"{ref[k]}" + with open(DATA / "bertvit_merged.csv", "w", newline="") as f: + w = csv.DictWriter(f, fieldnames=names) + w.writeheader(); w.writerows(rows) + print("[OK] appended edma_ref2 to bertvit_merged.csv") + for k, r in enumerate(rows): + print(f" {float(r['snr_db']):4.0f} dB " + f"EDMA {float(r['edma']):.3f} " + f"ref(0.59M) {float(r['edma_ref']):.3f} " + f"ref2(2.36M) {ref[k]:.3f} " + f"ATT(2.36M) {float(r['att']):.3f} " + f"genie {float(r['genie']):.3f}") + + +if __name__ == "__main__": + main() diff --git a/code/replot_merged.py b/code/replot_merged.py new file mode 100644 index 0000000..d3d200b --- /dev/null +++ b/code/replot_merged.py @@ -0,0 +1,41 @@ +"""Canonical replot of fig_bertvit_merged.pdf from data/bertvit_merged.csv. +Curves: EDMA, EDMA + refinement (hybrid), ToDMA-adapted, OMA, genie bound. +The attention columns remain in the CSV but are not plotted.""" +import csv +from pathlib import Path +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt + +ROOT = Path(__file__).resolve().parents[1] +plt.rcParams.update({ + "font.family": "serif", + "font.serif": ["DejaVu Serif", "Times New Roman"], + "font.size": 9, "axes.labelsize": 9, "legend.fontsize": 6.6, + "xtick.labelsize": 8, "ytick.labelsize": 8, + "axes.grid": True, "grid.linestyle": "--", "grid.linewidth": 0.4, + "grid.alpha": 0.6, "lines.linewidth": 1.4, "lines.markersize": 4.0, + "figure.figsize": (3.15, 2.36), "pdf.fonttype": 42, +}) +AXES_RECT = dict(left=0.205, right=0.965, top=0.955, bottom=0.185) + +rows = list(csv.DictReader(open(ROOT / "data" / "bertvit_merged.csv"))) +snr = [float(r["snr_db"]) for r in rows] +col = lambda k: [float(r[k]) for r in rows] + +fig, ax = plt.subplots() +ax.plot(snr, col("edma"), "o-", color="C3", label="EDMA (closed form)") +ax.plot(snr, col("edma_ref"), "^-", color="C2", + label="EDMA + refinement stage") +ax.plot(snr, col("todma"), "d-.", color="C4", label="ToDMA-adapted") +ax.plot(snr, col("oma"), "v:", color="C1", label="OMA") +ax.plot(snr, col("genie"), "-", color="gray", lw=1.0, + label="Genie-aided SIC bound") +ax.set_xlabel("Per-block SNR $\\rho$ [dB]") +ax.set_ylabel("Mean cosine similarity") +ax.set_xlim(snr[0], snr[-1]) +ax.set_ylim(0, 0.85) +ax.legend(loc="upper left") +fig.subplots_adjust(**AXES_RECT) +fig.savefig(ROOT / "fig" / "fig_bertvit_merged.pdf") +print("[OK] wrote fig_bertvit_merged.pdf (no attention curve)") diff --git a/code/replot_sic.py b/code/replot_sic.py new file mode 100644 index 0000000..a7d633f --- /dev/null +++ b/code/replot_sic.py @@ -0,0 +1,43 @@ +"""Canonical replot of fig_sic.pdf from data/sic_comparison.csv +(realizable analog SIC vs genie SIC vs EDMA vs OMA, beta = 0.311, +d = 512, block-Rayleigh). US-spelling labels, uniform geometry.""" +import csv +from pathlib import Path +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt + +ROOT = Path(__file__).resolve().parents[1] +plt.rcParams.update({ + "font.family": "serif", + "font.serif": ["DejaVu Serif", "Times New Roman"], + "font.size": 9, "axes.labelsize": 9, "legend.fontsize": 6.6, + "xtick.labelsize": 8, "ytick.labelsize": 8, + "axes.grid": True, "grid.linestyle": "--", "grid.linewidth": 0.4, + "grid.alpha": 0.6, "lines.linewidth": 1.4, "lines.markersize": 4.0, + "figure.figsize": (3.15, 2.36), "pdf.fonttype": 42, +}) +AXES_RECT = dict(left=0.205, right=0.965, top=0.955, bottom=0.185) + +rows = list(csv.DictReader(open(ROOT / "data" / "sic_comparison.csv"))) +snr = [float(r["snr_db"]) for r in rows] +col = lambda k: [float(r[k]) for r in rows] + +fig, ax = plt.subplots() +ax.plot(snr, col("edma"), "o-", color="C3", label="EDMA (closed form)") +ax.plot(snr, col("sic"), "^-.", color="C2", label="Realizable analog SIC") +ax.plot(snr, col("oma"), "v:", color="C1", label="OMA") +ax.plot(snr, col("genie"), "-", color="gray", lw=1.0, + label="Genie-aided SIC bound") +ax.set_xlabel("Per-block SNR $\\rho$ [dB]") +ax.set_ylabel("Mean cosine similarity") +ax.set_xlim(snr[0], snr[-1]) +ax.set_ylim(0, 0.7) +ax.legend(loc="upper left") +fig.subplots_adjust(**AXES_RECT) +fig.savefig(ROOT / "fig" / "fig_sic.pdf") +print("[OK] wrote fig_sic.pdf") +for r in rows: + print(f" {float(r['snr_db']):4.0f} dB EDMA {float(r['edma']):.3f} " + f"SIC {float(r['sic']):.3f} genie {float(r['genie']):.3f} " + f"OMA {float(r['oma']):.3f}") diff --git a/code/revision_sims.py b/code/revision_sims.py new file mode 100644 index 0000000..c0ab0e6 --- /dev/null +++ b/code/revision_sims.py @@ -0,0 +1,617 @@ +""" +Revision simulations for the EDMA TCOM resubmission. +======================================================= +Implements the per-realisation (finite-d) analysis and the corrected +energy-normalised rate accounting, plus the reviewer-requested +experiments: + + E0 Theorem-1 verification: exact self-interference constant C_SI + E1 fig_floor : per-user MSE vs block SNR, interference floor + E2 fig_sic : realisable SIC vs genie SIC vs EDMA vs OMA + E3 (text numbers) : Rayleigh unconditional MSE, ZF vs regularised + E4 fig_csi : imperfect-CSI robustness + E5 fig_maskfam : Walsh-Hadamard structured masks vs Haar + E6 fig_coop : high-affinity combining-mode crossover + E7 fig_rate_corrected, fig_beta_sweep_corrected, fig_multiuser_corrected + +Conventions (identical to the revised manuscript): + * unit per-block transmit energy E_b = 1 per user + * rho = E_b / sigma_n^2 (per-block received SNR; per-symbol SNR rho/d) + * block-Rayleigh h ~ CN(0,1) unless the AWGN point |h|=1 is stated + * complex AWGN CN(0, sigma^2 I_d); embeddings real, unit norm + * orientation convention = +beta +Fixed seed. CSVs -> ../fig, PDFs -> ../fig_toc. +""" +from __future__ import annotations +import csv +import math +from pathlib import Path +import numpy as np +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt + +ROOT = Path(__file__).resolve().parents[1] +CSV_DIR = ROOT / "data"; CSV_DIR.mkdir(exist_ok=True) +FIG_DIR = ROOT / "fig"; FIG_DIR.mkdir(exist_ok=True) + +plt.rcParams.update({ + "font.family": "serif", + "font.serif": ["DejaVu Serif", "Times New Roman"], + "font.size": 9, "axes.labelsize": 9, "axes.titlesize": 9, + "legend.fontsize": 7.0, "xtick.labelsize": 8, "ytick.labelsize": 8, + "axes.grid": True, "grid.linestyle": "--", "grid.linewidth": 0.4, + "grid.alpha": 0.6, "lines.linewidth": 1.4, "lines.markersize": 4.0, + "figure.figsize": (3.15, 2.36), "pdf.fonttype": 42, +}) +AXES_RECT = dict(left=0.205, right=0.965, top=0.955, bottom=0.185) + +rng = np.random.default_rng(2026) + + +def save_fig(fig, name): + p = FIG_DIR / f"{name}.pdf" + fig.subplots_adjust(**AXES_RECT) + fig.savefig(p) + plt.close(fig) + print(f"[OK] wrote {p}") + + +def write_csv(name, header, rows): + p = CSV_DIR / f"{name}.csv" + with open(p, "w", newline="") as f: + w = csv.writer(f); w.writerow(header); w.writerows(rows) + print(f"[OK] wrote {p}") + + +# ------------------------------------------------------------------ +# Core constructions +# ------------------------------------------------------------------ +def haar(d): + G = rng.standard_normal((d, d)) + Q, R = np.linalg.qr(G) + return Q * np.sign(np.diag(R)) + + +def unit(v): + return v / np.linalg.norm(v) + + +def embed_pair(d, beta): + """e1, e2 real unit vectors with = +beta.""" + e1 = unit(rng.standard_normal(d)) + w = rng.standard_normal(d) + w = unit(w - (w @ e1) * e1) + e2 = beta * e1 + math.sqrt(1.0 - beta**2) * w + return e1, e2 + + +def two_user_masks(d, beta, U1=None, U2=None): + if U1 is None: U1 = haar(d) + if U2 is None: U2 = haar(d) + g = math.sqrt(1.0 - beta**2) + return U1, beta * U1 + g * U2 + + +def rayleigh(n=1): + return (rng.standard_normal(n) + 1j * rng.standard_normal(n)) / math.sqrt(2) + + +def C_SI(beta, c): + """User-1 self-interference constant (exact to O(1/d)), =+beta.""" + g = 1.0 - beta**2 + return (g**2 * abs(c)**2 + beta**2 + beta**4 * abs(c)**2 + + 2.0 * beta**4 * np.real(c)) / g + + +def C_SI2(beta, c2): + """User-2 self-interference constant (deterministic), c2 = h1/h2.""" + g = 1.0 - beta**2 + return (abs(c2)**2 + beta**2 + 2.0 * beta**2 * np.real(c2)) / g + + +def C_bar(beta): + """Symmetrised constant at |h|=1 (block-alternating mask roles).""" + return 0.5 * (C_SI(beta, 1.0 + 0j) + C_SI2(beta, 1.0 + 0j)) + + +def demux(r, M1, M2, h1, h2, beta): + """beta-aware demultiplexer (13); returns (e1_hat, e2_hat).""" + g = 1.0 - beta**2 + t1 = (M1.T @ r) / h1 + t2 = (M2.T @ r) / h2 + e1 = (t1 - beta * (h2 / h1) * t2) / g + e2 = (t2 - beta * (h1 / h2) * t1) / g + return e1, e2 + + +def cosine(a, b): + return abs(np.vdot(a, b)) / (np.linalg.norm(a) * np.linalg.norm(b)) + + +# ------------------------------------------------------------------ +# E0 : Theorem-1 verification +# ------------------------------------------------------------------ +def E0_theorem_check(d=512, betas=(0.0, 0.311, 0.5, 0.7), ntr=300): + print("\n=== E0: Theorem 1 (self-interference constant) verification ===") + rows = [] + worst = 0.0 + for beta in betas: + # random unit-modulus channels (AWGN-type magnitude, random phase) + errs1, errs2 = [], [] + for _ in range(ntr): + h1 = np.exp(1j * rng.uniform(0, 2 * np.pi)) + h2 = np.exp(1j * rng.uniform(0, 2 * np.pi)) + e1, e2 = embed_pair(d, beta) + M1, M2 = two_user_masks(d, beta) + r = h1 * (M1 @ e1) + h2 * (M2 @ e2) # noise-free + g1, g2 = demux(r, M1, M2, h1, h2, beta) + errs1.append(np.linalg.norm(g1 - e1)**2 / C_SI(beta, h2 / h1)) + errs2.append(np.linalg.norm(g2 - e2)**2 / C_SI2(beta, h1 / h2)) + r1, r2 = float(np.mean(errs1)), float(np.mean(errs2)) + dev = max(abs(r1 - 1.0), abs(r2 - 1.0)) * 100 + worst = max(worst, dev) + print(f" beta={beta:.3f} MC/theory user1 = {r1:.4f}, user2 = {r2:.4f}" + f" (max dev {dev:.2f}%)") + rows.append([beta, r1, r2, dev]) + write_csv("theorem_check", ["beta", "user1_mc_over_theory", + "user2_mc_over_theory", "max_dev_pct"], rows) + print(f" worst-case deviation {worst:.2f}%") + return worst + + +# ------------------------------------------------------------------ +# E1 : interference floor (MSE vs block SNR), AWGN point |h|=1 +# ------------------------------------------------------------------ +def E1_floor(beta=0.311, dims=(256, 768), snr_db=np.arange(0, 41, 2.5), ntr=150): + print("\n=== E1: finite-d interference floor ===") + g = 1.0 - beta**2 + csi = C_SI(beta, 1.0 + 0j) + fig, ax = plt.subplots() + colors = {256: "C0", 768: "C3"} + rows = [] + for d in dims: + mc = np.zeros(len(snr_db)) + for _ in range(ntr): + e1, e2 = embed_pair(d, beta) + M1, M2 = two_user_masks(d, beta) + r0 = (M1 @ e1) + (M2 @ e2) + n = (rng.standard_normal(d) + 1j * rng.standard_normal(d)) / math.sqrt(2) + for k, s in enumerate(snr_db): + sig = 10 ** (-s / 20.0) + g1, _ = demux(r0 + sig * n, M1, M2, 1.0, 1.0, beta) + mc[k] += np.linalg.norm(g1 - e1)**2 + mc /= ntr + rho = 10 ** (snr_db / 10.0) + th = d / (rho * g) + csi + ideal = d / (rho * g) + ax.semilogy(snr_db, mc, "o", ms=3.5, color=colors[d], mfc="none", + label=rf"MC, $d={d}$") + ax.semilogy(snr_db, th, "-", color=colors[d], + label=rf"Theorem 1, $d={d}$") + if d == dims[-1]: + ax.semilogy(snr_db, ideal, ":", color="k", lw=1.1, + label="Idealized (no floor)") + for s, m, t, i in zip(snr_db, mc, th, ideal): + rows.append([d, s, m, t, i]) + onset = 10 * math.log10(d / (g * csi)) + print(f" d={d}: floor C_SI={csi:.4f}, onset ~{onset:.1f} dB, " + f"max MC/theory dev " + f"{100*max(abs(mc/th-1)):.1f}%") + ax.axhline(csi, color="gray", lw=0.8, ls="--") + ax.text(1.0, csi * 1.15, r"floor $C_{\mathrm{SI}}$", fontsize=7, color="gray") + ax.set_xlabel("Per-block SNR $\\rho$ [dB]") + ax.set_ylabel(r"Per-user MSE $\mathbb{E}\|\hat{\mathbf{e}}_u-\mathbf{e}_u\|_2^2$") + ax.set_xlim(0, 40); ax.set_ylim(0.5, 2000) + ax.legend(loc="upper right", ncol=1) + save_fig(fig, "fig_floor") + write_csv("floor_validation", ["d", "snr_db", "mse_mc", "mse_theory", "mse_ideal"], rows) + + +# ------------------------------------------------------------------ +# E2 : realisable SIC vs genie SIC vs EDMA vs OMA (Rayleigh) +# ------------------------------------------------------------------ +def E2_sic(beta=0.311, d=512, snr_db=np.arange(0, 31, 5), ntr=400): + print("\n=== E2: realisable vs genie SIC (Rayleigh) ===") + res = {k: np.zeros(len(snr_db)) for k in + ("edma", "oma", "genie", "sic")} + for _ in range(ntr): + e1, e2 = embed_pair(d, beta) + M1, M2 = two_user_masks(d, beta) + h1, h2 = rayleigh(2) + r0 = h1 * (M1 @ e1) + h2 * (M2 @ e2) + n = (rng.standard_normal(d) + 1j * rng.standard_normal(d)) / math.sqrt(2) + n2 = (rng.standard_normal(d) + 1j * rng.standard_normal(d)) / math.sqrt(2) + for k, s in enumerate(snr_db): + sig = 10 ** (-s / 20.0) + r = r0 + sig * n + # EDMA + g1, g2 = demux(r, M1, M2, h1, h2, beta) + res["edma"][k] += 0.5 * (cosine(g1, e1) + cosine(g2, e2)) + # OMA equivalent-bandwidth model: interference-free, noise x sqrt(2) + o1 = e1 + math.sqrt(2) * sig * n / h1 + o2 = e2 + math.sqrt(2) * sig * n2 / h2 + res["oma"][k] += 0.5 * (cosine(o1, e1) + cosine(o2, e2)) + # genie SIC: perfect removal of the other user for BOTH users + ge1 = M1.T @ (r - h2 * (M2 @ e2)) / h1 + ge2 = M2.T @ (r - h1 * (M1 @ e1)) / h2 + res["genie"][k] += 0.5 * (cosine(ge1, e1) + cosine(ge2, e2)) + # realisable SIC: stronger user first (matched filter), + # unit-norm projection as the analog decision, then subtract + if abs(h1) >= abs(h2): + hs, hw, Ms, Mw, es, ew = h1, h2, M1, M2, e1, e2 + else: + hs, hw, Ms, Mw, es, ew = h2, h1, M2, M1, e2, e1 + d_s = Ms.T @ r / hs + dec_s = d_s / np.linalg.norm(d_s) # analog decision + r_res = r - hs * (Ms @ dec_s) + d_w = Mw.T @ r_res / hw + res["sic"][k] += 0.5 * (cosine(d_s, es) + cosine(d_w, ew)) + for k in res: + res[k] /= ntr + fig, ax = plt.subplots() + ax.plot(snr_db, res["edma"], "o-", color="C3", label="EDMA") + ax.plot(snr_db, res["genie"], "s--", color="C0", label="Genie-aided SIC") + ax.plot(snr_db, res["sic"], "^-.", color="C2", label="Realisable SIC") + ax.plot(snr_db, res["oma"], "v:", color="C1", label="OMA") + ax.set_xlabel("Per-block SNR $\\rho$ [dB]") + ax.set_ylabel("Mean cosine similarity") + ax.set_xlim(snr_db[0], snr_db[-1]); ax.set_ylim(0, 1) + ax.legend(loc="upper left") + save_fig(fig, "fig_sic") + rows = [[s] + [res[k][i] for k in ("edma", "oma", "genie", "sic")] + for i, s in enumerate(snr_db)] + write_csv("sic_comparison", ["snr_db", "edma", "oma", "genie", "sic"], rows) + i20 = list(snr_db).index(20) + print(f" at 20 dB: EDMA {res['edma'][i20]:.3f}, realisable SIC " + f"{res['sic'][i20]:.3f}, genie {res['genie'][i20]:.3f}, " + f"OMA {res['oma'][i20]:.3f}") + + +# ------------------------------------------------------------------ +# E3 : Rayleigh unconditional MSE ??ZF inversion vs regularised +# ------------------------------------------------------------------ +def E3_regularised(beta=0.311, d=512, snrs=(10, 20), ntr=4000): + print("\n=== E3: Rayleigh unconditional MSE, ZF vs regularised ===") + rows = [] + for s in snrs: + sig = 10 ** (-s / 20.0) + sig2 = sig**2 + mse_zf, mse_rg = [], [] + for _ in range(ntr): + e1, e2 = embed_pair(d, beta) + M1, M2 = two_user_masks(d, beta) + h1, h2 = rayleigh(2) + r = h1 * (M1 @ e1) + h2 * (M2 @ e2) \ + + sig * (rng.standard_normal(d) + 1j * rng.standard_normal(d)) / math.sqrt(2) + g1, _ = demux(r, M1, M2, h1, h2, beta) + mse_zf.append(np.linalg.norm(g1 - e1)**2) + # regularised inversion: 1/h -> h*/(|h|^2 + d sigma^2) + eps = d * sig2 + f1 = (abs(h1)**2 + eps) / np.conj(h1) + f2 = (abs(h2)**2 + eps) / np.conj(h2) + g1r, _ = demux(r, M1, M2, f1, f2, beta) + mse_rg.append(np.linalg.norm(g1r - e1)**2) + zf_mean, zf_med = float(np.mean(mse_zf)), float(np.median(mse_zf)) + rg_mean, rg_med = float(np.mean(mse_rg)), float(np.median(mse_rg)) + print(f" {s} dB: ZF mean {zf_mean:9.2f} (median {zf_med:6.2f}) | " + f"regularised mean {rg_mean:6.3f} (median {rg_med:6.3f})") + rows.append([s, zf_mean, zf_med, rg_mean, rg_med]) + write_csv("rayleigh_mse", ["snr_db", "zf_mean", "zf_median", + "reg_mean", "reg_median"], rows) + + +# ------------------------------------------------------------------ +# E4 : imperfect CSI +# ------------------------------------------------------------------ +def E4_csi(beta=0.311, d=512, snr=30.0, + sh2=np.array([0.0, 0.01, 0.02, 0.05, 0.1, 0.2, 0.3]), ntr=400): + """EDMA cosine is CSI-direction-invariant (h-estimates cancel in the + demux direction); realisable SIC degrades through its subtraction stage.""" + print("\n=== E4: imperfect CSI robustness (EDMA vs realisable SIC) ===") + sig = 10 ** (-snr / 20.0) + res_e = np.zeros(len(sh2)); res_s = np.zeros(len(sh2)) + for _ in range(ntr): + e1, e2 = embed_pair(d, beta) + M1, M2 = two_user_masks(d, beta) + h1, h2 = rayleigh(2) + r = h1 * (M1 @ e1) + h2 * (M2 @ e2) + sig * ( + rng.standard_normal(d) + 1j * rng.standard_normal(d)) / math.sqrt(2) + eps1, eps2 = rayleigh(2) + for j, v in enumerate(sh2): + hh1 = h1 + math.sqrt(v) * eps1 + hh2 = h2 + math.sqrt(v) * eps2 + g1, g2 = demux(r, M1, M2, hh1, hh2, beta) + res_e[j] += 0.5 * (cosine(g1, e1) + cosine(g2, e2)) + # realisable SIC with the same imperfect estimates + if abs(hh1) >= abs(hh2): + hs, hw, Ms, Mw, es, ew = hh1, hh2, M1, M2, e1, e2 + else: + hs, hw, Ms, Mw, es, ew = hh2, hh1, M2, M1, e2, e1 + d_s = Ms.T @ r / hs + dec_s = d_s / np.linalg.norm(d_s) + r_res = r - hs * (Ms @ dec_s) + d_w = Mw.T @ r_res / hw + res_s[j] += 0.5 * (cosine(d_s, es) + cosine(d_w, ew)) + res_e /= ntr; res_s /= ntr + print(f" EDMA: {res_e[0]:.4f} -> {res_e[-1]:.4f} " + f"(delta {100*(res_e[0]-res_e[-1]):.2f} points)") + print(f" SIC : {res_s[0]:.4f} -> {res_s[-1]:.4f} " + f"(delta {100*(res_s[0]-res_s[-1]):.2f} points)") + fig, ax = plt.subplots() + ax.plot(sh2, res_e, "o-", color="C3", label="EDMA") + ax.plot(sh2, res_s, "^-.", color="C2", label="Realisable SIC") + ax.set_xlabel(r"CSI error variance $\sigma_h^2$") + ax.set_ylabel("Mean cosine similarity") + ax.set_xlim(0, sh2[-1]); ax.set_ylim(0, 0.7) + ax.legend(loc="lower left") + save_fig(fig, "fig_csi") + rows = [[v, res_e[j], res_s[j]] for j, v in enumerate(sh2)] + write_csv("csi_error", ["sigma_h2", "edma", "sic"], rows) + + +# ------------------------------------------------------------------ +# E5 : Walsh-Hadamard structured masks vs Haar +# ------------------------------------------------------------------ +def hadamard(n): + H = np.array([[1.0]]) + while H.shape[0] < n: + H = np.block([[H, H], [H, -H]]) + return H / math.sqrt(n) + + +def E5_maskfam(beta=0.311, d=512, snr_db=np.arange(0, 41, 5), ntr=200): + print("\n=== E5: Walsh-Hadamard masks vs Haar mixture ===") + H = hadamard(d) + g = math.sqrt(1.0 - beta**2) + res = {"haar": np.zeros(len(snr_db)), "wh": np.zeros(len(snr_db))} + for _ in range(ntr): + e1, e2 = embed_pair(d, beta) + M1, M2 = two_user_masks(d, beta) + D1 = np.diag(rng.choice([-1.0, 1.0], d)) + D2 = np.diag(rng.choice([-1.0, 1.0], d)) + W1 = H @ D1 + W2 = beta * W1 + g * (H @ D2) + r0h = (M1 @ e1) + (M2 @ e2) + r0w = (W1 @ e1) + (W2 @ e2) + n = (rng.standard_normal(d) + 1j * rng.standard_normal(d)) / math.sqrt(2) + for k, s in enumerate(snr_db): + sig = 10 ** (-s / 20.0) + g1, _ = demux(r0h + sig * n, M1, M2, 1.0, 1.0, beta) + w1, _ = demux(r0w + sig * n, W1, W2, 1.0, 1.0, beta) + res["haar"][k] += cosine(g1, e1) + res["wh"][k] += cosine(w1, e1) + for k in res: + res[k] /= ntr + dev = 100 * np.max(np.abs(res["wh"] - res["haar"])) + print(f" max |WH - Haar| cosine deviation: {dev:.2f} points") + fig, ax = plt.subplots() + ax.plot(snr_db, res["haar"], "o-", color="C3", + label=r"Haar mixture, $\mathcal{O}(d^2)$") + ax.plot(snr_db, res["wh"], "s--", color="C0", + label=r"Walsh-Hadamard, $\mathcal{O}(d\log d)$") + ax.set_xlabel("Per-block SNR $\\rho$ [dB]") + ax.set_ylabel("Mean cosine similarity") + ax.set_xlim(snr_db[0], snr_db[-1]); ax.set_ylim(0, 0.8) + ax.legend(loc="upper left") + save_fig(fig, "fig_maskfam") + rows = [[s, res["haar"][i], res["wh"][i]] for i, s in enumerate(snr_db)] + write_csv("mask_family_rev", ["snr_db", "haar", "wh"], rows) + + +# ------------------------------------------------------------------ +# E6 : high-affinity combining mode +# ------------------------------------------------------------------ +def E6_coop(d=512, snr=20.0, betas=np.linspace(0.0, 0.98, 21), ntr=100): + print("\n=== E6: high-affinity combining-mode crossover ===") + sig = 10 ** (-snr / 20.0) + pairs = [(haar(d), haar(d)) for _ in range(ntr)] + chans = [rayleigh(2) for _ in range(ntr)] + cos_dx = np.zeros(len(betas)); cos_cb = np.zeros(len(betas)) + for j, beta in enumerate(betas): + for t in range(ntr): + U1, U2 = pairs[t] + h1, h2 = chans[t] + e1, e2 = embed_pair(d, beta) + M1, M2 = two_user_masks(d, beta, U1, U2) + r = h1 * (M1 @ e1) + h2 * (M2 @ e2) + sig * ( + rng.standard_normal(d) + 1j * rng.standard_normal(d)) / math.sqrt(2) + g1, _ = demux(r, M1, M2, h1, h2, beta) + cos_dx[j] += cosine(g1, e1) + # affinity combining: coherent weights for the e1 component + a1 = h1 + beta**2 * h2 + a2 = beta * (h1 + h2) + comb = np.conj(a1) * (M1.T @ r) + np.conj(a2) * (M2.T @ r) + cos_cb[j] += cosine(comb, e1) + cos_dx /= ntr; cos_cb /= ntr + ix = np.where(cos_cb >= cos_dx)[0] + cross = betas[ix[0]] if len(ix) else float("nan") + print(f" crossover affinity ~ {cross:.2f} at rho={snr:.0f} dB") + fig, ax = plt.subplots() + ax.plot(betas, cos_dx, "o-", color="C3", label="Separation mode (demux)") + ax.plot(betas, cos_cb, "s--", color="C0", label="Combining mode") + ax.set_xlabel(r"Pairwise affinity $\beta$") + ax.set_ylabel("Mean cosine similarity") + ax.set_xlim(0, 1); ax.set_ylim(0, 0.8) + ax.legend(loc="lower left") + save_fig(fig, "fig_coop") + rows = [[b, cos_dx[i], cos_cb[i]] for i, b in enumerate(betas)] + write_csv("coop_mode", ["beta", "cos_demux", "cos_combine"], rows) + return cross + + +# ------------------------------------------------------------------ +# E7 : corrected effective-rate figures +# ------------------------------------------------------------------ +def eta_edma(rho, d, beta, csi=None): + g = 1.0 - beta**2 + if csi is None: + csi = C_bar(beta) # symmetrised constant (alternating masks) + return 1.0 / (d / (rho * g) + csi) + + +def E7_rates(beta=0.311, d=512): + print("\n=== E7a: corrected effective-rate comparison ===") + snr_db = np.arange(0, 31, 1.0) + rho = 10 ** (snr_db / 10.0) + g = 1.0 - beta**2 + T_edma = 2 * np.log2(1 + eta_edma(rho, d, beta)) + T_ideal = 2 * np.log2(1 + rho * g / d) + T_oma = 2 * np.log2(1 + rho / (2 * d)) + T_genie = 2 * np.log2(1 + rho / d) + C_mac = np.log2(1 + 2 * rho / d) + fig, ax = plt.subplots() + ax.plot(snr_db, T_edma, "-", color="C3", label="EDMA (Theorem 1)") + ax.plot(snr_db, T_ideal, ":", color="C3", lw=1.1, + label="EDMA idealized (infeasible)") + ax.plot(snr_db, T_oma, "--", color="C1", label="OMA") + ax.plot(snr_db, T_genie, "-.", color="C0", label="Genie-aided SIC bound") + ax.plot(snr_db, C_mac, "-", color="k", lw=1.0, label="MAC sum capacity") + ax.set_xlabel("Per-block SNR $\\rho$ [dB]") + ax.set_ylabel("Effective sum rate [bps/Hz]") + ax.set_xlim(0, 30); ax.set_ylim(0, 3.2) + ax.legend(loc="upper left") + save_fig(fig, "fig_rate_corrected") + rows = [[s, T_edma[i], T_ideal[i], T_oma[i], T_genie[i], C_mac[i]] + for i, s in enumerate(snr_db)] + write_csv("rate_corrected", + ["snr_db", "edma", "edma_ideal", "oma", "genie", "mac"], rows) + i20 = list(snr_db).index(20.0) + csi = C_bar(beta) + rho_c = d * (2 - 1 / g) / csi + print(f" at 20 dB: EDMA {T_edma[i20]:.3f}, OMA {T_oma[i20]:.3f} " + f"(gain {T_edma[i20]/T_oma[i20]:.2f}x), MAC {C_mac[i20]:.3f}, " + f"EDMA/MAC {T_edma[i20]/C_mac[i20]:.3f} (gamma={g:.3f})") + print(f" OMA re-crossover rho_c = {10*math.log10(rho_c):.1f} dB") + + print("\n=== E7b: corrected beta sweep ===") + betas = np.linspace(0.0, 0.98, 99) + fig, ax = plt.subplots() + rows = [] + for s, col in ((10, "C0"), (20, "C3")): + rho_s = 10 ** (s / 10.0) + Te = np.array([2 * np.log2(1 + eta_edma(rho_s, d, b)) for b in betas]) + To = 2 * np.log2(1 + rho_s / (2 * d)) + Tg = 2 * np.log2(1 + rho_s / d) + ax.plot(betas, Te, "-", color=col, label=rf"EDMA, $\rho={s}$ dB") + ax.axhline(To, color=col, ls="--", lw=1.0, + label=rf"OMA, $\rho={s}$ dB") + ax.axhline(Tg, color=col, ls="-.", lw=0.8, + label=rf"Genie-aided SIC, $\rho={s}$ dB") + ix = np.where(Te <= To)[0] + bstar = betas[ix[0]] if len(ix) else float("nan") + print(f" rho={s} dB: crossover beta* = {bstar:.3f} " + f"(wideband limit 1/sqrt(2)=0.707)") + for i, b in enumerate(betas): + rows.append([s, b, Te[i], To, Tg]) + for b0 in (0.031, 0.311): + ax.axvline(b0, color="gray", ls=":", lw=0.9) + ax.set_xlabel(r"Pairwise affinity $\beta$") + ax.set_ylabel("Effective sum rate [bps/Hz]") + ax.set_xlim(0, 1); ax.set_ylim(0, 1.02) + ax.set_yticks([0.0, 0.2, 0.4, 0.6, 0.8, 1.0]) + ax.legend(loc="upper right", ncol=1, fontsize=5.8, + handlelength=1.5, borderaxespad=0.2) + save_fig(fig, "fig_beta_sweep_corrected") + write_csv("beta_sweep_corrected", + ["snr_db", "beta", "edma", "oma", "genie"], rows) + + +def E7_multiuser(beta=0.311, d=512, Us=(2, 3, 4), ntr_cal=80, ntr_mc=120): + print("\n=== E7c: corrected multi-user scaling ===") + snr_db = np.arange(0, 31, 2.5) + snr_mk = np.arange(0, 31, 5) + rho = 10 ** (snr_db / 10.0) + fig, ax = plt.subplots() + colors = {2: "C0", 3: "C2", 4: "C3"} + rows = [] + csi2 = C_SI(beta, 1.0 + 0j) + for U in Us: + B = (1 - beta) * np.eye(U) + beta * np.ones((U, U)) + Binv_uu = np.linalg.inv(B)[0, 0] + gU = 1.0 / Binv_uu + # calibrate C_SI^(U) by noise-free MC at h_u = 1 (the same + # evaluation convention as the two-user rate curves, so the + # U = 2 curve reduces exactly to T_EDMA with C_bar), + # averaged over all users (mask roles are asymmetric) + acc = 0.0 + for _ in range(ntr_cal): + A = np.linalg.cholesky(B) + Uks = [haar(d) for _ in range(U)] + Ms = [sum(A[u, k] * Uks[k] for k in range(U)) for u in range(U)] + h = np.ones(U, dtype=complex) + # symmetric equal-affinity embeddings: e_u = beta-mixed set + base = unit(rng.standard_normal(d)) + es = [] + for u in range(U): + w = rng.standard_normal(d) + w = unit(w - (w @ base) * base) + # construct so that ~ beta pairwise + es.append(unit(math.sqrt(beta) * base + + math.sqrt(1 - beta) * w)) + r = sum(h[u] * (Ms[u] @ es[u]) for u in range(U)) + Binv = np.linalg.inv(B) + # block demux e_hat_u = (1/h_u) sum_v Binv[u,v] M_v^T r + for u in range(U): + eh = sum(Binv[u, v] * (Ms[v].T @ r) for v in range(U)) / h[u] + acc += np.linalg.norm(eh - es[u])**2 + csiU = acc / (ntr_cal * U) + print(f" U={U}: C_SI^(U) = {csiU:.3f} " + f"((U-1)*C_bar = {(U-1)*C_bar(beta):.3f}), gamma_U = {gU:.3f}") + eta = 1.0 / (d * Binv_uu / rho + csiU) + T_th = U * np.log2(1 + eta) + T_oma = U * np.log2(1 + rho / (U * d)) + ax.plot(snr_db, T_th, "-", color=colors[U], label=rf"EDMA, $U={U}$") + ax.plot(snr_db, T_oma, "--", color=colors[U], lw=1.0, + label=rf"OMA, $U={U}$") + # MC markers (with noise, h_u = 1, per-realization real masks) + err_mc = np.zeros(len(snr_mk)) + for _ in range(ntr_mc): + A = np.linalg.cholesky(B) + Uks = [haar(d) for _ in range(U)] + Ms = [sum(A[u, k] * Uks[k] for k in range(U)) for u in range(U)] + h = np.ones(U, dtype=complex) + base = unit(rng.standard_normal(d)) + es = [] + for u in range(U): + w = rng.standard_normal(d) + w = unit(w - (w @ base) * base) + es.append(unit(math.sqrt(beta) * base + + math.sqrt(1 - beta) * w)) + r0 = sum(h[u] * (Ms[u] @ es[u]) for u in range(U)) + n = (rng.standard_normal(d) + 1j * rng.standard_normal(d)) / math.sqrt(2) + Binv = np.linalg.inv(B) + for k, s in enumerate(snr_mk): + sig = 10 ** (-s / 20.0) + r = r0 + sig * n + for u in range(U): + eh = sum(Binv[u, v] * (Ms[v].T @ r) for v in range(U)) / h[u] + err_mc[k] += np.linalg.norm(eh - es[u])**2 + err_mc /= ntr_mc * U + T_mc = U * np.log2(1 + 1.0 / err_mc) + ax.plot(snr_mk, T_mc, "o", color=colors[U], ms=4, mfc="none") + for i, s in enumerate(snr_db): + rows.append([U, s, T_th[i], T_oma[i]]) + i20 = list(snr_db).index(20.0) + print(f" at 20 dB: EDMA {T_th[i20]:.3f} vs OMA {T_oma[i20]:.3f} " + f"(gain {T_th[i20]/T_oma[i20]:.2f}x)") + ax.set_xlabel("Per-block SNR $\\rho$ [dB]") + ax.set_ylabel("Effective sum rate [bps/Hz]") + ax.set_xlim(0, 30); ax.set_ylim(0, 1.5) + ax.legend(loc="upper left", ncol=1, fontsize=6.2) + save_fig(fig, "fig_multiuser_corrected") + write_csv("multiuser_corrected", ["U", "snr_db", "edma", "oma"], rows) + + +if __name__ == "__main__": + import sys + todo = set(sys.argv[1:]) + ALL = { + "E0": E0_theorem_check, "E1": E1_floor, "E2": E2_sic, + "E3": E3_regularised, "E4": E4_csi, "E5": E5_maskfam, + "E6": E6_coop, "E7a": E7_rates, "E7c": E7_multiuser, + } + for name, fn in ALL.items(): + if not todo or name in todo: + fn() + print("\nAll requested revision simulations complete.") diff --git a/code/verify_math.py b/code/verify_math.py new file mode 100644 index 0000000..0a4ccd4 --- /dev/null +++ b/code/verify_math.py @@ -0,0 +1,374 @@ +""" +Complete numerical verification of every closed form in the manuscript. +======================================================================= +Each check implements the formula EXACTLY as printed in main.tex and +compares it against a direct Monte-Carlo or algebraic evaluation. +Prints PASS/FAIL per item with the achieved deviation. Fixed seed. + + V1 per-realization Gram identity M1^T M2 = beta I + sqrt(g) Q + V2 Theorem 1 full MSE (noise + C_SI,u) vs MC, random complex h + V3 noise-free calibration of C_SI,1 / C_SI,2 (several phases) + V4 quoted constants: C_SI,1, C_SI,2, C-bar at (0.311, h=1); + cosine ceiling 1/sqrt(1+C_SI,1) = 0.70; rho_f = 28 dB at d=768 + V5 SINR corollary eta_u = 1/MSE (per-coordinate accounting) + V6 C_SI,u >= 1 for all beta (proof identities gamma*C_SI,1 = + gamma + 4 beta^4, gamma*C_SI,2 = 1 + 3 beta^2 at h=1) + V7 Proposition (MAC consistency) on a (beta, rho) grid + V8 wideband limit T/C_MAC -> gamma + V9 beta* crossover roots at 10/20 dB (0.700 / 0.590, d=512) + V10 rho_c = d(2-1/gamma)/C-bar exact iff-condition + 25.7 dB value + V11 idealized no-floor variant crosses C_MAC at 2 beta^2 d/gamma^2 + (~21 dB at d=512, beta=0.311) + V12 mismatch identity (eq:mismatch) + bound value 8.8e-3 + V13 CSI-direction invariance: |cos| unchanged under wrong h-hat; + eq:csi-free equals eq:correct + V14 cross-moment lemma E[n^H M_u M_v^T n] = sigma^2 beta d + V15 multi-user [B^-1]_uu Sherman-Morrison formula, U = 2..6 + V16 multi-user noise-free C_SI^(U) ~ (U-1) C-bar (within 10 %) + V17 Walsh-Hadamard masks: exact orthogonality + expected cross-Gram +""" +from __future__ import annotations +import math +import numpy as np + +def hadamard(n): + H = np.array([[1.0]]) + while H.shape[0] < n: + H = np.block([[H, H], [H, -H]]) + return H + + +def brentq(f, a, b, tol=1e-12): + fa, fb = f(a), f(b) + assert fa * fb < 0, "no sign change" + for _ in range(200): + m = 0.5 * (a + b) + fm = f(m) + if abs(fm) < tol or (b - a) < tol: + return m + if fa * fm < 0: + b, fb = m, fm + else: + a, fa = m, fm + return 0.5 * (a + b) + +rng = np.random.default_rng(2026) +FAIL = [] + + +def report(name, ok, detail): + tag = "PASS" if ok else "FAIL" + if not ok: + FAIL.append(name) + print(f"[{tag}] {name}: {detail}") + + +def haar(d): + Q, R = np.linalg.qr(rng.standard_normal((d, d))) + return Q * np.sign(np.diag(R)) + + +def unit(v): + return v / np.linalg.norm(v) + + +def pair(d, beta): + e1 = unit(rng.standard_normal(d)) + w = rng.standard_normal(d) + w = unit(w - (w @ e1) * e1) + return e1, beta * e1 + math.sqrt(1 - beta**2) * w + + +def csi1(beta, c): + g = 1 - beta**2 + n2 = 1 + beta**2 * abs(c)**2 + 2 * beta**2 * np.real(c) + return (g**2 * abs(c)**2 + beta**2 * n2) / g + + +def csi2(beta, c): + g = 1 - beta**2 + return (abs(c)**2 + beta**2 + 2 * beta**2 * np.real(c)) / g + + +# ---------------- V1: per-realization Gram identity ---------------- +d, beta = 256, 0.311 +g = 1 - beta**2 +U1, U2 = haar(d), haar(d) +M1, M2 = U1, beta * U1 + math.sqrt(g) * U2 +dev = np.abs(M1.T @ M2 - (beta * np.eye(d) + + math.sqrt(g) * U1.T @ U2)).max() +report("V1 Gram identity", dev < 1e-12, f"max dev {dev:.2e}") + +# ---------------- V2: Theorem 1 full MSE, random complex h --------- +d = 512 +for beta in (0.1, 0.311, 0.5): + g = 1 - beta**2 + h = (rng.standard_normal(2) + 1j * rng.standard_normal(2)) / math.sqrt(2) + h1, h2 = h + rho_db = 15.0 + sig = 10 ** (-rho_db / 20.0) + e1, e2 = pair(d, beta) + mc = np.zeros(2) + NT = 300 + for _ in range(NT): + U1, U2 = haar(d), haar(d) + M1, M2 = U1, beta * U1 + math.sqrt(g) * U2 + n = (rng.standard_normal(d) + 1j * rng.standard_normal(d)) \ + / math.sqrt(2) + r = h1 * (M1 @ e1) + h2 * (M2 @ e2) + sig * n + t1 = M1.T @ r / h1 + t2 = M2.T @ r / h2 + g1 = (t1 - beta * (h2 / h1) * t2) / g + g2 = (t2 - beta * (h1 / h2) * t1) / g + mc[0] += np.linalg.norm(g1 - e1)**2 + mc[1] += np.linalg.norm(g2 - e2)**2 + mc /= NT + th1 = d * sig**2 / (abs(h1)**2 * g) + csi1(beta, h2 / h1) + th2 = d * sig**2 / (abs(h2)**2 * g) + csi2(beta, h1 / h2) + dev = max(abs(mc[0] / th1 - 1), abs(mc[1] / th2 - 1)) + report(f"V2 Theorem 1 MSE (beta={beta})", dev < 0.02, + f"MC/theory dev {100*dev:.2f}% (O(1/d) at d={d})") + +# ---------------- V3: noise-free C_SI calibration ------------------ +d = 512 +for phase in (0.0, math.pi / 3, math.pi): + beta = 0.311 + g = 1 - beta**2 + h1 = 1.0 + 0j + h2 = np.exp(1j * phase) + e1, e2 = pair(d, beta) + mc = np.zeros(2) + NT = 200 + for _ in range(NT): + U1, U2 = haar(d), haar(d) + M1, M2 = U1, beta * U1 + math.sqrt(g) * U2 + r = h1 * (M1 @ e1) + h2 * (M2 @ e2) + t1 = M1.T @ r / h1 + t2 = M2.T @ r / h2 + g1 = (t1 - beta * (h2 / h1) * t2) / g + g2 = (t2 - beta * (h1 / h2) * t1) / g + mc[0] += np.linalg.norm(g1 - e1)**2 + mc[1] += np.linalg.norm(g2 - e2)**2 + mc /= NT + t1v, t2v = csi1(beta, h2 / h1), csi2(beta, h1 / h2) + dev = max(abs(mc[0] / t1v - 1), abs(mc[1] / t2v - 1)) + report(f"V3 noise-free C_SI (phase={phase:.2f})", dev < 0.02, + f"dev {100*dev:.2f}%") + +# ---------------- V4: quoted constants ----------------------------- +beta = 0.311 +g = 1 - beta**2 +c1v, c2v = csi1(beta, 1.0 + 0j), csi2(beta, 1.0 + 0j) +cbar = (c1v + c2v) / 2 +ceil1 = 1 / math.sqrt(1 + c1v) +rho_f_db = 10 * math.log10(768 * g / c1v) +ok = (abs(cbar - 1.2349) < 5e-4 and abs(ceil1 - 0.70) < 5e-3 + and abs(rho_f_db - 28) < 0.5) +report("V4 quoted constants", ok, + f"C_SI,1 {c1v:.4f}, C_SI,2 {c2v:.4f}, C-bar {cbar:.4f} " + f"(quoted 1.2349), ceiling {ceil1:.4f} (quoted 0.70), " + f"rho_f {rho_f_db:.1f} dB (quoted 28)") + +# ---------------- V5: SINR = 1/MSE --------------------------------- +rho = 10 ** (15 / 10) +eta = 1 / (512 / (rho * g) + c1v) +mse = 512 / (rho * g) + c1v +report("V5 SINR corollary", abs(eta * mse - 1) < 1e-12, + f"eta*MSE = {eta*mse:.6f}") + +# ---------------- V6: C_SI >= 1 and proof identities --------------- +ok = True +worst = 1e9 +for b in np.linspace(0.0, 0.99, 200): + gg = 1 - b**2 + lhs1 = gg * csi1(b, 1.0 + 0j) + lhs2 = gg * csi2(b, 1.0 + 0j) + if abs(lhs1 - (gg + 4 * b**4)) > 1e-12: ok = False + if abs(lhs2 - (1 + 3 * b**2)) > 1e-12: ok = False + worst = min(worst, csi1(b, 1.0 + 0j), csi2(b, 1.0 + 0j)) +report("V6 C_SI >= 1 + proof identities", ok and worst >= 1 - 1e-12, + f"min C_SI over beta grid = {worst:.6f}") + +# ---------------- V7: MAC consistency on a grid -------------------- +def T_edma(b, r_, d_): + gg = 1 - b**2 + cb = (csi1(b, 1 + 0j) + csi2(b, 1 + 0j)) / 2 + return 2 * np.log2(1 + 1 / (d_ / (r_ * gg) + cb)) + +ok = True +for b in np.linspace(0, 0.95, 40): + for rdb in np.linspace(-10, 60, 60): + r_ = 10 ** (rdb / 10) + gg = 1 - b**2 + mid = np.log2(1 + 2 * r_ * gg / 512) + cmac = np.log2(1 + 2 * r_ / 512) + if T_edma(b, r_, 512) > mid + 1e-12 or mid > cmac + 1e-12: + ok = False +report("V7 MAC consistency grid", ok, "T_EDMA <= log2(1+2 rho g/d) <= C_MAC") + +# ---------------- V8: wideband limit ------------------------------- +b = 0.311 +r_ = 1e-6 * 512 +lim = T_edma(b, r_, 512) / np.log2(1 + 2 * r_ / 512) +report("V8 wideband limit", abs(lim - (1 - b**2)) < 1e-3, + f"T/C_MAC at rho/d=1e-6: {lim:.5f} vs gamma {1-b**2:.5f}") + +# ---------------- V9: beta* crossover roots ------------------------ +def beta_star(rdb, d_=512): + r_ = 10 ** (rdb / 10) + T_oma = 2 * np.log2(1 + r_ / (2 * d_)) + return brentq(lambda b: T_edma(b, r_, d_) - T_oma, 0.3, 0.9) + +b10, b20 = beta_star(10), beta_star(20) +report("V9 beta* crossover", abs(b10 - 0.700) < 5e-3 + and abs(b20 - 0.590) < 5e-3, + f"10 dB: {b10:.3f} (quoted 0.700), 20 dB: {b20:.3f} (quoted 0.590)") + +# ---------------- V10: rho_c iff-condition + value ----------------- +b = 0.311 +gg = 1 - b**2 +cb = (csi1(b, 1 + 0j) + csi2(b, 1 + 0j)) / 2 +rho_c = 512 * (2 - 1 / gg) / cb +rho_c_db = 10 * math.log10(rho_c) +eps = 1e-4 +below = T_edma(b, rho_c * (1 - eps), 512) \ + - 2 * np.log2(1 + rho_c * (1 - eps) / 1024) +above = T_edma(b, rho_c * (1 + eps), 512) \ + - 2 * np.log2(1 + rho_c * (1 + eps) / 1024) +report("V10 rho_c crossover", below > 0 > above + and abs(rho_c_db - 25.7) < 0.1, + f"rho_c {rho_c_db:.2f} dB (quoted 25.7), sign flip verified") + +# ---------------- V11: idealized-MAC crossing ---------------------- +rho_x = 2 * b**2 * 512 / gg**2 +f = lambda r_: 2 * np.log2(1 + r_ * gg / 512) - np.log2(1 + 2 * r_ / 512) +root = brentq(f, 10.0, 1e4) +report("V11 idealized crossing", abs(root / rho_x - 1) < 1e-6 + and abs(10 * math.log10(root) - 21) < 0.3, + f"root {10*math.log10(root):.2f} dB, formula 2b^2d/g^2 " + f"{10*math.log10(rho_x):.2f} dB (quoted ~21)") + +# ---------------- V12: mismatch identity + bound value ------------- +b, delta = 0.3, 0.06 +bh = b + delta +hr = 1.0 + 0j +e1, e2 = pair(64, b) +t1 = e1 + b * hr * e2 # expected-Gram surrogate outputs +t2v_ = e2 + b * np.conj(hr) * e1 +g1 = (t1 - bh * hr * t2v_) / (1 - bh**2) +lhs = g1 - e1 +rhs = delta / (1 - bh**2) * (bh * e1 - hr * e2) +dev = np.linalg.norm(lhs - rhs) +bound = delta**2 * (abs(bh) + abs(hr))**2 / (1 - bh**2)**2 +report("V12 mismatch identity", dev < 1e-12 + and abs(bound - 8.8e-3) < 2e-4, + f"identity dev {dev:.1e}, bound {bound:.4f} (quoted 8.8e-3)") + +# ---------------- V13: CSI-direction invariance -------------------- +d = 256 +b = 0.311 +g = 1 - b**2 +e1, e2 = pair(d, b) +U1, U2 = haar(d), haar(d) +M1, M2 = U1, b * U1 + math.sqrt(g) * U2 +h1, h2 = 0.7 - 0.4j, -0.2 + 1.1j +n = (rng.standard_normal(d) + 1j * rng.standard_normal(d)) / math.sqrt(2) +r = h1 * (M1 @ e1) + h2 * (M2 @ e2) + 0.1 * n +truec = (M1.T @ r / h1 - b * (h2 / h1) * (M2.T @ r / h2)) / g +csif = (M1 - b * M2).T @ r / (h1 * g) +dev1 = np.abs(truec - csif).max() +h1w = h1 * (1.5 * np.exp(0.8j)) # badly wrong estimate +wrong = (M1 - b * M2).T @ r / (h1w * g) +c_true = abs(np.vdot(truec, e1)) / (np.linalg.norm(truec)) +c_wrong = abs(np.vdot(wrong, e1)) / (np.linalg.norm(wrong)) +report("V13 CSI invariance", dev1 < 1e-12 and abs(c_true - c_wrong) < 1e-12, + f"csi-free identity dev {dev1:.1e}, |cos| unchanged " + f"({c_true:.6f} vs {c_wrong:.6f})") + +# ---------------- V14: cross-moment lemma -------------------------- +d = 256 +b = 0.311 +sig2 = 0.5 +acc = 0.0 +NT = 4000 +U1, U2 = haar(d), haar(d) +M1, M2 = U1, b * U1 + math.sqrt(1 - b**2) * U2 +for _ in range(NT): + n = math.sqrt(sig2) * (rng.standard_normal(d) + + 1j * rng.standard_normal(d)) / math.sqrt(2) + acc += np.real(np.conj(n) @ (M1 @ (M2.T @ n))) +acc /= NT +th = sig2 * b * d +report("V14 cross-moment lemma", abs(acc / th - 1) < 0.05, + f"MC {acc:.3f} vs sigma^2 beta d {th:.3f} " + f"({100*abs(acc/th-1):.1f}%)") + +# ---------------- V15: [B^-1]_uu Sherman-Morrison ------------------ +ok = True +for U in range(2, 7): + for b in (0.1, 0.311, 0.6): + B = (1 - b) * np.eye(U) + b * np.ones((U, U)) + num = 1 + (U - 2) * b + den = (1 - b) * (1 + (U - 1) * b) + if abs(np.linalg.inv(B)[0, 0] - num / den) > 1e-12: + ok = False +report("V15 [B^-1]_uu formula", ok, "U=2..6, beta grid, exact") + +# ---------------- V16: multi-user C_SI^(U) ------------------------- +d = 512 +b = 0.311 +g = 1 - b**2 +cb = (csi1(b, 1 + 0j) + csi2(b, 1 + 0j)) / 2 +for U in (3, 4): + B = (1 - b) * np.eye(U) + b * np.ones((U, U)) + Binv = np.linalg.inv(B) + es = [] + e1 = unit(rng.standard_normal(d)) + for u in range(U): + if u == 0: + es.append(e1) + else: + w = rng.standard_normal(d) + w = unit(w - (w @ e1) * e1) + es.append(b * e1 + math.sqrt(g) * w) + mse = 0.0 + NT = 60 + for _ in range(NT): + Us = [haar(d) for _ in range(U)] + Ms = [Us[0]] + for u in range(1, U): + Ms.append(b * Us[0] + math.sqrt(g) * Us[u]) + r = sum(Ms[u] @ es[u] for u in range(U)) # h_u = 1 + t = np.stack([Ms[u].T @ r for u in range(U)]) + rec = np.einsum("uv,vd->ud", Binv, t) + mse += np.linalg.norm(rec[0] - es[0])**2 + mse /= NT + ratio = mse / ((U - 1) * cb) + report(f"V16 C_SI^(U) additivity (U={U})", abs(ratio - 1) < 0.10, + f"noise-free MSE {mse:.3f} vs (U-1)C-bar " + f"{(U-1)*cb:.3f} (ratio {ratio:.3f})") + +# ---------------- V17: Walsh-Hadamard masks ------------------------ +d = 256 +H = hadamard(d) / math.sqrt(d) +b = 0.311 +acc = np.zeros((d, d)) +NT = 400 +for _ in range(NT): + D1 = np.diag(rng.choice([-1.0, 1.0], d)) + D2 = np.diag(rng.choice([-1.0, 1.0], d)) + W1 = H @ D1 + W2 = b * W1 + math.sqrt(1 - b**2) * H @ D2 + acc += W1.T @ W2 / NT +orth = np.abs((H @ np.diag(rng.choice([-1.0, 1.0], d))).T + @ (H @ np.diag(rng.choice([-1.0, 1.0], d))) + @ np.ones(d) / d).max() +diag_dev = abs(np.diag(acc).mean() - b) +off = np.abs(acc - np.diag(np.diag(acc))).mean() +report("V17 WH masks", diag_dev < 0.02 and off < 0.01, + f"E[cross-Gram] diag {np.diag(acc).mean():.4f} vs beta {b}, " + f"mean |off-diag| {off:.4f}") + +print() +print("=" * 60) +print(f"RESULT: {'ALL PASS' if not FAIL else 'FAILURES: ' + ', '.join(FAIL)}") diff --git a/data/bert_vit_cached.pkl b/data/bert_vit_cached.pkl new file mode 100644 index 0000000..60b1d11 Binary files /dev/null and b/data/bert_vit_cached.pkl differ diff --git a/data/bertvit_merged.csv b/data/bertvit_merged.csv new file mode 100644 index 0000000..e5747bd --- /dev/null +++ b/data/bertvit_merged.csv @@ -0,0 +1,8 @@ +snr_db,edma,oma,genie,att,att_x,todma,edma_ref,edma_ref2 +0.0,0.04509729548248326,0.03878942917318714,0.04510116805362887,0.06537951208185813,0.028956357115368724,0.009263779561898224,0.051216359648716556,0.05146971093667761 +5.0,0.06451154394270053,0.0507439763307109,0.06460883367368744,0.11006860490285489,0.02899092581015571,0.014985754149760718,0.08214049352367703,0.08245299246543433 +10.0,0.10340689217396296,0.07698703231946222,0.10399757263661547,0.18771051966437632,0.029096261892353006,0.03519980048035704,0.13997304338278754,0.14036958956224843 +15.0,0.17239818787681693,0.12749333352586661,0.17548648804419778,0.3057075884366212,0.029248349735797895,0.10412376981275459,0.23587541750084084,0.23652206338075601 +20.0,0.2789011314185965,0.2156999450240525,0.29264116008861335,0.4510118978738174,0.029343304376527338,0.21990683440776934,0.37242277625984427,0.37307923116081904 +25.0,0.41080176204708735,0.3530383192829985,0.45731712677712716,0.5840194131238486,0.02929760473668019,0.3162182821357606,0.5247271302425862,0.5245882651817437 +30.0,0.5301073454886365,0.5306502765434139,0.6408057261877141,0.6726967507733389,0.029179666470356736,0.3661426990593047,0.6493871028835615,0.6479461262985481 diff --git a/data/beta_sweep_corrected.csv b/data/beta_sweep_corrected.csv new file mode 100644 index 0000000..35978b2 --- /dev/null +++ b/data/beta_sweep_corrected.csv @@ -0,0 +1,199 @@ +snr_db,beta,edma,oma,genie +10,0.0,0.054752877862610065,0.028040940629869258,0.055811993139768964 +10,0.01,0.054747350279920316,0.028040940629869258,0.055811993139768964 +10,0.02,0.054730767346147666,0.028040940629869258,0.055811993139768964 +10,0.03,0.05470312850494755,0.028040940629869258,0.055811993139768964 +10,0.04,0.05466443283160872,0.028040940629869258,0.055811993139768964 +10,0.05,0.054614679036858765,0.028040940629869258,0.055811993139768964 +10,0.06,0.05455386547218096,0.028040940629869258,0.055811993139768964 +10,0.07,0.05448199013665998,0.028040940629869258,0.055811993139768964 +10,0.08,0.05439905068534405,0.028040940629869258,0.055811993139768964 +10,0.09,0.05430504443913251,0.028040940629869258,0.055811993139768964 +10,0.1,0.054199968396186245,0.028040940629869258,0.055811993139768964 +10,0.11,0.05408381924486196,0.028040940629869258,0.055811993139768964 +10,0.12,0.053956593378173205,0.028040940629869258,0.055811993139768964 +10,0.13,0.05381828690977572,0.028040940629869258,0.055811993139768964 +10,0.14,0.053668895691484,0.028040940629869258,0.055811993139768964 +10,0.15,0.05350841533231138,0.028040940629869258,0.055811993139768964 +10,0.16,0.0533368412190459,0.028040940629869258,0.055811993139768964 +10,0.17,0.05315416853834894,0.028040940629869258,0.055811993139768964 +10,0.18,0.0529603923003942,0.028040940629869258,0.055811993139768964 +10,0.19,0.052755507364029314,0.028040940629869258,0.055811993139768964 +10,0.2,0.05253950846347685,0.028040940629869258,0.055811993139768964 +10,0.21,0.05231239023656164,0.028040940629869258,0.055811993139768964 +10,0.22,0.0520741472544757,0.028040940629869258,0.055811993139768964 +10,0.23,0.05182477405307159,0.028040940629869258,0.055811993139768964 +10,0.24,0.05156426516568283,0.028040940629869258,0.055811993139768964 +10,0.25,0.051292615157482124,0.028040940629869258,0.055811993139768964 +10,0.26,0.05100981866135644,0.028040940629869258,0.055811993139768964 +10,0.27,0.05071587041531166,0.028040940629869258,0.055811993139768964 +10,0.28,0.05041076530139947,0.028040940629869258,0.055811993139768964 +10,0.29,0.050094498386152486,0.028040940629869258,0.055811993139768964 +10,0.3,0.04976706496254393,0.028040940629869258,0.055811993139768964 +10,0.31,0.04942846059343863,0.028040940629869258,0.055811993139768964 +10,0.32,0.04907868115655627,0.028040940629869258,0.055811993139768964 +10,0.33,0.04871772289091538,0.028040940629869258,0.055811993139768964 +10,0.34,0.04834558244476425,0.028040940629869258,0.055811993139768964 +10,0.35000000000000003,0.04796225692498555,0.028040940629869258,0.055811993139768964 +10,0.36,0.04756774394795485,0.028040940629869258,0.055811993139768964 +10,0.37,0.047162041691850294,0.028040940629869258,0.055811993139768964 +10,0.38,0.04674514895039684,0.028040940629869258,0.055811993139768964 +10,0.39,0.046317065188023115,0.028040940629869258,0.055811993139768964 +10,0.4,0.04587779059641903,0.028040940629869258,0.055811993139768964 +10,0.41000000000000003,0.045427326152471775,0.028040940629869258,0.055811993139768964 +10,0.42,0.04496567367756414,0.028040940629869258,0.055811993139768964 +10,0.43,0.04449283589819911,0.028040940629869258,0.055811993139768964 +10,0.44,0.044008816507942514,0.028040940629869258,0.055811993139768964 +10,0.45,0.04351362023064087,0.028040940629869258,0.055811993139768964 +10,0.46,0.04300725288489753,0.028040940629869258,0.055811993139768964 +10,0.47000000000000003,0.04248972144976068,0.028040940629869258,0.055811993139768964 +10,0.48,0.04196103413160188,0.028040940629869258,0.055811993139768964 +10,0.49,0.041421200432142105,0.028040940629869258,0.055811993139768964 +10,0.5,0.040870231217584416,0.028040940629869258,0.055811993139768964 +10,0.51,0.04030813878880955,0.028040940629869258,0.055811993139768964 +10,0.52,0.03973493695259877,0.028040940629869258,0.055811993139768964 +10,0.53,0.039150641093819265,0.028040940629869258,0.055811993139768964 +10,0.54,0.03855526824853652,0.028040940629869258,0.055811993139768964 +10,0.55,0.03794883717799136,0.028040940629869258,0.055811993139768964 +10,0.56,0.03733136844337993,0.028040940629869258,0.055811993139768964 +10,0.5700000000000001,0.03670288448138829,0.028040940629869258,0.055811993139768964 +10,0.58,0.036063409680404356,0.028040940629869258,0.055811993139768964 +10,0.59,0.035412970457347266,0.028040940629869258,0.055811993139768964 +10,0.6,0.034751595335041165,0.028040940629869258,0.055811993139768964 +10,0.61,0.03407931502005716,0.028040940629869258,0.055811993139768964 +10,0.62,0.033396162480946456,0.028040940629869258,0.055811993139768964 +10,0.63,0.03270217302678281,0.028040940629869258,0.055811993139768964 +10,0.64,0.03199738438593123,0.028040940629869258,0.055811993139768964 +10,0.65,0.03128183678494588,0.028040940629869258,0.055811993139768964 +10,0.66,0.030555573027513647,0.028040940629869258,0.055811993139768964 +10,0.67,0.02981863857334309,0.028040940629869258,0.055811993139768964 +10,0.68,0.02907108161689106,0.028040940629869258,0.055811993139768964 +10,0.6900000000000001,0.028312953165838792,0.028040940629869258,0.055811993139768964 +10,0.7000000000000001,0.027544307119194162,0.028040940629869258,0.055811993139768964 +10,0.71,0.026765200344916605,0.028040940629869258,0.055811993139768964 +10,0.72,0.02597569275694614,0.028040940629869258,0.055811993139768964 +10,0.73,0.025175847391522434,0.028040940629869258,0.055811993139768964 +10,0.74,0.024365730482666725,0.028040940629869258,0.055811993139768964 +10,0.75,0.023545411536702542,0.028040940629869258,0.055811993139768964 +10,0.76,0.022714963405684276,0.028040940629869258,0.055811993139768964 +10,0.77,0.02187446235961152,0.028040940629869258,0.055811993139768964 +10,0.78,0.021023988157275277,0.028040940629869258,0.055811993139768964 +10,0.79,0.0201636241156152,0.028040940629869258,0.055811993139768964 +10,0.8,0.019293457177440704,0.028040940629869258,0.055811993139768964 +10,0.81,0.018413577977365713,0.028040940629869258,0.055811993139768964 +10,0.8200000000000001,0.01752408090582169,0.028040940629869258,0.055811993139768964 +10,0.8300000000000001,0.016625064170996434,0.028040940629869258,0.055811993139768964 +10,0.84,0.015716629858541303,0.028040940629869258,0.055811993139768964 +10,0.85,0.014798883988911094,0.028040940629869258,0.055811993139768964 +10,0.86,0.013871936572160262,0.028040940629869258,0.055811993139768964 +10,0.87,0.012935901660068121,0.028040940629869258,0.055811993139768964 +10,0.88,0.011990897395407239,0.028040940629869258,0.055811993139768964 +10,0.89,0.011037046058227617,0.028040940629869258,0.055811993139768964 +10,0.9,0.010074474108977324,0.028040940629869258,0.055811993139768964 +10,0.91,0.009103312228316158,0.028040940629869258,0.055811993139768964 +10,0.92,0.008123695353459515,0.028040940629869258,0.055811993139768964 +10,0.93,0.007135762710902627,0.028040940629869258,0.055811993139768964 +10,0.9400000000000001,0.006139657845366697,0.028040940629869258,0.055811993139768964 +10,0.9500000000000001,0.0051355286448144695,0.028040940629869258,0.055811993139768964 +10,0.96,0.004123527361384938,0.028040940629869258,0.055811993139768964 +10,0.97,0.0031038106281055557,0.028040940629869258,0.055811993139768964 +10,0.98,0.002076539471223253,0.028040940629869258,0.055811993139768964 +20,0.0,0.4366911765474922,0.2688526404418522,0.5147756853853035 +20,0.01,0.4366440287117167,0.2688526404418522,0.5147756853853035 +20,0.02,0.43650257394341674,0.2688526404418522,0.5147756853853035 +20,0.03,0.43626677851363305,0.2688526404418522,0.5147756853853035 +20,0.04,0.4359365863873445,0.2688526404418522,0.5147756853853035 +20,0.05,0.4355119194934469,0.2688526404418522,0.5147756853853035 +20,0.06,0.43499267810292913,0.2688526404418522,0.5147756853853035 +20,0.07,0.43437874131546467,0.2688526404418522,0.5147756853853035 +20,0.08,0.433669967654663,0.2688526404418522,0.5147756853853035 +20,0.09,0.4328661957722866,0.2688526404418522,0.5147756853853035 +20,0.1,0.4319672452617536,0.2688526404418522,0.5147756853853035 +20,0.11,0.43097291758127854,0.2688526404418522,0.5147756853853035 +20,0.12,0.4298829970870116,0.2688526404418522,0.5147756853853035 +20,0.13,0.42869725217652294,0.2688526404418522,0.5147756853853035 +20,0.14,0.42741543654296693,0.2688526404418522,0.5147756853853035 +20,0.15,0.4260372905402156,0.2688526404418522,0.5147756853853035 +20,0.16,0.424562542659194,0.2688526404418522,0.5147756853853035 +20,0.17,0.4229909111155674,0.2688526404418522,0.5147756853853035 +20,0.18,0.4213221055488418,0.2688526404418522,0.5147756853853035 +20,0.19,0.4195558288327915,0.2688526404418522,0.5147756853853035 +20,0.2,0.4176917789970004,0.2688526404418522,0.5147756853853035 +20,0.21,0.41572965125910694,0.2688526404418522,0.5147756853853035 +20,0.22,0.413669140167154,0.2688526404418522,0.5147756853853035 +20,0.23,0.411509941851192,0.2688526404418522,0.5147756853853035 +20,0.24,0.40925175638303946,0.2688526404418522,0.5147756853853035 +20,0.25,0.40689429024279106,0.2688526404418522,0.5147756853853035 +20,0.26,0.40443725889034393,0.2688526404418522,0.5147756853853035 +20,0.27,0.40188038943985416,0.2688526404418522,0.5147756853853035 +20,0.28,0.399223423434633,0.2688526404418522,0.5147756853853035 +20,0.29,0.39646611971957335,0.2688526404418522,0.5147756853853035 +20,0.3,0.3936082574077306,0.2688526404418522,0.5147756853853035 +20,0.31,0.3906496389371964,0.2688526404418522,0.5147756853853035 +20,0.32,0.3875900932138665,0.2688526404418522,0.5147756853853035 +20,0.33,0.38442947883515566,0.2688526404418522,0.5147756853853035 +20,0.34,0.3811676873891227,0.2688526404418522,0.5147756853853035 +20,0.35000000000000003,0.37780464682285725,0.2688526404418522,0.5147756853853035 +20,0.36,0.3743403248733226,0.2688526404418522,0.5147756853853035 +20,0.37,0.3707747325532069,0.2688526404418522,0.5147756853853035 +20,0.38,0.36710792768362743,0.2688526404418522,0.5147756853853035 +20,0.39,0.36334001846484065,0.2688526404418522,0.5147756853853035 +20,0.4,0.35947116707538496,0.2688526404418522,0.5147756853853035 +20,0.41000000000000003,0.35550159328936487,0.2688526404418522,0.5147756853853035 +20,0.42,0.3514315781008482,0.2688526404418522,0.5147756853853035 +20,0.43,0.3472614673436184,0.2688526404418522,0.5147756853853035 +20,0.44,0.3429916752938002,0.2688526404418522,0.5147756853853035 +20,0.45,0.3386226882421745,0.2688526404418522,0.5147756853853035 +20,0.46,0.3341550680222958,0.2688526404418522,0.5147756853853035 +20,0.47000000000000003,0.32958945547987767,0.2688526404418522,0.5147756853853035 +20,0.48,0.3249265738682723,0.2688526404418522,0.5147756853853035 +20,0.49,0.32016723215429327,0.2688526404418522,0.5147756853853035 +20,0.5,0.3153123282180963,0.2688526404418522,0.5147756853853035 +20,0.51,0.3103628519303606,0.2688526404418522,0.5147756853853035 +20,0.52,0.3053198880896007,0.2688526404418522,0.5147756853853035 +20,0.53,0.3001846192021282,0.2688526404418522,0.5147756853853035 +20,0.54,0.2949583280869223,0.2688526404418522,0.5147756853853035 +20,0.55,0.2896424002875374,0.2688526404418522,0.5147756853853035 +20,0.56,0.2842383262731147,0.2688526404418522,0.5147756853853035 +20,0.5700000000000001,0.27874770341065014,0.2688526404418522,0.5147756853853035 +20,0.58,0.27317223769083826,0.2688526404418522,0.5147756853853035 +20,0.59,0.2675137451901349,0.2688526404418522,0.5147756853853035 +20,0.6,0.26177415325210973,0.2688526404418522,0.5147756853853035 +20,0.61,0.25595550137174955,0.2688526404418522,0.5147756853853035 +20,0.62,0.2500599417670772,0.2688526404418522,0.5147756853853035 +20,0.63,0.24408973962332856,0.2688526404418522,0.5147756853853035 +20,0.64,0.238047272995916,0.2688526404418522,0.5147756853853035 +20,0.65,0.23193503235958426,0.2688526404418522,0.5147756853853035 +20,0.66,0.22575561979243286,0.2688526404418522,0.5147756853853035 +20,0.67,0.21951174778494328,0.2688526404418522,0.5147756853853035 +20,0.68,0.21320623766571534,0.2688526404418522,0.5147756853853035 +20,0.6900000000000001,0.20684201763731608,0.2688526404418522,0.5147756853853035 +20,0.7000000000000001,0.20042212041749688,0.2688526404418522,0.5147756853853035 +20,0.71,0.1939496804829589,0.2688526404418522,0.5147756853853035 +20,0.72,0.18742793091491083,0.2688526404418522,0.5147756853853035 +20,0.73,0.1808601998477949,0.2688526404418522,0.5147756853853035 +20,0.74,0.17424990652476743,0.2688526404418522,0.5147756853853035 +20,0.75,0.16760055696580017,0.2688526404418522,0.5147756853853035 +20,0.76,0.16091573925657646,0.2688526404418522,0.5147756853853035 +20,0.77,0.15419911846869444,0.2688526404418522,0.5147756853853035 +20,0.78,0.147454431224025,0.2688526404418522,0.5147756853853035 +20,0.79,0.14068547991839878,0.2688526404418522,0.5147756853853035 +20,0.8,0.13389612662207573,0.2688526404418522,0.5147756853853035 +20,0.81,0.12709028667666042,0.2688526404418522,0.5147756853853035 +20,0.8200000000000001,0.12027192201028448,0.2688526404418522,0.5147756853853035 +20,0.8300000000000001,0.11344503419488466,0.2688526404418522,0.5147756853853035 +20,0.84,0.10661365727133493,0.2688526404418522,0.5147756853853035 +20,0.85,0.0997818503699352,0.2688526404418522,0.5147756853853035 +20,0.86,0.09295369015536699,0.2688526404418522,0.5147756853853035 +20,0.87,0.0861332631266301,0.2688526404418522,0.5147756853853035 +20,0.88,0.07932465780369294,0.2688526404418522,0.5147756853853035 +20,0.89,0.07253195683359023,0.2688526404418522,0.5147756853853035 +20,0.9,0.06575922904947452,0.2688526404418522,0.5147756853853035 +20,0.91,0.059010521516665373,0.2688526404418522,0.5147756853853035 +20,0.92,0.052289851600038885,0.2688526404418522,0.5147756853853035 +20,0.93,0.04560119908714468,0.2688526404418522,0.5147756853853035 +20,0.9400000000000001,0.03894849840124009,0.2688526404418522,0.5147756853853035 +20,0.9500000000000001,0.03233563093797358,0.2688526404418522,0.5147756853853035 +20,0.96,0.025766417558755906,0.2688526404418522,0.5147756853853035 +20,0.97,0.019244611272917996,0.2688526404418522,0.5147756853853035 +20,0.98,0.012773890139589456,0.2688526404418522,0.5147756853853035 diff --git a/data/csi_error.csv b/data/csi_error.csv new file mode 100644 index 0000000..083ca84 --- /dev/null +++ b/data/csi_error.csv @@ -0,0 +1,8 @@ +sigma_h2,edma,sic +0.0,0.5445012038424076,0.5460929325736039 +0.01,0.5445012038424075,0.5432828884753359 +0.02,0.5445012038424075,0.5408883972120776 +0.05,0.5445012038424076,0.536040175102482 +0.1,0.5445012038424076,0.5277163316448914 +0.2,0.5445012038424075,0.5169782457463995 +0.3,0.5445012038424076,0.5125870168901699 diff --git a/data/floor_validation.csv b/data/floor_validation.csv new file mode 100644 index 0000000..2af3d95 --- /dev/null +++ b/data/floor_validation.csv @@ -0,0 +1,35 @@ +d,snr_db,mse_mc,mse_theory,mse_ideal +256,0.0,283.9142603214685,284.4533071258869,283.41188049318094 +256,2.5,160.10089036917105,160.41563908393022,159.37421245122425 +256,5.0,90.47776215634474,90.66413246369561,89.62270583098966 +256,7.5,51.32748447743017,51.439977796849504,50.398551164143555 +256,10.0,29.312928645832216,29.38261468202405,28.3411880493181 +256,12.5,16.934181412329476,16.978847877828375,15.937421245122422 +256,15.0,9.973810638369487,10.00369721580492,8.962270583098967 +256,17.5,6.060239200519523,6.081281749120308,5.039855116414356 +256,20.0,3.85987571082472,3.8755454376377614,2.8341188049318093 +256,22.5,2.622819942039895,2.6351687572181945,1.5937421245122423 +256,25.0,1.9273969939302285,1.9376536910158488,0.8962270583098966 +256,27.5,1.5365003821408372,1.5454121443473878,0.5039855116414356 +256,30.0,1.3168093834480725,1.3248385131991331,0.28341188049318095 +256,32.5,1.1933627827422904,1.2008008451571766,0.15937421245122424 +256,35.0,1.1240146926635879,1.131049338536942,0.08962270583098966 +256,37.5,1.085070664488797,1.0918251838700959,0.05039855116414356 +256,40.0,1.0632107739660197,1.0697678207552703,0.028341188049318098 +768,0.0,853.4623064741292,851.2770681122488,850.2356414795429 +768,2.5,480.3951698733645,479.1640639863787,478.12263735367276 +768,5.0,270.60393697803266,269.9095441256749,268.86811749296896 +768,7.5,152.62953339008442,152.23708012513663,151.19565349243067 +768,10.0,86.28755819977675,86.06499078066025,85.0235641479543 +768,12.5,48.98065441173488,48.85369036807322,47.81226373536727 +768,15.0,28.001388546341435,27.928238382002856,26.886811749296903 +768,17.5,16.203841270734593,16.160991981949017,15.119565349243066 +768,20.0,9.569563575405624,9.543783047501382,8.502356414795429 +768,22.5,5.838813072859685,5.822653006242679,4.781226373536727 +768,25.0,3.7408413998745558,3.730107807635642,2.6886811749296897 +768,27.5,2.5610528622492597,2.553383167630259,1.5119565349243067 +768,30.0,1.8975997387446928,1.8916622741854952,0.8502356414795429 +768,32.5,1.5245056756935615,1.519549270059625,0.4781226373536727 +768,35.0,1.314694250809021,1.3102947501989213,0.268868117492969 +768,37.5,1.1967047053652895,1.1926222861983828,0.15119565349243066 +768,40.0,1.1303513753850125,1.1264501968539065,0.0850235641479543 diff --git a/data/mask_family_rev.csv b/data/mask_family_rev.csv new file mode 100644 index 0000000..85d8881 --- /dev/null +++ b/data/mask_family_rev.csv @@ -0,0 +1,10 @@ +snr_db,haar,wh +0,0.05647200954133838,0.05310404690708958 +5,0.08330434028403662,0.07868202035026188 +10,0.1362057604122267,0.1321506210735374 +15,0.22743813806699198,0.22553342842896307 +20,0.3620849406777639,0.36807222557624214 +25,0.5112067973087223,0.5367516728593701 +30,0.6187704792155053,0.6693336605430739 +35,0.670208963249346,0.7373274649462636 +40,0.689340654075078,0.7636921461142663 diff --git a/data/multiuser_corrected.csv b/data/multiuser_corrected.csv new file mode 100644 index 0000000..2ddecf8 --- /dev/null +++ b/data/multiuser_corrected.csv @@ -0,0 +1,40 @@ +U,snr_db,edma,oma +2,0.0,0.005074924495401339,0.0028163887856167778 +2,2.5,0.00900326092256961,0.005006425437490073 +2,5.0,0.015943181620258408,0.008896821012114422 +2,7.5,0.028141662406857754,0.015802100105602065 +2,10.0,0.04939420068532773,0.028040940629869258 +2,12.5,0.08585687636915751,0.04967759953004671 +2,15.0,0.14680246472845263,0.08775733805059951 +2,17.5,0.24437407492333546,0.15425664820473364 +2,20.0,0.39036168250847997,0.2688526404418522 +2,22.5,0.5882403211652596,0.46202930580118473 +2,25.0,0.8236174091640396,0.7765249721523131 +2,27.5,1.0639633374340793,1.2629750132730075 +2,30.0,1.2740371164829405,1.9659871493886203 +3,0.0,0.007166121727981404,0.002816846908845676 +3,2.5,0.012688743558228709,0.005007872928745025 +3,5.0,0.022393454021796717,0.00890139152557443 +3,7.5,0.03929335948023371,0.015816514922095765 +3,10.0,0.0682640356183996,0.028086309611073952 +3,12.5,0.11661479903052363,0.04981987507531682 +3,15.0,0.19381754610825597,0.08820067087182441 +3,17.5,0.3087986357540208,0.15562283620381148 +3,20.0,0.46347315369044484,0.27298359666177824 +3,22.5,0.6453865207846086,0.4741321857975819 +3,25.0,0.8284280872854691,0.8102498992888099 +3,27.5,0.9858586691638921,1.3502134677042328 +3,30.0,1.1039774302731393,2.1701295882547527 +4,0.0,0.009176956996801149,0.002817076044986573 +4,2.5,0.01621312884680335,0.005008597092951442 +4,5.0,0.028502070980983823,0.008903679130992838 +4,7.5,0.04967547887822116,0.015823735486660634 +4,10.0,0.08531654320798115,0.028109067575874017 +4,12.5,0.14302271741867906,0.04989142100177373 +4,15.0,0.2308213082888609,0.08842458349643963 +4,17.5,0.3525340922014516,0.15631809220986412 +4,20.0,0.5011822600084102,0.27511311194165045 +4,22.5,0.6570342137422986,0.4805054388299137 +4,25.0,0.7963620296794736,0.8286132554549862 +4,27.5,0.9042441392584162,1.4000909956579182 +4,30.0,0.9788428109037877,2.294588749973288 diff --git a/data/rate_corrected.csv b/data/rate_corrected.csv new file mode 100644 index 0000000..d907553 --- /dev/null +++ b/data/rate_corrected.csv @@ -0,0 +1,32 @@ +snr_db,edma,edma_ideal,oma,genie,mac +0.0,0.005074922224889888,0.005085968590195728,0.0028163887856167778,0.0056300312141080765,0.005624549193878107 +1.0,0.006383905773418096,0.00640139527831988,0.003545175584028836,0.007086000682366226,0.007077321020140645 +2.0,0.008028883565814418,0.008056567110301317,0.004462402142875826,0.008917913609859949,0.008904174704705635 +3.0,0.010095150934059762,0.010138955836487416,0.005616707528960678,0.01122250279942868,0.011200762797452897 +4.0,0.012689107038928529,0.01275839279457609,0.007069235608353175,0.014121193837328306,0.014086807264803002 +5.0,0.015943159211639874,0.016052690844464874,0.008896821012114422,0.017766293841720057,0.017711931943651758 +6.0,0.02002158323181113,0.02019462550149604,0.011195969967589819,0.022348664887473,0.022262779372211397 +7.0,0.025127416528746203,0.025400572273952007,0.014087824481532864,0.028107199678235918,0.02797162021400105 +8.0,0.0315103938374857,0.03194114344476364,0.017724337286238952,0.03534046212793969,0.03512665200704765 +9.0,0.03947581013823077,0.040154209208400245,0.022295928433737136,0.044420892444668104,0.044084138042602035 +10.0,0.049393985592843845,0.05046071565802713,0.028040940629869258,0.055811993139768964,0.0552824355011896 +11.0,0.06170967643915826,0.0633837099689487,0.03525725553198444,0.07008888995333985,0.0692577754750065 +12.0,0.0769502860361166,0.07957092682607385,0.04431647029951141,0.08796257073317358,0.08666134967423425 +13.0,0.09573105092136092,0.0998211434335674,0.05568105080659391,0.11030790336846401,0.10827678933608055 +14.0,0.11875450818829247,0.12511422236562836,0.06992485602776169,0.13819516297913534,0.13503645793865676 +15.0,0.14680056444078776,0.1566442653623848,0.08775733805059951,0.17292418580683266,0.1680341158620806 +16.0,0.18070258576377174,0.19585451834583373,0.11005152164455634,0.21605933241156736,0.20853051118575192 +17.0,0.22130451408346616,0.24447152199498445,0.13787549850403957,0.26946211915160523,0.2579474779472346 +18.0,0.26939472678854465,0.3045344507753753,0.17252656142846565,0.3353166538092753,0.31784551311517 +19.0,0.3256150019555273,0.3784136758834648,0.21556617256790578,0.41614100494016715,0.38988005918955787 +20.0,0.3903482286678335,0.4688105525777229,0.2688526404418522,0.5147756853853035,0.47573343096639775 +21.0,0.4635964354662514,0.578728779073748,0.3345666611396269,0.6343391981725063,0.577022932441223 +22.0,0.5448699198657065,0.7114072599088519,0.415222874482676,0.7781410805176758,0.6951911159367629 +23.0,0.6331155105059729,0.8702063713693562,0.5136586334498267,0.949546269308418,0.8313903238477779 +24.0,0.7267127428215634,1.0584449667589202,0.6329899409936198,1.1517917856752415,0.9863786311250137 +25.0,0.8235572044540542,1.279194773017329,0.7765249721523131,1.3877675181776066,1.160445692544041 +26.0,0.9212304756826651,1.535050886559065,0.9476289515595221,1.6597852620892968,1.3533832196252402 +27.0,1.0172315400901657,1.8279087143109145,1.1495412900125348,1.9693701773560432,1.5645062768096216 +28.0,1.1092250066402953,2.158784275832778,1.385156635171699,2.3171116947925556,1.7927209139328206 +29.0,1.1952558858426987,2.5277120709526093,1.656793870330709,2.702603452084613,2.0366245350378795 +30.0,1.273891696882788,2.9337415407944656,1.9659871493886203,3.1244848484421452,2.294620748891627 diff --git a/data/sic_comparison.csv b/data/sic_comparison.csv new file mode 100644 index 0000000..952c2f4 --- /dev/null +++ b/data/sic_comparison.csv @@ -0,0 +1,8 @@ +snr_db,edma,oma,genie,sic +0,0.05294439072634817,0.04744227546406637,0.05379261851424639,0.05389018816321296 +5,0.07594817329319892,0.06247051502646607,0.07880139316120491,0.07881668500992306 +10,0.12217900552930536,0.09554048483563253,0.12869653017833063,0.12780141614005225 +15,0.20186260353988977,0.15862477653551696,0.21655865548824185,0.210747933448543 +20,0.3172937611389947,0.2656884406371917,0.3525261215672996,0.32788756860888746 +25,0.44561602112223075,0.4226203227111401,0.5255728405808201,0.45184034211813306 +30,0.5477712579317904,0.6069297656726085,0.6947138682844006,0.542792955511582 diff --git a/data/theorem_check.csv b/data/theorem_check.csv new file mode 100644 index 0000000..dcc48b1 --- /dev/null +++ b/data/theorem_check.csv @@ -0,0 +1,5 @@ +beta,user1_mc_over_theory,user2_mc_over_theory,max_dev_pct +0.0,1.0,1.0,0.0 +0.311,1.0007005180677906,0.9999999999999998,0.07005180677905898 +0.5,0.9964527822308402,0.9999999999999998,0.3547217769159783 +0.7,0.9981870510670983,1.0,0.18129489329017368 diff --git a/fig/block_diagram.pdf b/fig/block_diagram.pdf new file mode 100644 index 0000000..195519a Binary files /dev/null and b/fig/block_diagram.pdf differ diff --git a/fig/fig_bertvit_merged.pdf b/fig/fig_bertvit_merged.pdf new file mode 100644 index 0000000..98778b8 Binary files /dev/null and b/fig/fig_bertvit_merged.pdf differ diff --git a/fig/fig_beta_sweep_corrected.pdf b/fig/fig_beta_sweep_corrected.pdf new file mode 100644 index 0000000..5950ea5 Binary files /dev/null and b/fig/fig_beta_sweep_corrected.pdf differ diff --git a/fig/fig_floor.pdf b/fig/fig_floor.pdf new file mode 100644 index 0000000..e895b51 Binary files /dev/null and b/fig/fig_floor.pdf differ diff --git a/fig/fig_multiuser_corrected.pdf b/fig/fig_multiuser_corrected.pdf new file mode 100644 index 0000000..bc3b7b6 Binary files /dev/null and b/fig/fig_multiuser_corrected.pdf differ diff --git a/fig/fig_rate_corrected.pdf b/fig/fig_rate_corrected.pdf new file mode 100644 index 0000000..3e0f32e Binary files /dev/null and b/fig/fig_rate_corrected.pdf differ diff --git a/fig/fig_sic.pdf b/fig/fig_sic.pdf new file mode 100644 index 0000000..68c6f52 Binary files /dev/null and b/fig/fig_sic.pdf differ