Ising Model

Statistical Mechanics

The Ising model is a mathematical model of ferromagnetism in statistical mechanics. It consists of discrete variables (spins) that can be in one of two states (+1 or -1) arranged on a lattice, with interactions between neighboring spins.

Hamiltonian

For a 2D square lattice, the Ising model Hamiltonian is:

where:

Metropolis Algorithm

The Metropolis algorithm is used to sample spin configurations according to the Boltzmann distribution. At each step:

  1. Select a random spin
  2. Calculate the energy change if the spin is flipped
  3. Accept the flip with probability where

The energy change for flipping spin is:

Phase Transition

The 2D Ising model exhibits a phase transition at the critical temperature . Below , the system is ferromagnetic (spontaneous magnetization), and above , it is paramagnetic (no net magnetization).

Binder Cumulant

The Binder cumulant is a useful quantity for identifying phase transitions:

where is the magnetization per spin and is the system size. At the critical temperature, the Binder cumulant becomes size-independent, making it useful for finite-size scaling analysis.

Python Implementation

The following code implements the 2D Ising model with Metropolis Monte Carlo sampling:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl

def set_publication_style():
    """Set publication-quality matplotlib style."""
    mpl.rcParams.update({
        'font.family': 'serif',
        'font.size': 12,
        'axes.labelsize': 14,
        'axes.titlesize': 16,
        'axes.linewidth': 1.2,
        'axes.labelpad': 8,
        'axes.titlepad': 10,
        'xtick.labelsize': 12,
        'ytick.labelsize': 12,
        'xtick.direction': 'in',
        'ytick.direction': 'in',
        'xtick.top': True,
        'ytick.right': True,
        'xtick.major.size': 6,
        'ytick.major.size': 6,
        'xtick.major.width': 1.2,
        'ytick.major.width': 1.2,
        'legend.fontsize': 12,
        'legend.frameon': False,
        'lines.linewidth': 2,
        'lines.markersize': 6,
        'figure.dpi': 100,
        'savefig.dpi': 300,
        'savefig.bbox': 'tight'
    })

set_publication_style()

def initialize_spins(N):
    """Initialize a random spin configuration."""
    return np.random.choice([-1, 1], size=(N, N))

def calculate_magnetization(spins):
    """Calculate total magnetization."""
    return np.sum(spins)

def calculate_energy(spins, J=1.0, h=0.0):
    """Calculate the energy of a spin configuration."""
    N = spins.shape[0]
    energy = 0.0
    
    # Nearest neighbor interactions (periodic boundary conditions)
    for i in range(N):
        for j in range(N):
            energy -= J * spins[i, j] * (
                spins[(i+1) % N, j] +  # Right neighbor
                spins[(i-1) % N, j] +  # Left neighbor
                spins[i, (j+1) % N] +  # Top neighbor
                spins[i, (j-1) % N]    # Bottom neighbor
            )
    
    # External field contribution
    energy -= h * np.sum(spins)
    
    return energy / 2  # Each pair counted twice

def metropolis_step(spins, beta, J=1.0, h=0.0):
    """Perform one Metropolis Monte Carlo step."""
    N = spins.shape[0]
    
    for _ in range(N * N):  # One sweep through all spins
        i, j = np.random.randint(0, N, size=2)
        
        # Calculate energy change if spin flips
        spin_flip_energy = 2 * spins[i, j] * (
            J * (spins[(i+1) % N, j] + spins[(i-1) % N, j] +
                 spins[i, (j+1) % N] + spins[i, (j-1) % N]) + h
        )
        
        # Metropolis acceptance criterion
        if spin_flip_energy <= 0 or np.random.rand() < np.exp(-spin_flip_energy * beta):
            spins[i, j] *= -1
    
    return spins

