Skip to content

Commit 5e40240

Browse files
P6: document LTS/LTSLA non-comparability in README, add BLER accounting + diagnostic
README.md: add a compact P6-vs-P4-LTS section (algorithm-structure diff, feedback-path diff, experimental-axis diff) cross-referenced from P4 Investigation 3's LTS bullet, so the non-comparability documented in P6_reaudit_status.md SS3 is visible from the reader-facing doc, not just the status doc. docs/index.html has the same gap (mentions LTS and LTSLA in separate cards with no relationship noted) but was left unedited per scope -- flagged for a future pass. Adds a mean-BLER column to P6RLRunner's campaign CSV (additive only: the per-TTI BLER value was already computed for the ACK/NACK draw in both _run_olla and _run_rl, just never accumulated or returned). Verified purely additive via full test suite (235 passed) and a sanity-check re-run of an already-committed cell (seed=42, 30km/h, CDL-A, LTSLA) matching the 81c76bb dataset to within floating-point noise. Diagnostic re-run (same 5 seeds, N_TTIS=30000, full 6-config grid) finds two distinct failure modes behind the 81c76bb throughput results: RLLA's BLER runs at 0.13-0.17x the 10% target at every config (over-conservative MCS, not a reliability problem); LTSLA's BLER matches OLLA's at 3 km/h (a genuine efficiency win, matching its positive gain there) but runs at 1.57-2.07x target at 30/120 km/h (aggressive MCS failing to track faster fading, matching its negative gain there). OLLA itself regulates to 0.99-1.01x target throughout, confirming it as a valid baseline.
1 parent 81c76bb commit 5e40240

10 files changed

Lines changed: 346 additions & 47 deletions

README.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ investigated further.
333333
**Investigation 3 — closed-loop controllers.** Benchmarks four closed-loop learned controllers against OLLA under matched conditions across 6 feedback-impairment conditions (idealized, quantization, report period, noise, delay, ACK/NACK error) to test robustness under realistic feedback, not just idealized performance — a decomposition none of the four cited papers tested, so large divergence from each paper's own headline number is expected (see the algorithms doc for why, per arm):
334334

