From phase space to the configurational integral¶
A classical fluid of particles in volume at temperature has a Hamiltonian that splits cleanly:
Because the kinetic and configurational pieces add, the canonical partition function factorizes:
The kinetic prefactor is exactly the ideal gas. Everything that makes a liquid a liquid lives in , the configurational integral. Computing analytically is impossible for any realistic — we need either approximations (mean-field theories, virial expansions) or simulations.
The pressure is
For a stable equilibrium fluid the volume dependence of gives , but a fluid stretched into the metastable region can sustain transiently before cavitating — the inequality is a stability statement, not an absolute one.
Reduced distribution functions¶
We rarely need the full -body probability . Most thermodynamic quantities depend only on low-order marginals. Define the -particle density
The combinatorial prefactor counts the ways to label particles at positions . For a homogeneous, isotropic fluid the one-body density is constant, . The two-body density depends only on the separation , and it defines the radial distribution function via
Read this as: is the local density at distance from a tagged particle. For an ideal gas everywhere; for a real fluid is small at small (excluded volume), oscillates around 1 at intermediate (coordination shells), and approaches 1 at large (uncorrelated).
Reading : shells, packing, structure¶
Three features of a liquid’s encode different physics:
The first peak at is the contact distance set by hard-core repulsion. Its height grows with density as the first solvation shell becomes more occupied.
The first minimum sets the boundary of the first coordination shell; integrating up to it gives the coordination number
Damped oscillations beyond the first peak reflect packing — like ripples around each particle. They die out at much larger than the correlation length.
The figures below contrast a dilute gas, dense gas, liquid, and crystal.

