Files
TMC/code/exp3_mobility.py
T

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()