Dilation-Matched Metropolized Dynamics / dilation_experiment.py

Mechanism confirmed, baseline not beaten

Raw ⬇ ZIP
 1import math
 2import numpy as np
 3
 4SEED = 2517
 5A, B, KAPPA = 0.8, 3.0, 0.35
 6XMIN = 0.20
 7
 8def phi(x): return np.sin(2*np.pi*x)
 9def phip(x): return 2*np.pi*np.cos(2*np.pi*x)
10def W(x, n):
11    x=np.asarray(x,float); y=np.zeros_like(x)
12    for j in range(n+1): y += A**j*phi(B**j*x)
13    return y
14def Wprime(x,n):
15    x=np.asarray(x,float); y=np.zeros_like(x)
16    for j in range(n+1): y += (A*B)**j*phip(B**j*x)
17    return y
18def dq_direct(x,n): return (W(x,n)-A*W(B*x,n))/x
19def dq_closed(x,n): return (phi(x)-A**(n+1)*phi(B**(n+1)*x))/x
20def V(x): return .5*(x-1.5)**2
21def Vp(x): return x-1.5
22def U(x,n): return V(x)+KAPPA*W(x,n)
23def force(x,n,kind):
24    return Vp(x) + KAPPA*(Wprime(x,n) if kind=='classical' else dq_closed(x,n))
25
26def proposal(x,p,n,kind,eps,L):
27    x0,p0=x,p
28    for _ in range(L):
29        p -= .5*eps*force(x,n,kind); x += eps*p
30        if x <= XMIN or not np.isfinite(x): return x0,p0,False
31        p -= .5*eps*force(x,n,kind)
32    dH=U(x,n)+.5*p*p-U(x0,n)-.5*p0*p0
33    ok=math.log(np.random.random()) < min(0.,-dH)
34    return (x,p,True) if ok else (x0,p0,False)
35
36def chain(n,kind,eps=.01,L=4,steps=5000,burn=1000):
37    x=1.5; xs=[]; accepted=0; valid=0
38    for t in range(steps):
39        p=np.random.normal(); xn,pn,ok=proposal(x,p,n,kind,eps,L)
40        valid += int(ok)
41        if ok: accepted += int(xn != x or pn != p); x=xn
42        if t>=burn: xs.append(x)
43    z=np.asarray(xs); zc=z-z.mean(); ac1=np.dot(zc[:-1],zc[1:])/max(1e-12,np.dot(zc,zc))
44    ess=len(z)*(1-ac1)/(1+ac1)
45    return float(accepted/steps),float(valid/steps),float(z.mean()),float(ess)
46
47def main():
48    np.random.seed(SEED)
49    grid=np.linspace(XMIN,4.0,4000)
50    print('CORE_MATH')
51    for n in [0,2,4,8,12,16]:
52        err=float(np.max(np.abs(dq_direct(grid,n)-dq_closed(grid,n))))
53        md=float(np.max(np.abs(dq_closed(grid,n))))
54        mp=float(np.max(np.abs(Wprime(grid,n))))
55        print(f'N={n:2d} closure_maxerr={err:.3e} quotient_max={md:.3f} derivative_max={mp:.3f}')
56    print('SCALING_PREDICTION')
57    # Predicted quotient envelope <= 2/xmin; derivative leading envelope grows (ab)^N.
58    for n in [2,4,6,8,10,12,14,16]:
59        q=float(np.percentile(np.abs(dq_closed(grid,n)),99))
60        d=float(np.percentile(np.abs(Wprime(grid,n)),99))
61        print(f'N={n:2d} q99={q:.3f} classical_dq99={d:.3f} ratio={d/max(q,1e-9):.2f}')
62    print('SAMPLER eps=.01 L=4, exact target MH correction')
63    for n in [4,8,12,16]:
64        np.random.seed(SEED+n)
65        c=chain(n,'classical'); np.random.seed(SEED+n)
66        q=chain(n,'quotient')
67        print(f'N={n:2d} classical acc={c[0]:.3f} mean={c[2]:.3f} ESS={c[3]:.1f} quotient acc={q[0]:.3f} mean={q[2]:.3f} ESS={q[3]:.1f}')
68    print('STEP_SWEEP_N16')
69    for eps in [.002,.005,.01,.02]:
70        np.random.seed(SEED+int(eps*10000)); c=chain(16,'classical',eps=eps,steps=2500,burn=500)
71        np.random.seed(SEED+int(eps*10000)); q=chain(16,'quotient',eps=eps,steps=2500,burn=500)
72        print(f'eps={eps:.3f} classical_acc={c[0]:.3f} quotient_acc={q[0]:.3f}')
73
74if __name__=='__main__': main()