%matplotlib inline
import numpy as np, matplotlib as mpl, scipy as sp, networkx as nx
from matplotlib import pylab, mlab, pyplot as plt
from matplotlib.pylab import plot, scatter, contour, xlabel, ylabel, title, legend
from matplotlib.animation import FuncAnimation
from tqdm.notebook import tqdm, trange
from numpy import sqrt, pi, exp, log, floor, ceil, sin, cos
from numpy import linspace, arange, empty, zeros, ones, empty_like, zeros_like, ones_like
from numpy.linalg import norm
plt.rcParams.update({
'image.cmap': 'coolwarm',
'animation.html': 'jshtml',
'animation.embed_limit': 40, # 40 mb
})
rng = np.random.default_rng()
(N1, N2) = (10, 10)
β = 10
G = nx.grid_2d_graph( N1, N2 )
pos = {(x,y):(y,-x) for x,y in G.nodes()}
n_reals = 1000
x0 = rng.choice( [-1, 1], size=(N1, N2, n_reals) )
nx.draw( G, pos=pos, node_color=x0[:, :, 0] )
_ = title( 'Uniformly random spin configuration' )
The functions mcmc
and logπu
have been stripped from this notebook. In order for the notebook to run, you will have to implement these functions yourself. Here are the proto-types and doc-strings:
def mcmc(x0, n_iters, β):
"""Perform an MCMC iteration
x0 -- initial spin configuartion (N1 × N2 × n_reals array)
n_iters -- number of iterations to perform
β -- inverse temperature
Returns: an N1×N2×n_reals array of spin configurations after iteration
"""
def logπu( x, β ):
"""Returns the log of the un-normalized Gibbs measure with inverse temperature β"""
Few notes:
Do not compute $\pi_u$, and then take it's logarithm for the function
logπu
. Doing this will get you overflow errors.DO NOT use the function
logπu
when computing the acceptance ratio in your implementation ofmcmc
. Since the proposed state and current state differ in only one spin, you can compute the acceptance ratio by examining the spin at just a few points. Do a pencil and paper calculation and figure out the exact formula. (If you use the functionlogπu
, then your code will be about 100 times slower (when $N = 10$). It gets worse as $N$ increases.)
Few things that are helpful during implementation:
v = tuple( rng.choice( G.nodes() ) )
Chooses one random node ofG
G.neighbors(v)
generates an iterable of neighbors of the nodev
Once you have implemented these functions, the rest of this notebook should run and produce the figures shown on the main page.
β = 1
y = x0.copy()
lπu = [logπu(y, β)]
iters_per_step = 10
for _ in trange( 500 ):
y = mcmc( y, iters_per_step, β )
lπu.append( logπu(y, β) )
lπu = np.array( lπu )
0%| | 0/500 [00:00<?, ?it/s]
iters = arange( lπu.shape[0] )*iters_per_step
μ = lπu.mean( axis=-1 )
plot( iters, μ, label=r'$E\log(\pi_u)$' )
σ = lπu.std( axis=-1 )
plt.fill_between( iters, μ - σ, μ + σ, alpha=.3, label='StdDev' )
for n in sorted( rng.integers( n_reals, size=3 ) ):
plot( iters, lπu[:, rng.integers(n_reals)], label=f'Trial {n}', alpha=.7 )
lπ1 = logπu( ones( (N1, N1) ), β )
plt.hlines( lπ1, iters[0], iters[-1], linestyles='--', label=r'Max $\log \pi_u$', alpha=.5 )
xlabel( '# Iterations' )
legend()
#plt.savefig( 'figures/ising-logpiu.svg' )
<matplotlib.legend.Legend at 0x799259b6dd00>
def M(y:np.ndarray):
"""Average magnetization"""
return y.mean( axis=(0,1) )
iters_per_step = 100
ββ = np.logspace( -2, 0, num=7 )
mag = np.empty( (100, len(ββ) ) )
for i, β in tqdm( enumerate( ββ), total=len(ββ) ):
y = x0.copy()
mag[0, i] = abs( M(y) ).mean()
for t in trange( 1, mag.shape[0], leave=False ):
y = mcmc( y, iters_per_step, β )
mag[t, i] = abs( M(y) ).mean()
0%| | 0/7 [00:00<?, ?it/s]
0%| | 0/99 [00:00<?, ?it/s]
0%| | 0/99 [00:00<?, ?it/s]
0%| | 0/99 [00:00<?, ?it/s]
0%| | 0/99 [00:00<?, ?it/s]
0%| | 0/99 [00:00<?, ?it/s]
0%| | 0/99 [00:00<?, ?it/s]
0%| | 0/99 [00:00<?, ?it/s]
tt = np.arange( mag.shape[0] ) * iters_per_step
for i, β in enumerate( ββ ):
plot( tt, mag[:, i], label=f'β={β:.2f}' )
plt.ylabel( 'Expected magnetization strength' )
plt.xlabel( '# MCMC iterations' )
plt.legend()
#plt.savefig( 'figures/ising-mag-transition.svg' )
<matplotlib.legend.Legend at 0x799259a47f50>
β=.5
iters_per_step = 10
y = np.zeros( (N1, N2, 100 ) )
yn = x0[:, :, 0:1].copy()
y[:, :, 0 ] = yn[:,:,0]
for n in trange( 1, y.shape[-1] ):
yn = mcmc( yn, iters_per_step, β )
y[:, :, n] = yn[:, :, 0]
0%| | 0/99 [00:00<?, ?it/s]
n = -1
nx.draw( G, pos=pos, node_color=y[:, :, n] )
fig, ax = plt.subplots()
def update( frame ):
ax.clear()
ax.set_title(f'Iteration {frame*iters_per_step}')
nx.draw( G, pos=pos, node_color=y[:, :, frame] )
ani = FuncAnimation( fig, update, frames=y.shape[-1] )
ani
#with open( 'figures/mcmc-ising.html.inc', 'w' ) as f: f.write( ani.to_jshtml() )