|
576 | 576 | // the live split-R̂ falls past 1.1 (coral→green) as they merge — or, in |
577 | 577 | // "bad" mode, stay trapped in three separate modes with R̂ pinned high. |
578 | 578 | // |
579 | | - // The convergence STORY has to be visible: chains start well separated with |
580 | | - // a tight wiggle (three distinct threads), then relax toward one shared |
581 | | - // stationary Normal(0,1) — mean pulled to 0 while the wiggle grows to full |
582 | | - // scale — merging within roughly the first third of the window, then mixing |
583 | | - // indistinguishably. That relaxation-to-a-shared-target is exactly what an |
584 | | - // AR(1)/Langevin move toward N(0,1) does in expectation (mean decays |
585 | | - // geometrically, variance settles), so we drive the mean-collapse and the |
586 | | - // mixing wiggle with separate rates: a clean fall, a clean green flip. |
| 579 | + // The convergence STORY has to be visible, which means the vertical |
| 580 | + // ENVELOPE must shrink: three thin threads far apart (dark space between |
| 581 | + // them), a held separated phase, then a crisp funnel onto one shared |
| 582 | + // stationary Normal(0,1) band. Over-dispersion only reads if the start |
| 583 | + // separation is several stationary SDs and the warmup wiggle is tight — |
| 584 | + // otherwise "before" and "after" span the same pixels and nothing |
| 585 | + // collapses. That relaxation-to-a-shared-target is what an AR(1)/Langevin |
| 586 | + // move toward N(0,1) does in expectation (mean decays, variance settles); |
| 587 | + // we drive mean-collapse and wiggle-growth on one eased schedule. |
587 | 588 | // |
588 | 589 | // The readout R̂ is real split-R̂ (same rhatCore as everywhere) computed on |
589 | 590 | // the recent (post-warmup) draws — a trailing window — so it falls to ~1.00 |
|
596 | 597 | var mode = (root.getAttribute("data-mode") || "good").toLowerCase(); |
597 | 598 | var GOOD = mode !== "bad"; |
598 | 599 | var NCH = 3, MAXN = 140; |
599 | | - var MIXN = Math.round(MAXN / 3); // over-dispersed chains merge by ~here |
| 600 | + var SEPN = Math.round(MAXN * 0.22); // fully separated threads until here… |
| 601 | + var MIXN = Math.round(MAXN * 0.42); // …then funnel; fully merged by here |
600 | 602 | var NPHI = 0.2; // mixing autocorrelation (low → clean R̂) |
601 | | - var SD0 = 0.65, SD1 = 1.0; // wiggle grows tight→full as chains merge |
| 603 | + var SD0 = 0.32, SD1 = 1.0; // thin warmup threads → full stationary band |
602 | 604 | var DWIN = 38; // trailing window for the live (post-warmup) R̂ |
603 | 605 | var BPHI = 0.82; // bad-mode within-mode autocorrelation |
604 | 606 |
|
605 | | - // smoothstep-eased schedules for the "good" collapse. |
| 607 | + // Hold separated, then smoothstep-funnel onto the shared band. |
606 | 608 | function ease(u) { return u * u * (3 - 2 * u); } |
607 | | - function collapse(k) { return k >= MIXN ? 0 : 1 - ease(k / MIXN); } // 1→0 mean scale |
608 | | - function sdScale(k) { return k >= MIXN ? SD1 : SD0 + (SD1 - SD0) * ease(k / MIXN); } |
| 609 | + function phase01(k) { return k <= SEPN ? 0 : k >= MIXN ? 1 : (k - SEPN) / (MIXN - SEPN); } |
| 610 | + function collapse(k) { return 1 - ease(phase01(k)); } // 1→0 mean scale |
| 611 | + function sdScale(k) { return SD0 + (SD1 - SD0) * ease(phase01(k)); } |
609 | 612 |
|
610 | 613 | mount(root, { |
611 | 614 | height: 150, hz: 6, |
|
620 | 623 | S.eta = []; S.cur = []; |
621 | 624 | // good: dispersed starts, one shared Normal(0,1) target → chains mix. |
622 | 625 | // bad: chains trapped in three separate modes → split-R̂ stuck ≫ 1.1. |
623 | | - S.starts = GOOD ? [-2.2, 0.0, 2.2] : [-1.7, 0.1, 1.7]; |
| 626 | + S.starts = GOOD ? [-3.4, 0.0, 3.4] : [-1.7, 0.1, 1.7]; |
624 | 627 | S.centers = GOOD ? [0, 0, 0] : [-1.5, 0.0, 1.5]; |
625 | 628 | for (var i = 0; i < NCH; i++) { S.eta[i] = FV.randn(S.rng); S.cur[i] = S.starts[i]; } |
626 | 629 | } |
|
0 commit comments