Ising Model (Computation, pt. 2)
 Ising Model (Computation, pt. 2) 
 A post about the computation of the Ising Model.
Ising Model Code
Code inspired by this post by @Jake VanderPlas which compares Python against cPython.
1
2
3
4
5
6
7
8
import numpy as np
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from IPython.display import display, Markdown
from typing import List
np.random.seed(1)
MCMC Implementations
Below I copy+pasted the functions directly from @Jake VanderPlas blog post. Looking back to the update equations for MH, MCMC:
\[A(x' | x) = \min{\big[ 1, \exp{-2\beta x_n \sum_m J_{n,m} x_m + h} \big ]}\]We recognize the original blog post’s update as a MH, MCMC step.
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
# Functions from Jake VanderPlas's blog post.
def random_spin_field(N, M):
    return np.random.choice([-1, 1], size=(N, M))
# Add an `update_fn` arg.
def ising_step(field, update_fn, beta=0.4):
    N, M = field.shape
    for n_offset in range(2):
        for m_offset in range(2):
            for n in range(n_offset, N, 2):
                for m in range(m_offset, M, 2):
                    update_fn(field, n, m, beta)
    return field
def _ising_update(field, n, m, beta):
    total = 0
    N, M = field.shape
    for i in range(n - 1, n + 2):
        for j in range(m - 1, m + 2):
            if i == n and j == m:
                continue
            total += field[i % N, j % M]
    dE = 2 * field[n, m] * total
    if dE <= 0:
        field[n, m] *= -1
    elif np.exp(-dE * beta) > np.random.rand():
        field[n, m] *= -1
We can also add in the Gibbs Sampling step:
\[P(x_n = 1 | \{x_i\}_{i \neq n}) = \sigma(2\beta\sum_{m}J_{n, m}x_m + h)\]1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Also make a random stepping Ising update.
def sigmoid(x: float) -> float:
    # 700 seems to be the magic number where numpy will overflow.
    # In this case, analytically handle the probability
    if -x > 700:
        return 0.0
    return float(1 / (1 + np.exp(-x)))
def _ising_update_gibbs(field: np.ndarray, n: int, m: int, beta: float) -> None:
    total = 0
    N, M = field.shape
    for i in range(n - 1, n + 2):
        for j in range(m - 1, m + 2):
            if i == n and j == m:
                continue
            total += field[i % N, j % M]
    dE = 2 * beta * total
    if sigmoid(dE) > np.random.rand():
        field[n, m] = 1
    else:
        field[n, m] = -1
Plotting Helpers
A couple helper functions so that we can plot to markdown.
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
# Plotting helper functions.
def render_plotly_html(fig: go.Figure) -> None:
    """Display a Plotly figure in markdown with HTML."""
    # Ensure frame is square
    display(
        Markdown(
            fig.to_html(
                include_plotlyjs="cdn",
            )
        )
    )
def _grid_to_long(grid: np.ndarray) -> np.ndarray:
    """Convert a N x M matrix into a (N x M, 2) long vector."""
    # x coordinate, y coordinate, spin of the coordinate.
    return np.vstack(
        [*map(lambda x: x.flatten(), np.indices(grid.shape)), grid.flatten()]
    ).T
def grids_to_long(grids: List[np.ndarray]) -> pd.DataFrame:
    """Convert a n-list of grid matrices into a long-format DataFrame.
    Args:
        grids: List of grid matrices ordered by time.
    Returns:
        DataFrame with each N x M matrix converted to a (N x M, 2) section.
        Each section is indexed by the order in `grids`.
    """
    t = np.array([[i] * np.prod(grid.shape) for i, grid in enumerate(grids)])
    df = pd.DataFrame(np.concatenate([_grid_to_long(i) for i in grids]))
    df["t"] = t.flatten()
    df.columns = ["x", "y", "spin", "t"]
    return df
Sampling
Now we can sample both MH MCMC and Gibbs Sampling and compare the results.
1
2
3
4
5
N, M = 40, 40
STEPS = 500
BETA = 0.25
# Start from the same random initial configuration.
images = [random_spin_field(N, M)]
1
2
3
4
5
mh_images = images.copy()
for i in range(STEPS):
    mh_images.append(ising_step(mh_images[-1].copy(), beta=BETA, update_fn=_ising_update))
mh_steps = grids_to_long(mh_images)
mh_steps["sampler"] = "MH"
1
2
3
4
5
gibbs_images = images.copy()
for i in range(STEPS):
    gibbs_images.append(ising_step(gibbs_images[-1].copy(), beta=BETA, update_fn=_ising_update_gibbs))
gibbs_steps = grids_to_long(gibbs_images)
gibbs_steps["sampler"] = "Gibbs"
Plots
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Plot the simulation as a time lapse in Plotly.
fig = px.scatter(
    mh_steps,
    x="x",
    y="y",
    color="spin",
    color_continuous_scale="thermal",
    animation_frame="t",
    hover_name="t",
)
fig.update(layout_coloraxis_showscale=False)
# Ensure frame is square
fig.update_yaxes(
    scaleanchor="x",
    scaleratio=1,
)
render_plotly_html(fig)
