162 lines
6.5 KiB
Python
162 lines
6.5 KiB
Python
"""E3: dynamic mobile environment -- time-varying share coefficients a_u(t)
|
|
from random-waypoint trajectories, with sparse affinity-pilot tracking.
|
|
|
|
Every K-th slot each user sends n_p orthogonal pilot embeddings (overhead
|
|
n_p/(K*batch) << 1); the tracker EWMA-smooths the triangulated observations.
|
|
|
|
Methods:
|
|
DR-genie : decomposition receiver with true a_u(t) (upper ref)
|
|
DR-tracked : DR with pilot-tracked a_hat(t) (proposed)
|
|
DR-static : DR designed for the time-averaged a (no adaptation)
|
|
SR / SC / LMMSE : baselines with true B(t)
|
|
|
|
Outputs: data/e3_timeseries.csv, data/e3_speed.csv
|
|
(figures come from replot_all.py only)
|
|
"""
|
|
import os
|
|
import numpy as np
|
|
from semantic_mac import (affinity_matrix, matched_filter, demux_sr, demux_sc,
|
|
demux_lmmse, demux_dr, sample_latents_isotropic,
|
|
mobility_trajectories, AffinityTracker,
|
|
pilot_affinity_obs, metrics,
|
|
oma_observe, demux_noma_genie)
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
FIG = os.path.join(HERE, "..", "fig")
|
|
DATA = os.path.join(HERE, "..", "data")
|
|
os.makedirs(FIG, exist_ok=True)
|
|
os.makedirs(DATA, exist_ok=True)
|
|
|
|
U, D, DC = 4, 64, 16
|
|
BATCH = 320
|
|
SNR_DB = 12
|
|
RHO = 10 ** (SNR_DB / 10)
|
|
T = 300
|
|
K_PILOT = 5 # pilot every K slots
|
|
N_PILOT = 64 # pilot embeddings per user per pilot slot
|
|
LAM = 0.3
|
|
METHODS = ["DR-genie", "DR-tracked", "DR-static", "SR", "SC", "LMMSE",
|
|
"OMA", "NOMA"]
|
|
MOB = dict(box=50.0, r_scene=32.0, a_max=0.95)
|
|
|
|
|
|
def run_trace(a_t, rng, collect_ts=False):
|
|
Vc = np.eye(D)[:, :DC]
|
|
a_bar = a_t.mean(axis=0)
|
|
tracker = AffinityTracker(U, lam=LAM, a_init=float(a_bar.mean()))
|
|
sers = {m: [] for m in METHODS}
|
|
coss = {m: [] for m in METHODS}
|
|
a_hat_log = []
|
|
for t in range(a_t.shape[0]):
|
|
a = a_t[t]
|
|
B = affinity_matrix(a)
|
|
z = sample_latents_isotropic(BATCH, U, D, DC, a, rng)
|
|
tilde, h = matched_filter(z, B, RHO, rng)
|
|
|
|
if t % K_PILOT == 0:
|
|
zp = sample_latents_isotropic(N_PILOT, U, D, DC, a, rng)
|
|
hp = np.clip(np.abs((rng.standard_normal(U) +
|
|
1j * rng.standard_normal(U)) / np.sqrt(2)),
|
|
0.2, None)
|
|
tracker.update(pilot_affinity_obs(zp, hp, RHO, rng))
|
|
a_hat = np.clip(tracker.a, 0.02, 0.95)
|
|
|
|
out = {}
|
|
out["DR-genie"] = demux_dr(tilde, B, h, RHO, a, Vc)
|
|
out["DR-tracked"] = demux_dr(tilde, affinity_matrix(a_hat), h, RHO,
|
|
a_hat, Vc)
|
|
out["DR-static"] = demux_dr(tilde, affinity_matrix(a_bar), h, RHO,
|
|
a_bar, Vc)
|
|
out["SR"] = demux_sr(tilde, B, h)
|
|
out["SC"] = demux_sc(tilde, h)
|
|
out["LMMSE"], _ = demux_lmmse(tilde, B, h, RHO)
|
|
out["OMA"] = oma_observe(z, h, RHO, rng)
|
|
out["NOMA"] = demux_noma_genie(z, h, RHO, rng)
|
|
|
|
a_hat_log.append(a_hat)
|
|
for m in METHODS:
|
|
c, _, s = metrics(out[m], z)
|
|
sers[m].append(s)
|
|
coss[m].append(c)
|
|
res = {m: (float(np.mean(coss[m])), float(np.mean(sers[m])))
|
|
for m in METHODS}
|
|
if collect_ts:
|
|
return res, sers, np.array(a_hat_log)
|
|
return res
|
|
|
|
|
|
def drive_through_profile(T, peaks=(80, 110, 140, 170), width=45.0,
|
|
a_lo=0.05, a_hi=0.9):
|
|
"""Scene pass-by: each user approaches the shared scene, dwells, leaves."""
|
|
t = np.arange(T)[:, None]
|
|
pk = np.asarray(peaks)[None, :]
|
|
return a_lo + (a_hi - a_lo) * np.exp(-(t - pk) ** 2 / (2 * width ** 2))
|
|
|
|
|
|
def main():
|
|
# --- time-series: scene pass-by, averaged over REPS_TS runs -------------
|
|
a_t = drive_through_profile(T)
|
|
REPS_TS = 5
|
|
sers_acc = None
|
|
means_acc = {m: [] for m in METHODS}
|
|
for rep_i in range(REPS_TS):
|
|
rng = np.random.default_rng(3 + rep_i)
|
|
res_i, sers_i, a_hat_i = run_trace(a_t, rng, collect_ts=True)
|
|
if rep_i == 0:
|
|
a_hat_log = a_hat_i
|
|
if sers_acc is None:
|
|
sers_acc = {m: np.array(sers_i[m], float) for m in METHODS}
|
|
else:
|
|
for m in METHODS:
|
|
sers_acc[m] += np.array(sers_i[m], float)
|
|
for m in METHODS:
|
|
means_acc[m].append(res_i[m])
|
|
print(f" ts rep {rep_i+1}/{REPS_TS} done")
|
|
sers = {m: (sers_acc[m] / REPS_TS).tolist() for m in METHODS}
|
|
res = {m: (float(np.mean([x[0] for x in means_acc[m]])),
|
|
float(np.mean([x[1] for x in means_acc[m]])))
|
|
for m in METHODS}
|
|
print("time-series means:", {m: f"cos={v[0]:.3f},ser={v[1]:.3f}"
|
|
for m, v in res.items()})
|
|
with open(os.path.join(DATA, "e3_timeseries.csv"), "w") as f:
|
|
f.write("t," + ",".join(f"a{u}" for u in range(U)) + ","
|
|
+ ",".join(f"ahat{u}" for u in range(U)) + ","
|
|
+ ",".join(f"{m}_ser" for m in METHODS) + "\n")
|
|
for t in range(T):
|
|
f.write(f"{t}," + ",".join(f"{a_t[t,u]:.4f}" for u in range(U))
|
|
+ "," + ",".join(f"{a_hat_log[t,u]:.4f}" for u in range(U))
|
|
+ "," + ",".join(f"{sers[m][t]:.4f}" for m in METHODS) + "\n")
|
|
|
|
# --- speed sweep (averaged over trajectory seeds) -----------------------
|
|
speeds = [0.5, 1.0, 2.0, 4.0, 8.0]
|
|
reps = 8
|
|
rows = []
|
|
for si, sp in enumerate(speeds):
|
|
acc = {m: [] for m in METHODS}
|
|
for rep in range(reps):
|
|
# disjoint seed blocks per speed point (no seed reuse across
|
|
# speeds)
|
|
rng_s = np.random.default_rng(1000 + 100 * si + rep)
|
|
a_tr = mobility_trajectories(U, T, sp, rng_s, **MOB)
|
|
r = run_trace(a_tr, rng_s)
|
|
for m in METHODS:
|
|
acc[m].append(r[m])
|
|
rows.append({m: (float(np.mean([x[0] for x in acc[m]])),
|
|
float(np.mean([x[1] for x in acc[m]])))
|
|
for m in METHODS})
|
|
print(f"speed={sp} " + " ".join(f"{m}:ser={rows[-1][m][1]:.3f}"
|
|
for m in METHODS))
|
|
with open(os.path.join(DATA, "e3_speed.csv"), "w") as f:
|
|
f.write("speed," + ",".join(f"{m}_cos,{m}_ser" for m in METHODS) + "\n")
|
|
for sp, r in zip(speeds, rows):
|
|
f.write(f"{sp}," + ",".join(f"{r[m][0]},{r[m][1]}"
|
|
for m in METHODS) + "\n")
|
|
|
|
# figures are produced only by the canonical replot_all.py (uniform
|
|
# geometry); experiment scripts write CSVs exclusively.
|
|
print("E3 done. Run replot_all.py to regenerate the figures.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|