“Know how to solve every problem that has been solved.” “What I cannot create, I do not understand.” — Richard Feynman

Project: Wang-Landau on a PySCF energy surface

Projects

Two tools that rarely meet in one script. PySCF computes real quantum-chemical energies for a molecular configuration. Wang-Landau sampling estimates the density of states — the number of configurations at each energy — and from a single you get the full thermodynamics at every temperature at once. Bolt them together and the quantum energies become the physics, while Wang-Landau turns them into free energies, populations, and heat capacities. This page does it twice: once where it's overkill but checkable, and once where it's the only option.

The Wang-Landau move is almost embarrassingly simple. Walk randomly through configuration space, but accept a step with probability — biased toward energies you've seen less — and after every step multiply at the current energy by a factor . The walk fills in a flat histogram of visited energies; as the histogram flattens, and converges. The trick is that pushing toward rarely-visited energies makes the walk sample the whole energy range, including states a fixed-temperature simulation would almost never reach.

# Wang-Landau: estimate the density of states g(E) by a flat-histogram
# random walk in energy. One run -> thermodynamics at EVERY temperature.
def wang_landau(energy_of, neighbors, nbins, emin, emax, lnf_final=1e-3):
    width = (emax - emin) / nbins
    binof = lambda e: min(max(int((e - emin) / width), 0), nbins - 1)
    lng = np.zeros(nbins)            # ln g(E), the unknown we're solving for
    state = initial_state(); bi = binof(energy_of(state))
    lnf = 1.0                        # modification factor f; ln f starts at 1
    while lnf > lnf_final:
        H = np.zeros(nbins)          # visit histogram for this stage
        while not is_flat(H):
            s2 = neighbors(state); bj = binof(energy_of(s2))
            # accept with prob min(1, g(E)/g(E')) — push toward unvisited E
            if np.log(rng.random()) <= lng[bi] - lng[bj]:
                state, bi = s2, bj
            lng[bi] += lnf           # the WL move: bump g at the current E
            H[bi]   += 1
        lnf *= 0.5                   # histogram flat -> refine f, reset H
    return lng                       # ln g(E), up to a constant

Once you have , temperature is free. The canonical partition function is just a weighted sum over the density of states, , and everything else follows:

# From a single g(E), all of thermodynamics at any temperature:
def thermodynamics(centers, g, T):
    w  = g * np.exp(-(centers - centers.min()) / (kB * T))   # Boltzmann-weighted DOS
    Z  = w.sum()
    E  = (w * centers).sum() / Z                              # <E>
    E2 = (w * centers**2).sum() / Z
    Cv = (E2 - E*E) / (kB * T*T)                              # heat capacity
    return E, Cv

That "every temperature from one run" property is Wang-Landau's whole point. A standard Metropolis simulation gives you one temperature per run; to map you'd re-run at every . Wang-Landau computes once and hands you the entire curve.

Part 1: butane, where it's overkill but checkable

Start with one rotatable bond: the central dihedral of butane. A rigid PySCF scan (RHF/STO-3G) over the dihedral gives the torsional energy surface — the textbook profile, with the anti conformer as the global minimum (0 kcal/mol), two gauche wells at ±60° (about 1.8 kcal/mol up), and a high syn barrier where the methyls eclipse (9.8 kcal/mol).

Butane central-dihedral energy versus angle, computed by a rigid PySCF scan. A deep anti minimum at 180 degrees, shallower gauche minima near 60 and 300 degrees, and a tall syn barrier of about 9.8 kcal/mol at 0 degrees.

Run Wang-Landau over the dihedral to get , then read off the conformer populations and heat capacity at all temperatures. The gauche wells are doubly degenerate (±60°), so entropy favors them as temperature rises even though they cost energy — the anti population falls and the gauche population climbs, with a heat-capacity bump where the two trade off.

Two panels. Left: anti and gauche populations versus temperature; anti dominant at low T and declining, gauche rising. Right: heat capacity versus temperature, Wang-Landau points lying exactly on the direct Boltzmann curve.

