| name | statistical-mechanics |
| description | Monte Carlo simulation for statistical mechanics — Ising model, Metropolis-Hastings, Wolff cluster algorithm, observables (magnetization, susceptibility, specific heat), finite-size scaling, and critical phenomena analysis. |
| category | physics |
| version | 1.0.0 |
| author | Synthetic Sciences |
| license | MIT |
| tags | ["Statistical Mechanics","Monte Carlo","Ising Model","Phase Transition","Critical Phenomena"] |
| dependencies | ["numpy>=1.24.0","scipy>=1.11.0","matplotlib>=3.7.0"] |
Statistical Mechanics (Monte Carlo)
Overview
Monte Carlo simulation of classical statistical mechanics models. Metropolis-Hastings and cluster algorithms for spin models, with tools for measuring observables, detecting phase transitions, and finite-size scaling analysis.
When to Use
- Simulating spin models (Ising, Potts, XY, Heisenberg)
- Finding critical temperatures and exponents
- Computing thermodynamic observables (energy, magnetization, susceptibility, specific heat)
- Finite-size scaling analysis
- Any equilibrium statistical mechanics simulation
Core Workflows
1. 2D Ising Model (Metropolis)
import numpy as np
import matplotlib.pyplot as plt
class IsingModel:
def __init__(self, L, T, J=1.0):
self.L = L
self.T = T
self.J = J
self.beta = 1.0 / T
self.spins = np.random.choice([-1, 1], size=(L, L))
def energy(self):
"""Total energy: H = -J Σ s_i s_j (nearest neighbors)."""
s = self.spins
E = -self.J * np.sum(
s * np.roll(s, 1, axis=0) +
s * np.roll(s, 1, axis=1)
)
return E
def magnetization(self):
return np.sum(self.spins)
def metropolis_step(self):
"""Single Metropolis sweep (L² spin flip attempts)."""
L = self.L
for _ in range(L * L):
i, j = np.random.randint(0, L, size=2)
s = self.spins[i, j]
nn = (.spins[(i+)%L, j] + .spins[(i-)%L, j] +
.spins[i, (j+)%L] + .spins[i, (j-)%L])
dE = * .J * s * nn
dE <= np.random.rand() < np.exp(-.beta * dE):
.spins[i, j] *= -
():
L = .L
p_add = - np.exp(- * .beta * .J)
i, j = np.random.randint(, L, size=)
cluster_spin = .spins[i, j]
stack = [(i, j)]
visited = ()
visited.add((i, j))
stack:
ci, cj = stack.pop()
di, dj [(,),(-,),(,),(,-)]:
ni, nj = (ci+di)%L, (cj+dj)%L
(ni, nj) visited .spins[ni, nj] == cluster_spin:
np.random.rand() < p_add:
visited.add((ni, nj))
stack.append((ni, nj))
ci, cj visited:
.spins[ci, cj] *= -
():
model = IsingModel(L, T)
_ (n_equil):
algorithm == :
model.wolff_step()
:
model.metropolis_step()
E_samples = []
M_samples = []
N = L * L
_ (n_measure):
algorithm == :
model.wolff_step()
:
model.metropolis_step()
E_samples.append(model.energy() / N)
M_samples.append((model.magnetization()) / N)
E = np.array(E_samples)
M = np.array(M_samples)
{
: np.mean(E),
: np.std(E) / np.sqrt((E)),
: np.mean(M),
: np.std(M) / np.sqrt((M)),
: N * np.var(E) / T**,
: N * np.var(M) / T,
: model.spins.copy(),
}
2. Phase Transition: Temperature Sweep
from scipy import constants as const
T_c_exact = 2 / np.log(1 + np.sqrt(2))
print(f"Exact T_c = {T_c_exact:.4f}")
L = 32
temperatures = np.linspace(1.5, 3.5, 30)
results = []
for T in temperatures:
res = simulate(L, T, n_equil=2000, n_measure=5000, algorithm='wolff')
results.append(res)
print(f"T={T:.3f}: <E>={res['E_mean']:.4f}, <|M|>={res['M_mean']:.4f}, "
f"C={res['C']:.4f}, χ={res['chi']:.4f}")
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
E_vals = [r['E_mean'] for r in results]
M_vals = [r['M_mean'] for r in results]
C_vals = [r['C'] for r in results]
chi_vals = [r['chi'] for r in results]
ax, vals, ylabel, title (
axes.flat,
[E_vals, M_vals, C_vals, chi_vals],
[, ,
, ],
[, , , ]
):
ax.plot(temperatures, vals, , markersize=)
ax.axvline(T_c_exact, color=, linestyle=, label=)
ax.set_xlabel()
ax.set_ylabel(ylabel)
ax.set_title(title)
ax.legend()
ax.grid(, alpha=)
plt.tight_layout()
plt.savefig(, dpi=, bbox_inches=)
3. Finite-Size Scaling
def finite_size_scaling(sizes, temperatures, n_equil=3000, n_measure=10000):
"""Run simulations for multiple L to extract critical exponents."""
all_results = {}
for L in sizes:
all_results[L] = []
for T in temperatures:
res = simulate(L, T, n_equil=n_equil, n_measure=n_measure, algorithm='wolff')
all_results[L].append(res)
print(f"L={L} done")
for L in sizes:
chi = [r['chi'] for r in all_results[L]]
T_peak = temperatures[np.argmax(chi)]
print(f"L={L:3d}: χ peak at T = {T_peak:.3f}")
return all_results
4. Autocorrelation and Error Estimation
def autocorrelation_time(data):
"""Estimate integrated autocorrelation time."""
n = len(data)
mean = np.mean(data)
var = np.var(data)
if var == 0:
return 1.0
acf = np.correlate(data - mean, data - mean, mode='full')[n-1:]
acf /= acf[0]
tau = 0.5
for k in range(1, n // 4):
if acf[k] < 0:
break
tau += acf[k]
return tau
Critical Exponents (2D Ising, Exact)
| Exponent | Symbol | Value | Relation |
|---|
| Specific heat | α | 0 (log) | C ~ |
| Order parameter | β | 1/8 | M ~ |
| Susceptibility | γ | 7/4 | χ ~ |
| Correlation length | ν | 1 | ξ ~ |
| Critical isotherm | δ | 15 | M ~ H^(1/δ) at T_c |
Troubleshooting
| Symptom | Fix |
|---|
| Very long equilibration | Use Wolff algorithm near T_c (no critical slowing) |
| Noisy observables | Increase measurement sweeps, check autocorrelation time |
| Wrong T_c | Finite-size effect — run larger L or use scaling |
| Magnetization doesn't go to 0 | Take |
| Metropolis too slow near T_c | Switch to Wolff cluster algorithm |