335335
- **SALAD** (Wiesmayr et al., arXiv:2510.05784) — tracks a running SINR estimate nudged by ACK/NACK "surprise" (a cross-entropy gradient step), with a probing mechanism for fast recovery from underestimation. Two modes: **c1c-agn** ignores CQI entirely after the first slot; **c1c-cqi** treats its estimate as a correction on top of each fresh CQI report.
336-
- **LTS** (Saxena, Tullberg, Jaldén, IEEE TWC 2022 / arXiv:2010.08651) — **c1b**: tracks a full Bayesian probability distribution over SINR (not a point estimate), Doppler-relaxed every TTI, with a pessimistic point estimate for MCS selection.
336+
- **LTS** (Saxena, Tullberg, Jaldén, IEEE TWC 2022 / arXiv:2010.08651) — **c1b**: tracks a full Bayesian probability distribution over SINR (not a point estimate), Doppler-relaxed every TTI, with a pessimistic point estimate for MCS selection. Not the same algorithm as P6's similarly-named LTSLA, and not directly comparable to it — see [P6 vs P4's LTS](#p6-vs-p4s-lts-not-the-same-algorithm-not-directly-comparable) below P6's section.
337337
- **BayesLA** (Saxena & Jaldén, SPAWC 2020) — **c1**: Thompson-sampled Beta posteriors over per-(MCS, CQI) success probability, feeding a throughput-maximizing LP under the BLER constraint.
338338
- **PPO / LOLLA-style residual** (Wang et al., arXiv:2606.23110) — **c2**: a live-trained neural policy outputs a continuous SINR correction on top of the standard MCS table, with a Lagrangian dual variable auto-tuning the NACK penalty to the BLER target.
339339

@@ -486,6 +486,38 @@ python examples/run_p6_rl.py --mode campaign \
486486
--config examples/campaigns/p6_rl_link_adapt/rl_campaign.yaml --plot
487487
```
488488

489+
#### P6 vs P4's LTS: not the same algorithm, not directly comparable
490+
491+
P4 Investigation 3's **LTS** (`c1b`) and P6's **LTSLA** share a name lineage
492+
(both cite the Saxena/Tullberg/Jaldén paper) but are neither the same
493+
implementation nor comparable results:
494+
495+
- **Different mechanics.** P4's LTS maintains a discrete 184-bin PMF over a
496+
SINR grid, uses a pessimistic point estimate (`min(sample, E[PMF])`), a
497+
full-PMF Bayesian likelihood update, and a Gaussian-kernel relaxation
498+
applied every TTI (`projects/p4_ml_link_adapt/lts.py`). P6's LTSLA
499+
instead maintains a single Gaussian posterior, samples from it directly
500+
(Thompson sampling), and updates via a scalar Kalman-style gain with
501+
Doppler-based variance inflation (`projects/p6_rl_link_adapt/rl_core.py`).
502+
Different update math, not the same algorithm under different names.
503+
- **Feedback path — the substantive difference.** P4's LTS is routed
504+
through `feedback.py`'s impairment front end and sees degraded SINR
505+
under 5 of P4 Investigation 3's 6 conditions. P6's LTSLA (and its OLLA
506+
and RLLA baselines) never routes through any feedback-impairment model —
507+
every P6 arm always sees true, instantaneous `ue.current_sinr_db`. P6's
508+
results above, including the multi-seed headline, should be read as
509+
measured **under genie/unimpaired feedback** — this condition is not
510+
stated anywhere else in this section.
511+
- **Different experimental axis.** P4 Investigation 3 fixes mobility
512+
(30 km/h, CDL-A) and sweeps feedback-impairment knobs; P6 fixes feedback
513+
(idealized) and sweeps mobility/CDL. Neither is a superset or
514+
replication of the other.
515+
- **Conclusion:** the two projects' results are not directly comparable,
516+
and neither should be cited as validating or contradicting the other.
517+
518+
Full algorithm-line-by-line comparison and re-verification:
519+
[`docs/redesign/P6_reaudit_status.md`](docs/redesign/P6_reaudit_status.md) §3.
520+
489521
---
490522

491523
## 3GPP compliance

docs/redesign/P6_reaudit_status.md

Lines changed: 173 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -477,20 +477,21 @@ isolation study (unlike the P1–P4 RNG-decoupling audits).
477477

478478
### 7.2 STOP-condition check
479479

480-
- **Realized BLER vs. 10% target**: **cannot be directly verified from
481-
committed data.** `P6RLRunner`'s CSV schema (`FIELDNAMES` in `runner.py`)
482-
has never included a BLER column — not for the original campaign, the
483-
interim-corrected one, or this run. This is a pre-existing gap in the
484-
runner's own metrics collection, not something introduced or fixed here
485-
(adding BLER tracking would be a code change to `runner.py`, out of
486-
scope for this task). Noted plainly rather than silently treated as
487-
checked. As partial, indirect assurance: both `_best_mcs_for_sinr`
488-
(`rl_core.py`, used by `ThompsonSamplingLA`) and `EpsilonGreedyRLLA`'s
489-
own selection rule enforce a `target_bler=NRConstants.TARGET_BLER`
490-
feasibility constraint by construction, and OLLA (via P3's
491-
`LinkAdaptationManager`) is designed to converge to the same 10% target
492-
— a structural reason to expect BLER near target, not a substitute for
493-
a measurement.
480+
- **Realized BLER vs. 10% target**: **could not be directly verified from
481+
committed data at the time this section was drafted.** `P6RLRunner`'s CSV
482+
schema (`FIELDNAMES` in `runner.py`) had never included a BLER column —
483+
not for the original campaign, the interim-corrected one, or this run.
484+
This was a pre-existing gap in the runner's own metrics collection, not
485+
introduced by this task. **Update (§8, same document, later session):**
486+
a BLER column was subsequently added (additive-only change, sanity-check
487+
verified) and a diagnostic re-run covering this exact grid (5 seeds × 6
488+
configs × 3 arms, N_TTIS=30000) was completed — see §8 for the full
489+
table and mechanism finding. This resolves the check for P6's own grid;
490+
it does not retroactively add a BLER column to the `81c76bb` CSVs
491+
themselves (a separate, parallel diagnostic dataset was used instead),
492+
and it does not resolve the equivalent gap for any other project's
493+
committed data (P1–P5, P4's Investigations 2/3, etc.), which still has
494+
no BLER-vs-target verification performed as part of this series.
494495
- **Cross-seed consistency (sign-flip check)**: no bug-like inconsistency
495496
found. RLLA is negative in all 5 seeds × 6 configs (30/30 cells,
496497
−22.6% to −52.7%) — completely consistent direction. LTSLA is
@@ -617,3 +618,161 @@ Full per-row data: `results/P6_rl_campaign_results_seed{42,123,7,99,2024}_30ktti
617618
| Grid source | `examples/campaigns/p6_rl_link_adapt/rl_campaign.yaml` |
618619
| HEAD for this campaign | `4575a10b2284b0ef9ca84122f837051d06efa8a3` (reconfirmed unchanged) |
619620
| This section | Drafted 2026-07-18, same session as the campaign run |
621+
622+
## 8. BLER diagnostic — is RLLA/LTSLA's gain-vs-OLLA reliability-driven or something else?
623+
624+
Continues §7 (same commit lineage, `81c76bb`), separate working session.
625+
Goal: determine whether RLLA's −37.40% collapse and LTSLA's 30/120 km/h
626+
underperformance (§7.3–7.4) are BLER-driven (MCS selection pushing
627+
realized BLER far past the ~10% target) or something else (e.g.
628+
persistently over-conservative MCS with BLER well under target).
629+
630+
### 8.1 Schema addition — additive-only, verified
631+
632+
Per-TTI realized BLER was already computed in both simulation loops —
633+
`projects/p6_rl_link_adapt/runner.py`, `_run_olla` (`bler =
634+
NRConstants.sinr_to_bler(ue.current_sinr_db, mcs)`, previously line 77)
635+
and `_run_rl` (same call, previously line 107) — it was simply discarded
636+
after the `is_ack` draw, never accumulated or returned.
637+
638+
**Change made**: both functions now also accumulate `total_bler += bler`
639+
each scheduled transmission and return `(mean_tp, mean_bler)` instead of
640+
just `mean_tp`. `P6RLRunner._row` and `FIELDNAMES` gained one new column,
641+
`mean_bler`. `P6RLRunner.run()` was updated to unpack and pass through the
642+
new tuple. **No line inside either simulation loop that affects `mcs`,
643+
`is_ack`, `ctrl.update`/`lam.update`, scheduling, or RNG consumption order
644+
was touched** — the only addition is a running sum of a value that was
645+
already being computed. Two call sites outside `runner.py` that call
646+
`_run_olla`/`_run_rl` directly needed matching updates to unpack the new
647+
tuple (not simulation-logic changes, just consuming the new return shape):
648+
`examples/run_p6_rl.py`'s `run_demo`, and
649+
`tests/test_projects.py::TestRLLinkAdapt::test_controller_reproducible_same_seed`.
650+
Full test suite re-run after the change: **235 passed, 0 failed** (same
651+
count as before this change).
652+
653+
**Sanity-check reproduction** (seed=42, 30 km/h, CDL-A, LTSLA — an
654+
already-covered cell from the committed `81c76bb` dataset): re-running
655+
with the new code gave `system_tp_mbps=16.8471`, `gain_vs_olla_pct=-9.91`
656+
against the committed CSV's `16.8471` / `-9.91`
657+
(`results/P6_rl_campaign_results_seed42_30ktti.csv`, row
658+
`P6_ltsla,10,30,A,30000,16.8471,1.6847,-9.91`) — throughput matched to
659+
0.000034 Mbps and gain matched to 0.0019pp, both within floating-point/
660+
rounding noise from the CSV's own 4-decimal rounding. Confirms the schema
661+
addition did not alter simulation behavior.
662+
663+
### 8.2 Diagnostic re-run
664+
665+
Same 5 seeds as the committed campaign (`42, 123, 7, 99, 2024`),
666+
N_TTIS=30000 — directly comparable to `81c76bb`'s dataset, not a fresh
667+
independent sample. **Scope**: all 6 configs × all 3 arms (OLLA, RLLA,
668+
LTSLA). The task scoped LTSLA's required set to the 4 unfavorable configs
669+
(30/120 km/h × CDL-A/C) with the 2 favorable (3 km/h) configs optional;
670+
**both were included** — OLLA and RLLA already needed the full 6-config
671+
grid (RLLA was negative in all 30 §7 cells, no config excludable; OLLA is
672+
needed as the baseline at every config RLLA/LTSLA run at), so extending
673+
LTSLA to the same 6 configs cost no extra OLLA runs and gave a complete
674+
BLER picture of P6's entire committed grid for negligible extra time
675+
(~130s of the total 549.6s run).
676+
677+
Stored as `results/P6_bler_diagnostic_seed{42,123,7,99,2024}_30ktti.csv`
678+
(5 files, 19 lines each — 1 header + 18 rows) — a separate, clearly-named
679+
diagnostic dataset, **not merged into or overwriting** the `81c76bb`
680+
campaign CSVs.
681+
682+
### 8.3 Results — mean realized BLER per config (n=5 seeds)
683+
684+
| Speed | CDL | OLLA mean BLER | OLLA std | RLLA mean BLER | RLLA std | RLLA vs. 10% target | LTSLA mean BLER | LTSLA std | LTSLA vs. 10% target |
685+
|---|---|---|---|---|---|---|---|---|---|
686+
| 3 km/h | A | 0.0985 | 0.0017 | 0.0143 | 0.0020 | 0.14× | 0.1020 | 0.0083 | 1.02× |
687+
| 3 km/h | C | 0.0985 | 0.0045 | 0.0126 | 0.0022 | 0.13× | 0.0901 | 0.0118 | 0.90× |
688+
| 30 km/h | A | 0.1011 | 0.0010 | 0.0159 | 0.0020 | 0.16× | **0.1880** | 0.0049 | **1.88×** |
689+
| 30 km/h | C | 0.1011 | 0.0015 | 0.0136 | 0.0021 | 0.14× | 0.1566 | 0.0086 | 1.57× |
690+
| 120 km/h | A | 0.1004 | 0.0013 | 0.0173 | 0.0017 | 0.17× | **0.2073** | 0.0052 | **2.07×** |
691+
| 120 km/h | C | 0.1005 | 0.0014 | 0.0147 | 0.0025 | 0.15× | 0.1739 | 0.0074 | 1.74× |
692+
693+
(Full per-seed BLER values for every cell are in the committed diagnostic
694+
CSVs; not hand-transcribed here to avoid drift from the data.)
695+
696+
**OLLA**: BLER is 0.99×–1.01× of the 10% target at every config, every
697+
seed (std ≤0.0045) — confirms OLLA is regulating to its design target
698+
consistently, and validates it as a baseline for the comparisons below.
699+
700+
### 8.4 Mechanism finding — different failure mode per arm, stated per config as instructed
701+
702+
**RLLA: mechanism (b), overly conservative — BLER far under target at
703+
every config.** RLLA's realized BLER is 0.13×–0.17× of the 10% target
704+
(1.3%–1.7% absolute) at all 6 configs, all 5 seeds — no config comes
705+
close to the target, let alone exceeds it. This is not a reliability
706+
failure: RLLA is not decoding badly, it is choosing MCS indices far more
707+
robust than necessary, and the throughput deficit (§7.3's −37.40% mean)
708+
is fully consistent with spectral efficiency left on the table at a
709+
near-perfect success rate — not with an increased failure rate. This
710+
matches the data cleanly enough that no other explanation is needed for
711+
RLLA specifically. (Why the ε-greedy running-average estimator converges
712+
this conservatively is a separate question, already flagged in
713+
`results/README.md` as unresolved — not re-investigated here; this
714+
section only establishes that the deficit is BLER-driven-conservatism,
715+
not an unreliability problem.)
716+
717+
**LTSLA: two different regimes, matching the sign of its throughput gain
718+
in §7.3 exactly.**
719+
720+
- **3 km/h (both CDL types, favorable configs, +6.68%/+10.06% gain in
721+
§7.3): mechanism (c), on-target BLER.** LTSLA's realized BLER (0.0901–
722+
0.1020, i.e. 0.90×–1.02× of target) is statistically indistinguishable
723+
from OLLA's own (0.0985 at both). LTSLA achieves a real throughput
724+
*gain* here at essentially the *same* reliability level as OLLA — this
725+
is a genuine spectral-efficiency win (better MCS choice at matched
726+
BLER), not a reliability trade-off in either direction.
727+
- **30/120 km/h (all 4 configs, unfavorable, −2.51% to −10.98% gain in
728+
§7.3): mechanism (a), BLER far past target.** LTSLA's realized BLER is
729+
1.57×–2.07× of the 10% target (15.66%–20.73% absolute) at every one of
730+
these 4 configs, consistently across all 5 seeds (std ≤0.0086). This
731+
directly explains the throughput loss: MCS selection is too aggressive
732+
for the actual (faster-fading) channel, producing a NACK rate well
733+
above design target, and each NACK contributes zero throughput
734+
(`_tp_increment`'s `is_ack` gate). The severity **increases monotonically
735+
with speed** (30 km/h: 1.57×–1.88×; 120 km/h: 1.74×–2.07×) — consistent
736+
with the Doppler-adaptive forgetting factor (`rl_core.py`
737+
`_forgetting_factor`, already documented in §2) being insufficient to
738+
keep the posterior tracking the channel as fast fading gets faster,
739+
causing the point estimate to run stale/optimistic relative to the true,
740+
faster-moving SINR. This is consistent with, and adds a concrete
741+
reliability-side mechanism to, the regime-dependent pattern already
742+
established in §7.3 — not a new or separate finding about *whether*
743+
LTSLA underperforms at high speed, but *why*: an under-regulated BLER,
744+
not merely a suboptimal but still-reliable MCS choice.
745+
746+
**Per-arm summary**: RLLA underperforms via excess conservatism (BLER
747+
under target, mechanism b) at every config; LTSLA underperforms via excess
748+
aggression (BLER over target, mechanism a) specifically at 30/120 km/h,
749+
and matches OLLA's reliability while beating its throughput (mechanism c,
750+
a genuine win) at 3 km/h. No config for either arm shows "BLER on-target
751+
but throughput loss from something else" as the dominant story — mechanism
752+
(c) only describes LTSLA's *favorable* configs, where it is not a
753+
"loss" story at all.
754+
755+
### 8.5 Scope of what this resolves — explicitly partial
756+
757+
This diagnostic run resolves the BLER-vs-target check **for P6's own
758+
6-config × 3-arm × 5-seed grid only** — the same grid `81c76bb`'s
759+
headline campaign covers, via a **separate, parallel dataset**
760+
(`P6_bler_diagnostic_seed*_30ktti.csv`), not a retroactive column added to
761+
the `81c76bb` CSVs themselves (which still have no `mean_bler` column).
762+
It does **not** resolve the equivalent gap for:
763+
- Any other project's committed campaign data (P1–P5, P4 Investigations
764+
2/3) — none of those runners were touched, and none of their CSVs have a
765+
BLER-vs-target verification performed as part of this series.
766+
- Any future P6 run using a different grid, seed set, or N_TTIS than the
767+
one re-used here.
768+
769+
### 8.6 Provenance for this section
770+
771+
| Artifact | Location |
772+
|---|---|
773+
| Diagnostic CSVs (5, committed) | `results/P6_bler_diagnostic_seed{42,123,7,99,2024}_30ktti.csv` |
774+
| Schema change | `projects/p6_rl_link_adapt/runner.py` (`_run_olla`, `_run_rl`, `P6RLRunner._row`/`FIELDNAMES`/`run`) |
775+
| Consumer updates for the new return shape | `examples/run_p6_rl.py` (`run_demo`), `tests/test_projects.py` (`test_controller_reproducible_same_seed`) |
776+
| Test suite result after the change | 235 passed, 0 failed (`pytest tests/`) |
777+
| HEAD at start of this section | `81c76bb` (this document's own §7 commit) |
778+
| This section | Drafted 2026-07-18, same session as the diagnostic run |

examples/run_p6_rl.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,12 @@ def run_demo(seed: int, n_ues: int, speed: float, cdl: str,
4747
n_ttis: int, n_rb: int) -> None:
4848
print(f"P6 demo | n_ues={n_ues} speed={speed}km/h CDL-{cdl} "
4949
f"ttis={n_ttis}")
50-
olla = _run_olla(seed, n_ues, speed, n_ttis, n_rb, cdl)
51-
rlla = _run_rl("epsilon_greedy", seed, n_ues, speed, n_ttis, n_rb, cdl)
52-
lts = _run_rl("thompson", seed, n_ues, speed, n_ttis, n_rb, cdl)
53-
print(f" OLLA : {olla:.3f} Mbps")
54-
print(f" RLLA : {rlla:.3f} Mbps ({(rlla-olla)/olla*100:+.1f}%)")
55-
print(f" LTSLA : {lts:.3f} Mbps ({(lts-olla)/olla*100:+.1f}%)")
50+
olla, olla_bler = _run_olla(seed, n_ues, speed, n_ttis, n_rb, cdl)
51+
rlla, rlla_bler = _run_rl("epsilon_greedy", seed, n_ues, speed, n_ttis, n_rb, cdl)
52+
lts, lts_bler = _run_rl("thompson", seed, n_ues, speed, n_ttis, n_rb, cdl)
53+
print(f" OLLA : {olla:.3f} Mbps (BLER={olla_bler:.4f})")
54+
print(f" RLLA : {rlla:.3f} Mbps ({(rlla-olla)/olla*100:+.1f}%) (BLER={rlla_bler:.4f})")
55+
print(f" LTSLA : {lts:.3f} Mbps ({(lts-olla)/olla*100:+.1f}%) (BLER={lts_bler:.4f})")
5656

5757

5858
def main() -> None:

0 commit comments

Comments
 (0)