The validation is the point of this half. Because butane's dihedral is one dimension, the exact answer is a trivial Boltzmann integral over the scan — no sampling needed. Wang-Landau's -derived heat capacity lands right on that exact curve (maximum difference across 50–1000 K). So in 1D, Wang-Landau is overkill — and that's exactly why it's worth running here: it reproduces the answer you can check, which is what licenses trusting it where you can't. That's the computational-empiricism move: validate against the computable case, then go where computation is the only option.

Part 2: where Wang-Landau is actually needed

Butane has one dihedral. Chain a few more together — an alkane with rotatable bonds, each anti, gauche+, or gauche- — and the configuration count is . You might think a long chain still yields to a transfer matrix, the way a 1D Ising model does, and you'd be right if the only interactions were between neighboring dihedrals. They aren't. Real chains can't pass through themselves: every pair of units interacts through excluded volume, a non-local constraint that couples bond 1 to bond 40. That kills the transfer matrix. There is no closed form, and at large there are too many configurations to enumerate. This is the regime flat-histogram sampling was built for.

The model keeps the quantum-chemical part and adds the geometry. Each dihedral's torsional cost is the PySCF gauche penalty from Part 1; the chain is built in 3D by standard backbone geometry; non-bonded carbons repel at short range (hard-core excluded volume) and attract weakly in a contact shell. The attraction is a united-atom-style model parameter — the kind of thing a PySCF dimer calculation pins down — while the torsional energies are the PySCF-grounded part.

# Energy of one chain conformation: PySCF torsional cost + excluded volume.
# states[i] in {0:anti, 1:gauche+, 2:gauche-}; 3D coords built by NeRF placement.
def energy(states, mask):
    pts = build(states)                       # backbone carbons in 3D
    r = pairwise_distances(pts)[mask]          # non-bonded pairs (|i-j| >= 4)
    if (r < D_MIN).any():
        return np.inf                          # excluded volume: reject overlaps
    e_torsion = EPS_G * sum(s != 0 for s in states)   # gauche penalty (from PySCF)
    contacts  = int((r < D_CONTACT).sum())            # favorable contacts
    return e_torsion - EPS_C * contacts

Validate first. At there are only configurations (239 of them overlap-free), so the exact density of states is a direct enumeration. Wang-Landau reproduces the exact heat capacity to — the same check as butane, now on the self-avoiding chain.

Heat capacity versus temperature for the K=6 self-avoiding chain. Wang-Landau points lie exactly on the exact-enumeration curve over 3^6 = 729 configurations.

Then go where enumeration can't follow. At there are configurations, and with excluded volume there is no transfer-matrix shortcut — you would have to visit them. Wang-Landau doesn't. It produces a density of states spanning nearly four orders of magnitude from a random walk that never enumerates anything, and from that single the full thermodynamics follows.

Two panels. Left: log10 of the density of states versus energy for the K=18 self-avoiding chain, a smooth curve spanning about 3.7 decades. Right: heat capacity and average radius of gyration versus temperature, the chain modestly more compact at low temperature.

The left panel is the thing you cannot get any other way: for a 390-million-configuration self-avoiding ensemble, spanning 3.7 decades, produced by a walk that touched a vanishing fraction of those configurations. The right panel reads thermodynamics off it — heat capacity and average radius of gyration versus temperature, with the chain modestly more compact at low (the leading edge of a coil-to-globule collapse, which sharpens for longer chains). The caveat: at the compaction is mild and the heat-capacity feature is broad — but the method is exactly what you'd point at a 300-residue protein with rotamer states, where the only number you'll ever see is the one Wang-Landau gives you.

Why the pairing works

The division of labor is clean. PySCF supplies energies that are grounded in quantum mechanics rather than a hand-tuned potential — the butane torsional profile is a real electronic-structure calculation, not a cosine fit. Wang-Landau supplies the bridge from those energies to thermodynamics: one density of states, every temperature, any number of dimensions. Neither does the other's job. And the whole thing is anchored by the validation in Parts 1 and 2 — where the exact answer exists, Wang-Landau matches it; where it doesn't, Wang-Landau is the answer.

As a falsifiable claim: Wang-Landau's -derived heat capacity equals the exact Boltzmann result whenever the exact result is computable. The experiments are the butane scan (1D, diff ) and the chain (full enumeration, diff ). It would be falsified by a converged Wang-Landau run that disagreed with a tractable exact enumeration — which, run correctly, it does not.

Limitations

Related on this site