Hands-on: from a 2D Lennard-Jones simulation¶
To make this concrete, run a quick Monte-Carlo simulation of a 2D Lennard-Jones fluid and measure directly. The 2D normalization uses shell area instead of :
where is the average histogram count of pair separations falling in the annulus (each unordered pair counted once — hence the factor of 2).
Source
import numpy as np
import matplotlib.pyplot as plt
from numba import njit
rng = np.random.default_rng(0)
@njit
def lj_mc_2d_with_gr(N, L, T, n_sweeps, max_disp, n_bins, r_max, seed):
np.random.seed(seed)
side = int(np.sqrt(N))
pos = np.empty((N, 2))
spacing = L / side
for ix in range(side):
for iy in range(side):
k = ix * side + iy
pos[k, 0] = (ix + 0.5) * spacing
pos[k, 1] = (iy + 0.5) * spacing
rcut2 = r_max * r_max
hist = np.zeros(n_bins, dtype=np.int64)
beta = 1.0 / T
n_samples = 0
for sweep in range(n_sweeps):
for _ in range(N):
i = np.random.randint(N)
old_x, old_y = pos[i, 0], pos[i, 1]
# energy of particle i at old position
old_e = 0.0
for j in range(N):
if j == i:
continue
dx = old_x - pos[j, 0]
dy = old_y - pos[j, 1]
dx -= L * round(dx / L)
dy -= L * round(dy / L)
r2 = dx * dx + dy * dy
if r2 < rcut2 and r2 > 1e-12:
inv2 = 1.0 / r2
inv6 = inv2 * inv2 * inv2
old_e += 4.0 * (inv6 * inv6 - inv6)
# propose move
new_x = (old_x + (np.random.random() - 0.5) * 2.0 * max_disp) % L
new_y = (old_y + (np.random.random() - 0.5) * 2.0 * max_disp) % L
# energy at new position
new_e = 0.0
for j in range(N):
if j == i:
continue
dx = new_x - pos[j, 0]
dy = new_y - pos[j, 1]
dx -= L * round(dx / L)
dy -= L * round(dy / L)
r2 = dx * dx + dy * dy
if r2 < rcut2 and r2 > 1e-12:
inv2 = 1.0 / r2
inv6 = inv2 * inv2 * inv2
new_e += 4.0 * (inv6 * inv6 - inv6)
if np.random.random() < np.exp(-beta * (new_e - old_e)):
pos[i, 0] = new_x
pos[i, 1] = new_y
# sample pair histogram after burn-in
if sweep >= n_sweeps // 3:
for i in range(N - 1):
for j in range(i + 1, N):
dx = pos[i, 0] - pos[j, 0]
dy = pos[i, 1] - pos[j, 1]
dx -= L * round(dx / L)
dy -= L * round(dy / L)
r = np.sqrt(dx * dx + dy * dy)
if r < r_max:
b = int(r / r_max * n_bins)
if b < n_bins:
hist[b] += 1
n_samples += 1
return pos, hist, n_samples
def gr_from_hist(hist, n_samples, N, rho, r_max):
n_bins = len(hist)
edges = np.linspace(0, r_max, n_bins + 1)
r = 0.5 * (edges[:-1] + edges[1:])
dr = edges[1] - edges[0]
shell = 2.0 * np.pi * r * dr # 2D shell area
return r, 2.0 * hist / (n_samples * N * rho * shell)
# Run two densities at the same temperature
N, T, n_sweeps, max_disp, n_bins = 100, 1.5, 4000, 0.15, 80
results = {}
for rho in [0.3, 0.7]:
L = np.sqrt(N / rho)
_, hist, ns = lj_mc_2d_with_gr(N, L, T, n_sweeps, max_disp, n_bins, L / 2, 0)
r, g = gr_from_hist(hist, ns, N, rho, L / 2)
results[rho] = (r, g)
fig, ax = plt.subplots(figsize=(7, 4))
for rho, (r, g) in results.items():
ax.plot(r, g, lw=1.5, label=fr'$\rho = {rho}$')
ax.axhline(1, color='k', lw=0.5)
ax.set(xlabel='$r$', ylabel='$g(r)$', xlim=(0, 4),
title=f'2D Lennard-Jones $g(r)$ at $T={T}$')
ax.legend()
ax.grid(True, ls=':', alpha=0.5)
fig.tight_layout()
The dilute curve has only a small first peak; the dense curve develops the characteristic damped oscillations of a liquid. Both approach 1 at large — particles far away are uncorrelated.
The reversible work theorem: as a free energy¶
There is a striking second reading of . Define the potential of mean force by
is the reversible work required to bring two tagged particles from infinity to separation in the presence of all the others — fully equilibrated. Where peaks, has a minimum: a coordination-shell separation is favored. Where dips below 1 (the trough between shells), has a barrier.
This is more than a relabeling. The mean force at fixed separation is
so governs the slow, large-scale motion of pairs of particles in a liquid — exactly the picture you need for diffusion, association, and reaction kinetics in solution.
# w(r) = -k_B T log g(r) from the simulated RDF
fig, ax = plt.subplots(figsize=(7, 4))
for rho, (r, g) in results.items():
g_safe = np.where(g > 1e-3, g, 1e-3)
w = -T * np.log(g_safe)
ax.plot(r, w, lw=1.5, label=fr'$\rho = {rho}$')
ax.axhline(0, color='k', lw=0.5)
ax.set(xlabel='$r$', ylabel='$w(r) / k_B T$', xlim=(0.8, 4), ylim=(-1.2, 4),
title='Potential of mean force')
ax.legend()
ax.grid(True, ls=':', alpha=0.5)
fig.tight_layout()
At low density is essentially the bare LJ potential — . At higher density correlations dress : the well shifts, secondary minima appear, and the long-range tail picks up oscillatory structure. In water, for example, between two methane-like solutes carries a desolvation barrier that is entirely a many-body effect.
Thermodynamics from ¶
For pairwise interactions, determines the most important fluid thermodynamic quantities. In 3D:
Energy per particle
Each particle has neighbors at separation , each contributing ; the factor of avoids double counting.
Pressure (virial equation)
The first term is the ideal gas; the integral is the virial correction. In an MD simulation is what the histogram counts, and these two formulas turn it into thermodynamics.
Low-density limit and the second virial coefficient¶
In the dilute limit, multi-body correlations are rare and a particle “feels” only its instantaneous partner:
Plug this into the pressure equation and Taylor-expand in to obtain the virial expansion
where the second virial coefficient is
The integrand is the Mayer -function . It captures deviations from ideality: in the repulsive core (excluded volume), in the attractive well (over-density).
At low attraction wins and — the gas attracts itself; at high excluded volume wins and , the gas repels itself.
The temperature where is the Boyle temperature , where the gas behaves ideally to second order despite microscopic interactions.
Source
from scipy.integrate import quad
from scipy.optimize import brentq
def b2_lj(T, sigma=1.0, eps=1.0):
'''B_2(T) for Lennard-Jones in 3D, in units of sigma^3.'''
def integrand(r):
u = 4.0 * eps * ((sigma / r) ** 12 - (sigma / r) ** 6)
return (1.0 - np.exp(-u / T)) * r * r
val, _ = quad(integrand, 1e-3, 8.0, limit=200)
return 2.0 * np.pi * val
T_grid = np.linspace(0.6, 6.0, 120)
B2 = np.array([b2_lj(T) for T in T_grid])
T_Boyle = brentq(b2_lj, 2.0, 5.0)
fig, ax = plt.subplots(figsize=(7, 4))
ax.plot(T_grid, B2, color='steelblue', lw=2)
ax.axhline(0, color='k', lw=0.5)
ax.axvline(T_Boyle, color='crimson', ls='--', lw=1,
label=fr'$T_B \approx {T_Boyle:.2f}$')
ax.set(xlabel='$T$ (LJ units)', ylabel=r'$B_2(T) / \sigma^3$',
title='Second virial coefficient of the Lennard-Jones fluid')
ax.legend()
ax.grid(True, ls=':', alpha=0.5)
fig.tight_layout()
print(f'Boyle temperature for LJ: T_B = {T_Boyle:.4f} (literature: ~3.418)')Boyle temperature for LJ: T_B = 3.4083 (literature: ~3.418)

Takeaways¶
The whole fluid problem reduces to evaluating the configurational integral , that is what simulation does.
is the two-point marginal of the configurational distribution, and almost every thermodynamic quantity is a moment of against some weight: energy weights it by , pressure by , virial coefficients by Mayer’s .
has a thermodynamic identity through the reversible-work theorem: is the free-energy profile felt by a pair embedded in the fluid.
The low-density expansion () connects directly to the pair potential and locates the Boyle temperature where attractive and repulsive contributions balance.
Problems¶
Coordination number. Run the 2D LJ simulation at and . Identify the location of the first minimum of and integrate from 0 to — the result is the average number of first-shell neighbors. Repeat at and and report how the coordination number changes with density.
for the square-well potential. For for , for , and 0 for , compute analytically. Show that the Boyle temperature satisfies , i.e. , and plot .
Pressure from . From the simulated at , compute the configurational contribution to the 2D pressure (replace with and with in the virial formula — the dimensional factors change). Compare to the ideal-gas value ; comment on its sign.
Reference¶
J.-P. Hansen and I. R. McDonald, Theory of Simple Liquids, 4th ed. (Academic Press, 2013) — chapters 2 (distribution functions) and 3 (perturbation theory). The standard text on everything above.