File size: 4,536 Bytes
31d1d63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import numpy as np, json
from scoobdoob.validation import msm_build as mb, tpt, bridge
from scoobdoob.validation import mb_ground_truth as gt
from scoobdoob.validation import mb_data as V
import make_mb_panels as MP

LAG, N, TL, ALPHA = MP.LAG_OPT, MP.N_OPT, MP.TICA_LAG, 1.5
CLUST_SEEDS = list(range(20))
PATH_SEEDS = [0, 1, 2]
NPATH = 2000
sad = gt.saddle_points()
S1 = sad[np.argmin(sad[:, 0])]
trajs = V.load_trajs(str(V.DEFAULT_DATA))

raw_m, fk_m, within = [], [], []
for cs in CLUST_SEEDS:
    model = mb.build_full_msm(
        trajs, trajs, lag=LAG, tica_lag=TL, k=V.K, n_comp=2, seed=cs, use_tica=True
    )
    A, B, _ = mb.q2_sign_endpoints(model)
    start, end = V.orient_endpoints(model, A, B)
    xy = model.state_xy
    _, q, _ = tpt.committor_agreement(model, start, end)
    rf = tpt.reactive_flux(model, start, end)
    thru = np.asarray(rf.gross_flux).sum(1)
    share = thru / thru.sum()
    on = share > 0.5 / model.n_active
    on[list(start)] = True
    on[list(end)] = True
    Axy = model.active_to_xy(start).mean(0)
    Bxy = model.active_to_xy(end).mean(0)
    land = np.vstack([Axy, Bxy, sad])
    lo = land.min(0) - 0.35
    hi = land.max(0) + 0.35
    on = on & (
        (xy[:, 0] >= lo[0])
        & (xy[:, 0] <= hi[0])
        & (xy[:, 1] >= lo[1])
        & (xy[:, 1] <= hi[1])
    )
    labels = np.concatenate(model.dtrajs)
    counts = np.bincount(labels, minlength=model.n_active)[: model.n_active].astype(
        float
    )
    rho = np.maximum(counts, 1) / counts.mean()
    react = (q > 0.05) & (q < 0.95)
    wfk = np.exp(-LAG * ALPHA * np.maximum(rho, np.percentile(rho[react], 25)))
    wfk[~on] = 0.0
    wraw = np.ones_like(wfk)
    wraw[~on] = 0.0
    n1 = np.linalg.norm(xy - S1, axis=1) < 0.25
    Kraw = bridge.doob_kernels(model.P, end, N, w=wraw)
    Kfk = bridge.doob_kernels(model.P, end, N, w=wfk)
    r_ps, f_ps = [], []
    for psd in PATH_SEEDS:
        pr = bridge.sample_bridge_paths(Kraw, start, n_paths=NPATH, seed=psd)
        pf = bridge.sample_bridge_paths(Kfk, start, n_paths=NPATH, seed=psd)
        r_ps.append(np.mean([any(n1[s] for s in p) for p in pr]) * 100)
        f_ps.append(np.mean([any(n1[s] for s in p) for p in pf]) * 100)
    raw_m.append(np.mean(r_ps))
    fk_m.append(np.mean(f_ps))
    within.append((np.std(r_ps), np.std(f_ps)))
    print(
        f"  [clust seed {cs:2d}] raw {raw_m[-1]:5.1f}%  fk {fk_m[-1]:5.1f}%  "
        f"diff {fk_m[-1] - raw_m[-1]:+5.1f}  (path-seed sd {within[-1][0]:.1f}/{within[-1][1]:.1f})"
    )

raw = np.array(raw_m)
fk = np.array(fk_m)
d = fk - raw
n = len(d)
print(
    f"\n--- variance decomposition (n={n} clustering seeds, {len(PATH_SEEDS)} path seeds, "
    f"{NPATH} paths each) ---"
)
wr = np.mean([w[0] for w in within])
wf = np.mean([w[1] for w in within])
print(f"between-clustering sd : raw {raw.std(ddof=1):.1f}   fk {fk.std(ddof=1):.1f}")
print(f"within  (path-sampling) sd : raw {wr:.1f}   fk {wf:.1f}")
print(
    f"-> clustering dominates by {fk.std(ddof=1) / max(wf, 1e-9):.0f}x; Monte-Carlo error is negligible"
)

print(f"\n--- paired comparison, FK vs raw ---")
print(f"raw  {raw.mean():.1f} +- {raw.std(ddof=1):.1f}")
print(f"fk   {fk.mean():.1f} +- {fk.std(ddof=1):.1f}")
se = d.std(ddof=1) / np.sqrt(n)
print(f"paired diff {d.mean():+.1f} (sd {d.std(ddof=1):.1f}, se {se:.1f})")
print(
    f"  min diff over seeds {d.min():+.1f}, max {d.max():+.1f}  "
    f"(sign consistent: {'yes' if (d > 0).all() else 'NO'})"
)
print(f"Cohen's d_z = {d.mean() / d.std(ddof=1):.2f}")
try:
    from scipy import stats

    t, pt = stats.ttest_rel(fk, raw)
    w, pw = stats.wilcoxon(fk, raw)
    ci = stats.t.interval(0.95, n - 1, loc=d.mean(), scale=se)
    print(f"paired t: t={t:.1f}, p={pt:.2e}")
    print(
        f"Wilcoxon signed-rank: W={w:.0f}, p={pw:.2e} "
        f"(floor for n={n} is {2 / 2**n:.1e})"
    )
    print(f"95% CI of the mean difference: [{ci[0]:.1f}, {ci[1]:.1f}]")
except ImportError:
    print("scipy unavailable; bootstrap only")
rng = np.random.default_rng(0)
bs = np.array([rng.choice(d, n, replace=True).mean() for _ in range(20000)])
print(
    f"bootstrap 95% CI of mean diff: [{np.percentile(bs, 2.5):.1f}, {np.percentile(bs, 97.5):.1f}]"
)
json.dump(
    {
        "raw": raw.tolist(),
        "fk": fk.tolist(),
        "clust_seeds": CLUST_SEEDS,
        "path_seeds": PATH_SEEDS,
        "n_paths": NPATH,
        "tica_lag": TL,
        "lag": LAG,
        "N": N,
        "alpha": ALPHA,
    },
    open("data/mb_stats.json", "w"),
    indent=2,
)
print("[done]")