def simulate_ising(N, T, num_steps, thermalization_steps, J=1.0, h=0.0):
    """Simulate the Ising model and collect statistics."""
    spins = initialize_spins(N)
    beta = 1.0 / T
    
    magnetizations = []
    magnetizations_squared = []
    magnetizations_fourth = []
    
    for step in range(num_steps):
        spins = metropolis_step(spins, beta, J, h)
        
        if step >= thermalization_steps:
            m = calculate_magnetization(spins) / (N * N)  # Magnetization per spin
            magnetizations.append(m)
            magnetizations_squared.append(m**2)
            magnetizations_fourth.append(m**4)
    
    return (np.array(magnetizations),
            np.array(magnetizations_squared),
            np.array(magnetizations_fourth))

# Parameters
N = 20  # Lattice size (N x N)
temperatures = np.linspace(1.0, 3.5, 30)  # Temperature range
num_steps = 20000  # Total Monte Carlo steps
thermalization_steps = 5000  # Steps to discard for thermalization
J = 1.0  # Coupling constant
h = 0.0  # External field

# Arrays to store results
mean_magnetization = []
susceptibility = []
binder_cumulant = []

# Simulate at different temperatures
for T in temperatures:
    m, m2, m4 = simulate_ising(N, T, num_steps, thermalization_steps, J, h)
    
    mean_m = np.mean(m)
    mean_m2 = np.mean(m2)
    mean_m4 = np.mean(m4)
    
    # Susceptibility: chi = (1/T) * (<m^2> - <m>^2)
    chi = (mean_m2 - mean_m**2) / T
    
    # Binder cumulant
    U = 1 - mean_m4 / (3 * mean_m2**2) if mean_m2 > 0 else 0
    
    mean_magnetization.append(np.abs(mean_m))  # Use absolute value for magnetization
    susceptibility.append(chi)
    binder_cumulant.append(U)

# Plotting
fig, axes = plt.subplots(1, 3, figsize=(15, 5))

# Magnetization vs Temperature
axes[0].plot(temperatures, mean_magnetization, 'o-', linewidth=2, markersize=4)
axes[0].axvline(x=2.269, color='r', linestyle='--', label='T_c ≈ 2.269')
axes[0].set_xlabel('Temperature (T)')
axes[0].set_ylabel('|Magnetization| per Spin')
axes[0].set_title('Magnetization vs Temperature')
axes[0].legend()
axes[0].grid(True)

# Susceptibility vs Temperature
axes[1].plot(temperatures, susceptibility, 's-', linewidth=2, markersize=4, color='green')
axes[1].axvline(x=2.269, color='r', linestyle='--', label='T_c ≈ 2.269')
axes[1].set_xlabel('Temperature (T)')
axes[1].set_ylabel('Susceptibility (χ)')
axes[1].set_title('Susceptibility vs Temperature')
axes[1].legend()
axes[1].grid(True)

# Binder Cumulant vs Temperature
axes[2].plot(temperatures, binder_cumulant, '^-', linewidth=2, markersize=4, color='purple')
axes[2].axvline(x=2.269, color='r', linestyle='--', label='T_c ≈ 2.269')
axes[2].set_xlabel('Temperature (T)')
axes[2].set_ylabel('Binder Cumulant (U)')
axes[2].set_title('Binder Cumulant vs Temperature')
axes[2].legend()
axes[2].grid(True)

plt.tight_layout()
plt.savefig('figures/ising_model_phase_transition.png', dpi=300, bbox_inches='tight')
plt.show()

print(f"Critical temperature (exact): T_c = 2.269 J/k_B")
print(f"Peak susceptibility at T ≈ {temperatures[np.argmax(susceptibility)]:.3f}")

Visualization

The following plot shows the phase transition behavior of the Ising model:

Figure pending — running the script above produces figures/ising_model_phase_transition.png.

Key Features

Physical Significance

The Ising model is one of the simplest models that exhibits a phase transition. It has applications in:

Critical Exponents

Near the critical temperature, various quantities exhibit power-law behavior: