Dual-Ensemble Latent Transition Model / dual_ensemble_experiment.py

Failed on benchmark

Raw ⬇ ZIP
 1import numpy as np
 2
 3SEED = 1149
 4
 5def stationary(T):
 6    w, v = np.linalg.eig(T.T)
 7    x = np.real(v[:, np.argmin(abs(w - 1))])
 8    if x.sum() < 0: x = -x
 9    x = np.maximum(x, 0); return x / x.sum()
10
11def make_chain():
12    # Eight states grouped into four adjacent coarse states.
13    pc = np.array([18., 12., 6., 1.]) / 37.
14    pi = np.repeat(pc / 2, 2)
15    T = np.zeros((8, 8))
16    for i in range(7):
17        # Equalize neighboring probability flows, making pi stationary.
18        T[i, i+1] = min(.22, .12 * min(1., pi[i+1]/pi[i]))
19        T[i+1, i] = min(.22, .12 * min(1., pi[i]/pi[i+1]))
20    T[np.diag_indices(8)] = 1 - T.sum(1)
21    Q = T.copy(); Q[6:8] = [.5, .5, 0, 0, 0, 0, 0, 0]
22    return T, Q, pi
23
24def coarse(T, pi, alpha=0.):
25    groups = [np.array([2*i, 2*i+1]) for i in range(4)]
26    den = np.array([pi[g].sum() for g in groups])
27    out = np.zeros((4, 4))
28    for i, gi in enumerate(groups):
29        for j, gj in enumerate(groups):
30            out[i,j] = (pi[gi,None] * T[np.ix_(gi,gj)]).sum() / max(den[i], 1e-15)
31    return out, den
32
33def hit_time(T, start_dist, sink=(6,7)):
34    keep = [i for i in range(8) if i not in sink]
35    h = np.linalg.solve(np.eye(len(keep))-T[np.ix_(keep,keep)], np.ones(len(keep)))
36    return sum(start_dist[i] * h[keep.index(i)] for i in keep if start_dist[i] > 0)
37
38def sample_counts(T, n, rg, alpha=.5):
39    s = stationary(T); starts = rg.choice(8, n, p=s)
40    ends = np.array([rg.choice(8, p=T[i]) for i in starts])
41    C = np.full((8,8), alpha); np.add.at(C, (starts, ends), 1)
42    return C / C.sum(1, keepdims=True)
43
44def main():
45    P, Q, pi = make_chain(); piq = stationary(Q)
46    _, pic = coarse(P, pi); _, piqc = coarse(Q, piq)
47    reset = np.array([.5,.5,0,0,0,0,0,0])
48    direct = hit_time(P, reset)
49    hill = 1/piqc[3] - 1
50    print('CORE_CHECK')
51    print('P_stationarity_error %.3e' % max(abs(pi@P-pi)))
52    print('Q_stationarity_error %.3e' % max(abs(piq@Q-piq)))
53    print('equilibrium_target', np.round(pic,6))
54    print('ness_target', np.round(piqc,6))
55    print('reset_process_MFPT direct %.6f hill %.6f abs_error %.3e' % (direct,hill,abs(direct-hill)))
56
57    print('PREDICTION_1 lag-invariant matched stationary error: lag, equilibrium, NESS')
58    for lag in [1,2,4,8,16,32]:
59        ep = max(abs(stationary(coarse(np.linalg.matrix_power(P,lag),pi)[0])-pic))
60        eq = max(abs(stationary(coarse(np.linalg.matrix_power(Q,lag),piq)[0])-piqc))
61        print(lag, '%.3e %.3e' % (ep,eq))
62
63    print('PREDICTION_2 reset-strength: mix, dual_MFPT_error, single_MFPT_error, sink_rate')
64    for mix in [0,.25,.5,.75,1.]:
65        reset = np.array([1-mix,mix,0,0,0,0,0,0])
66        q = P.copy(); q[6:8] = reset
67        sq = stationary(q); _, sc = coarse(q,sq)
68        truth = hit_time(P,reset); dual = 1/sc[3]-1
69        single = 1/pic[3]-1
70        print('%.2f %.6f %.6f %.6f' % (mix,abs(dual-truth),abs(single-truth),sc[3]))
71
72    print('PREDICTION_3 sample-size RMS NESS occupancy error')
73    ns=[400,1600,6400,25600]; vals=[]
74    for n in ns:
75        de=[]; se=[]
76        for r in range(30):
77            rg=np.random.default_rng(SEED+100000+n+r)
78            pe=sample_counts(P,n,rg); qe=sample_counts(Q,n,rg)
79            spe=stationary(pe); sqe=stationary(qe)
80            _, pce=coarse(pe,spe); _, qce=coarse(qe,sqe)
81            de.append(np.linalg.norm(qce-piqc)); se.append(np.linalg.norm(pce-piqc))
82        d=float(np.sqrt(np.mean(np.square(de)))); s=float(np.sqrt(np.mean(np.square(se))))
83        vals.append(d); print(n,'dual %.6f single %.6f'%(d,s))
84    print('dual_loglog_slope %.3f predicted -0.5' % np.polyfit(np.log(ns),np.log(vals),1)[0])
85
86if __name__ == '__main__': main()