C.3
Exact diagonalisation and Monte Carlo
Two numerical routes into the many-body problem: brute-force the ground state of a Heisenberg chain with sparse matrices plus Lanczos, or replace integration by sampling with variational Monte Carlo — full code included, plus an honest confession about the sign problem.
Recommended first
After this section you should be able to
- Assemble a many-spin Hamiltonian as a sparse matrix via tensor products, and explain why the dimension explodes exponentially
- State the core ideas of the power method and Lanczos, and call eigsh for the ground state
- Write a minimal variational Monte Carlo program, understanding Metropolis sampling and the local energy
- Know what the sign problem is and why it blocks fermionic Monte Carlo
The single-particle problem was settled once and for all in C.1. The real difficulty is the many-body problem: the state space of spin-1/2 particles is -dimensional (A.1 tensor products: dimensions multiply). already means a trillion dimensions — you cannot even store one state vector. That is the “exponential wall” of quantum many-body physics, and also why quantum computing carries such hopes.
At the foot of the wall run two classic routes: exact diagonalisation (brute force, but only up to -odd) and Monte Carlo (sampling, which scales to large systems but has an Achilles heel). This section gives one minimal runnable example of each.
Route one: exact diagonalisation
Assembling the Hamiltonian: tensor products, translated literally
Take the one-dimensional antiferromagnetic Heisenberg chain — neighbouring spins coupled pairwise:
” on site ” is written, by the rules of A.1, as . The overwhelming majority of the matrix entries are zero, so sparse matrices (storing only the non-zeros) are mandatory.
Only the ground state needed: the power method and Lanczos
At dimensions you can still diagonalise the whole thing; beyond that you cannot. But usually we only want the lowest few states — and then full diagonalisation is unnecessary:
- Power method: multiply a random vector by over and over. Expanded in the eigenbasis, the eigencomponent with the largest modulus is amplified most on every multiplication, and it is the one left standing at the end. All it needs is the ability to “multiply a matrix by a vector” — never the inverse or a factorisation of the whole matrix.
- The Lanczos method is the power method’s clever cousin: instead of keeping only the last vector, it exploits the whole subspace spanned by the successive products (the Krylov space ) and diagonalises a small tridiagonal matrix inside it. Convergence is far faster — typically a few dozen matrix–vector products converge the ground-state energy to ten significant figures. scipy’s
eigshwraps algorithms of this family.
import numpy as np
from scipy.sparse import identity, kron, csr_matrix
from scipy.sparse.linalg import eigsh
# ---- The three operators of a single spin-1/2 (sparse matrices, units hbar = 1) ----
sx = csr_matrix(np.array([[0, 1], [1, 0]], dtype=float) / 2)
sy = csr_matrix(np.array([[0, -1j], [1j, 0]]) / 2)
sz = csr_matrix(np.array([[1, 0], [0, -1]], dtype=float) / 2)
one = identity(2, format='csr')
def site_op(op, i, N):
"""Place the single-site operator op on site i: 1 ⊗ … ⊗ op ⊗ … ⊗ 1"""
out = identity(1, format='csr')
for j in range(N):
out = kron(out, op if j == i else one, format='csr')
return out
def heisenberg(N, J=1.0, periodic=True):
"""Antiferromagnetic Heisenberg chain: H = J Σ S_i · S_{i+1}"""
dim = 2**N
H = csr_matrix((dim, dim), dtype=complex)
bonds = N if periodic else N - 1
for i in range(bonds):
j = (i + 1) % N
for s in (sx, sy, sz):
H = H + J * site_op(s, i, N) @ site_op(s, j, N)
return H
for N in [8, 10, 12]:
H = heisenberg(N)
# Lanczos: only the single lowest eigenvalue, which='SA' = smallest algebraic
E0 = eigsh(H, k=1, which='SA', return_eigenvectors=False)[0]
print(f"N = {N:2d} dim {2**N:5d} ground energy per site = {E0.real/N:.6f}")
# Bethe-ansatz exact result (N → ∞): e0 = 1/4 − ln2 ≈ −0.443147
print(f"thermodynamic-limit exact value e0 = {0.25 - np.log(2):.6f}")
Measured output:
N = 8 dim 256 ground energy per site = -0.456387
N = 10 dim 1024 ground energy per site = -0.451545
N = 12 dim 4096 ground energy per site = -0.448949
thermodynamic-limit exact value e0 = -0.443147
As grows, the ground-state energy per site approaches the exact Bethe-ansatz value monotonically — the remaining gap is finite-size effects, and extrapolating in matches it even more closely.
Route two: variational Monte Carlo
Change tack: instead of solving the equation, guess a parametrised wavefunction, estimate its energy by sampling, then tune the parameters to push the energy down. The theoretical licence is the variational principle: the energy expectation of any trial state is never below the true ground-state energy.
The key rewrite — the energy expectation becomes “an average sampled from ”:
is called the local energy — at a specific configuration , the ratio of the Hamiltonian’s action to the wavefunction itself. High-dimensional integrals are intractable, but sampling is tractable: the Metropolis algorithm decides acceptance using only the wavefunction ratio , so the normalisation constant is never needed — which is exactly why Monte Carlo scales to high dimensions.
Here is the smallest complete example that can be written down: harmonic oscillator + Gaussian trial wavefunction (local energy by hand: ):
import numpy as np
rng = np.random.default_rng(42)
def local_energy(x, a):
"""Local energy of the trial wavefunction ψ_a(x) = exp(−a x²) (oscillator, hbar = m = ω = 1)
E_loc = −ψ''/2ψ + V = a + (1/2 − 2a²) x²
"""
return a + (0.5 - 2 * a**2) * x**2
def vmc_energy(a, n_samples=200_000, step=1.0):
"""Metropolis-sample |ψ_a|², return ⟨E_loc⟩ and its statistical error"""
x = 0.0
samples = np.empty(n_samples)
for i in range(n_samples):
x_new = x + step * rng.uniform(-1, 1)
# acceptance ratio = |ψ(x_new)/ψ(x)|² = exp(−2a(x_new² − x²))
if rng.uniform() < np.exp(-2 * a * (x_new**2 - x**2)):
x = x_new
samples[i] = local_energy(x, a)
burn = n_samples // 10 # discard the first 10%: burn-in
E = samples[burn:]
return E.mean(), E.std() / np.sqrt(len(E))
print(" a ⟨E⟩ stat. error")
for a in [0.3, 0.4, 0.5, 0.6, 0.7]:
E, err = vmc_energy(a)
print(f" {a:.1f} {E:.5f} ±{err:.5f}" + (" ← exact" if a == 0.5 else ""))
Measured output:
a ⟨E⟩ stat. error
0.3 0.56864 ±0.00088
0.4 0.51155 ±0.00037
0.5 0.50000 ±0.00000 ← exact
0.6 0.50938 ±0.00030
0.7 0.52600 ±0.00059
The energy takes its minimum of at — precisely the oscillator ground state. Three details worth chewing on:
- The statistical error at is exactly zero: when the trial function hits the exact ground state, , the local energy equals the constant everywhere and the variance vanishes. This “zero-variance principle” is an important gauge of wavefunction quality in practical VMC.
- Burn-in: the chain starts at an arbitrary point and needs time to “forget” its starting position and reach the target distribution; those samples get discarded.
- The statistical error goes as , and neighbouring samples are correlated; the rigorous treatment estimates the error with blocking over the correlation length.
In practice, replace by the configuration of particles and the trial function by a Slater determinant times a correlation factor (Slater–Jastrow) — the workflow is identical. Growing the dimension costs only linearly: that is Monte Carlo’s victory over the exponential wall.
The sign problem: an honest confession
Monte Carlo’s Achilles heel in one sentence: it needs an everywhere-non-negative “probability” to sample from. Bosonic ground-state wavefunctions can be chosen positive — all is well; but fermionic wavefunctions must be antisymmetric — positive in places, negative in others. In more general quantum Monte Carlo (sampling the path integral), the sampled weights turn negative or even complex, and the only recourse is to shove the sign into the quantity being averaged. The price: the effective signal is the cancellation of huge positive and negative terms,
decaying exponentially in particle number and inverse temperature — so the statistical error blows up exponentially in return. This is the famous fermion sign problem, proved NP-hard in the worst case, with no general solution in existence. VMC dodges it with approximations such as fixed nodes (at the price of a systematic error) — and this is precisely the class of problem where quantum computers may deliver a genuine breakthrough.
Key formulas
The exponential wall
N = 40 is already a trillion dimensions; the root difficulty of many-body problems
Site operator
The literal translation into sparse-matrix kron
The Lanczos idea
Diagonalise a small tridiagonal matrix inside the Krylov space; needs only "matrix times vector"
Local energy
Zero variance when the trial state hits an eigenstate
The sign problem
Fermionic weights cancel between signs; statistical error explodes exponentially; NP-hard in the worst case
Self-check3 questions
- 1.
For a chain of 20 spin-1/2 particles, what is the dimension of the Hamiltonian matrix — and why is the computation still feasible?
- 2.
In a VMC run, you find that for some parameter set the variance of the local energy is nearly zero. What does that tell you?
- 3.
The root of the sign problem is:
Section 105 of 106 · use ← → to turn the page