Skip to content

Commit 3f7de84

Browse files
add some replication
1 parent 7f72222 commit 3f7de84

1 file changed

Lines changed: 95 additions & 0 deletions

File tree

tests/replicate_max_mh_coupling.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
"""
2+
This is to replicate the Figure 7 in Maximal Couplings of the Metropolis-Hastings.
3+
As discussed with authors, there was a mistake in their code so that P_{MR} works better than P_C.
4+
5+
"""
6+
7+
from functools import partial
8+
9+
import jax
10+
import jax.numpy as jnp
11+
import numpy as np
12+
import tqdm
13+
import matplotlib.pyplot as plt
14+
from coupled_rejection_sampling.mvn import reflection_maximal
15+
from coupled_rejection_sampling.reflected_coupled_mh import reflection_coupled_mh
16+
17+
DS = np.arange(1, 21, dtype=int)
18+
M = 1_000
19+
JAX_KEY = jax.random.PRNGKey(42)
20+
21+
22+
@partial(jax.jit, static_argnums=(0, 1))
23+
def experiment(D, kernel):
24+
25+
@jax.vmap
26+
def get_meeting_time(key):
27+
def cond(carry):
28+
return ~carry[-1]
29+
30+
def body(carry):
31+
curr_k, x, y, i, *_ = carry
32+
curr_k, op_k = jax.random.split(curr_k)
33+
x, y, coupled = kernel(op_k, x, y)
34+
return curr_k, x, y, i + 1, coupled
35+
36+
loop_key, init_key = jax.random.split(key, 2)
37+
x0, y0 = jax.random.normal(init_key, (2, D))
38+
39+
*_, coupling_time, _ = jax.lax.while_loop(cond, body, (loop_key, x0, y0, 0, False))
40+
return coupling_time
41+
42+
return get_meeting_time(jax.random.split(JAX_KEY, M))
43+
44+
45+
@jax.jit
46+
def status_quo_kernel(key, x, y, chol):
47+
proposal_key, acceptance_key = jax.random.split(key)
48+
x_prop, y_prop, coupled_prop = reflection_maximal(proposal_key, 1, x, y, chol)
49+
x_prop, y_prop, coupled_prop = x_prop[0], y_prop[0], coupled_prop[0]
50+
51+
log_alpha_x = -0.5 * jnp.sum(x_prop ** 2) + 0.5 * jnp.sum(x ** 2)
52+
log_alpha_y = -0.5 * jnp.sum(y_prop ** 2) + 0.5 * jnp.sum(y ** 2)
53+
54+
log_u = jnp.log(jax.random.uniform(acceptance_key))
55+
accept_x = log_u < log_alpha_x
56+
accept_y = log_u < log_alpha_y
57+
58+
x = jax.lax.select(accept_x, x_prop, x)
59+
y = jax.lax.select(accept_y, y_prop, y)
60+
61+
coupled = accept_x & accept_y & coupled_prop
62+
return x, y, coupled
63+
64+
65+
@jax.jit
66+
def reflected_mh_kernel(key, x, y, chol):
67+
target_logpdf = lambda z: -0.5 * jnp.sum(z ** 2)
68+
proposal = lambda k, z: z + chol @ jax.random.normal(k, z.shape)
69+
proposal_logpdf = lambda z, z_prime: 0. # Symmetric proposal, so doesn't matter
70+
step = reflection_coupled_mh(proposal, proposal_logpdf, target_logpdf)
71+
(x, _), (y, _), coupled = step(key, x, y)
72+
return x, y, coupled
73+
74+
75+
if __name__ == "__main__":
76+
status_quo_res = np.empty((len(DS), M))
77+
reflected_res = np.empty((len(DS), M))
78+
79+
for i, d in enumerate(tqdm.tqdm(DS)):
80+
proposal_chol = 2.38 * np.eye(d) / d ** 0.5
81+
status_quo_res[i] = experiment(d, partial(status_quo_kernel, chol=proposal_chol))
82+
reflected_res[i] = experiment(d, partial(reflected_mh_kernel, chol=proposal_chol))
83+
84+
plt.plot(DS, np.mean(status_quo_res, -1), label="$P_{SQ}$ with $Q_{MR}$", c="tab:blue")
85+
plt.fill_between(DS,
86+
np.percentile(status_quo_res, 5, -1),
87+
np.percentile(status_quo_res, 95, -1),
88+
color="tab:blue", alpha=0.5)
89+
plt.plot(DS, np.mean(reflected_res, -1), label="$P_{MR}$", c="tab:orange")
90+
plt.fill_between(DS,
91+
np.percentile(reflected_res, 5, -1),
92+
np.percentile(reflected_res, 95, -1),
93+
color="tab:orange", alpha=0.5)
94+
plt.legend()
95+
plt.show()

0 commit comments

Comments
 (0)