Hessians, vibrational frequencies & thermochemistry#
The gradient told you whether you are at equilibrium. The Hessian — the matrix
of second derivatives of the energy — tells you what kind of stationary point it is, and unlocks the two
things chemists actually measure at a minimum: vibrational frequencies (infrared/Raman spectra) and
thermochemistry (the free energy that predicts equilibria and rates). This is a real, tested qc-rs
feature (m.scf.hessian, qc.thermo.frequencies) that the rest of the manual has not yet covered.
Theory: why the second derivative needs orbital response#
Differentiate the energy once more and something changes. The gradient — the first derivative — needed no orbital-response term because Hellmann–Feynman collapses it away: the SCF is stationary in the orbitals (\(\mathbf g=0\), SCF convergence theory), so \(\partial E/\partial R_A\) only ever sees the operators’ explicit \(R\)-dependence. Differentiating that stationarity condition a second time is where the orbitals’ own response reappears — because how the orbitals relax as one nucleus moves is itself a function of where every other nucleus is:
The skeleton term is the easy half — second-derivative integrals (\(\partial^2 T\), \(\partial^2 V_{ne}\), \(\partial^2(\mu\nu|\lambda\sigma)\), \(\partial^2 S\) via \(W\)) contracted with the unperturbed density, exactly like the gradient but one derivative order up. The orbital-response term is new physics: \(U^B = \partial\mathbf C/\partial R_B\), how the orbitals themselves relax as nucleus \(B\) moves, found by solving the coupled-perturbed Hartree–Fock/Kohn–Sham (CPHF/CPKS) equations
the same orbital-Hessian operator \(\mathbf A+\mathbf B\) from the SCF convergence theory augmented-Hessian machinery — reused here as a linear solve rather than an eigenvalue problem, with a geometry-derivative right-hand side \(\mathbf b^B\) built from the first-derivative Fock \(F^{B,x}\). This is the one genuinely new ingredient beyond the gradient: qc-rs’s CPHF/CPKS response engine already exists (it also powers stability analysis), so the Hessian assembles the “skeleton + fold-back” recipe on top of it:
flowchart LR
S["Skeleton term<br/>2nd-deriv integrals × D, W"] --> SUM["+"]
G["1st-derivative Fock F^B,x<br/>(already built for the gradient)"] --> RHS["CPHF right-hand side b^B"]
RHS --> CPHF["Solve (A+B) U^B = −b^B<br/>(reuses the SCF response engine)"]
CPHF --> FOLD["Fold-back term<br/>Σ ∂F/∂R_A · U^B"]
FOLD --> SUM
SUM --> H["Molecular Hessian ∂²E/∂R_A∂R_B"]
Algorithm 2 (Assembling the molecular Hessian)
Input: converged SCF (\(\mathbf C\), \(\varepsilon\), \(\mathbf D\)), the CPHF response engine. Output: the Hessian \(\mathbf H \in \mathbb R^{3n_{\text{atom}}\times 3n_{\text{atom}}}\).
Accumulate the skeleton blocks — one-electron, two-electron (4-center or RI), and (for KS) XC second-derivative terms — each contracted with the fixed density \(\mathbf D\) and energy-weighted density \(\mathbf W\).
Build the geometry-derivative Fock \(F^{B,x}\) and overlap \(S^{B,x}\) for every nucleus \(B\) (reusing the same first-derivative integrals the gradient already needs).
For each perturbation \(B\), solve \((\mathbf A+\mathbf B)U^B = -\mathbf b^B\) for the orbital response \(U^B\) (the CPHF/CPKS solve — one linear solve per nucleus, \(3n_{\text{atom}}\) total).
Fold \(U^B\) back: add \(\sum_{ai}(\partial F_{ai}/\partial R_A)\,U^B_{ai} + \text{c.c.}\) to the skeleton.
Add the classical nuclear-repulsion Hessian \(\partial^2 V_{nn}/\partial R_A\partial R_B\) (closed form).
Self-test: the acoustic sum rule \(\sum_B \mathbf H_{AB} \approx \mathbf 0\) must hold (a rigid translation of the whole molecule changes no second derivative either) — the Hessian analogue of the gradient’s translational-invariance check.
From the Hessian to a spectrum: normal modes#
A Hessian in Cartesian coordinates mixes translation, rotation, and genuine vibration. Untangling them is a short, standard recipe:
Mass-weight the Hessian, \(\tilde H_{Ai,Bj} = H_{Ai,Bj}/\sqrt{m_A m_B}\) (masses in atomic units).
Project out rigid translation and rotation — 6 directions for a nonlinear molecule, 5 for a linear one (rotation about the molecular axis carries no energy) — using the mass-weighted translation/rotation projector.
Diagonalize the projected \(\tilde H\): each eigenvalue \(k\) is a normal-mode force constant, and the corresponding eigenvector is the normal mode — the pattern of atomic displacement for that vibration.
Convert to a frequency: \(\tilde\nu\,[\text{cm}^{-1}] = \sqrt{k}\cdot\text{(unit factor)}\). A negative \(k\) gives an imaginary frequency — the signature of a saddle point, not a true minimum (one imaginary mode along the reaction coordinate is exactly what a transition state looks like).
A nonlinear molecule with \(N\) atoms therefore has \(3N-6\) vibrational modes; a linear one has \(3N-5\) (one fewer rotation to remove). Water (\(N=3\), nonlinear) has 3; H₂ (\(N=2\), linear) has 1 — both verified below.
Thermochemistry: from frequencies to free energy#
The harmonic frequencies feed standard ideal-gas statistical mechanics (translational + rotational + vibrational partition functions) to produce the quantities a chemist actually wants at a given temperature and pressure:
Zero-point energy \(\text{ZPE} = \tfrac12\sum_k h c\,\tilde\nu_k\) — the vibrational ground-state energy every harmonic mode contributes even at \(T=0\).
Enthalpy \(H\), entropy \(S\), Gibbs free energy \(G=H-TS\), and the heat capacities \(C_v,C_p\) — each built from the translational, rotational, and vibrational partition functions in the usual way, added to the electronic energy.
\(G\) is what actually predicts chemical equilibria and, via transition-state theory, reaction rates — the harmonic frequencies are the bridge from “one SCF energy at one geometry” to that.
Usage#
The Hessian is a result on the SCF step, just like the gradient:
import qc, numpy as np
water = "O 0 0 0.117; H 0 0.757 -0.469; H 0 -0.757 -0.469"
m = qc.chk.new(atom=water, ao="sto-3g", unit="angstrom").scf(ref="r").run()
H = np.asarray(m.scf.hessian) # shape (3*natom, 3*natom) = (9, 9) for water
H.sum(axis=1) # ~0 in every row: the acoustic sum-rule self-test
qc.thermo.frequencies(mychk) takes it the rest of the way — mass-weighting, projection,
diagonalization, and the thermochemistry — in one call:
fr = qc.thermo.frequencies(m)
fr.frequencies # array of harmonic wavenumbers, cm⁻¹
fr.n_imaginary # count of imaginary (negative-k) modes: 0 at a true minimum
fr.norm_mode # the normal-mode displacement vectors
fr.thermo # dict: temperature, pressure, zpe, e_tot, h_tot, g_tot, s_tot, cv_tot, cp_tot
A converged geometry is a prerequisite in spirit, not just in practice: frequencies computed away from a
true minimum (\(\mathbf g\ne 0\)) are not meaningful harmonic frequencies — always run .opt()
(gradients & geometry optimization) first for a real analysis.
Worked example#
import qc, numpy as np
water = "O 0 0 0.117; H 0 0.757 -0.469; H 0 -0.757 -0.469"
m = qc.chk.new(atom=water, ao="sto-3g", unit="angstrom").scf(ref="r").run()
fr = qc.thermo.frequencies(m)
print("frequencies (cm⁻¹):", np.round(np.sort(fr.frequencies), 2))
print("imaginary modes :", fr.n_imaginary)
print("ZPE (Ha) :", round(fr.thermo["zpe"], 6))
print("G at 298.15 K (Ha) :", round(fr.thermo["g_tot"], 6))
# frequencies (cm⁻¹): [2041.42 4494.04 4796.72]
# imaginary modes : 0
# ZPE (Ha) : 0.025817
# G at 298.15 K (Ha) : 0.033348
# a linear molecule: 3N-5 = 1 mode, not 3N-6
h2 = qc.chk.new(atom="H 0 0 0; H 0 0 0.74", ao="sto-3g", unit="angstrom").scf(ref="r").run()
print("H2 modes:", len(qc.thermo.frequencies(h2).frequencies)) # 1
Three real, positive frequencies and zero imaginary modes confirm water’s geometry is a genuine minimum, not a saddle point — and the count itself (3 for nonlinear water, 1 for linear H₂) is a direct check that the translation/rotation projection worked.
Exercise 7
A colleague computes frequencies at a geometry they only partially optimized.
n_imaginarycomes back asWhat does that mean, and what should they check before trusting the frequencies?
Why does H₂ (linear, \(N=2\)) have exactly \(3N-5=1\) vibrational mode while a bent triatomic like water (\(N=3\), nonlinear) has \(3N-6=3\) — where did the “missing” rotational mode for water go, relative to a linear molecule with the same atom count?
The Hessian needs a CPHF solve that the gradient does not. In one sentence, why does the first derivative avoid it while the second cannot?
Solution to Exercise 7
One imaginary frequency means the Hessian has a negative eigenvalue — the structure is a saddle point (e.g. a transition state), not a minimum, consistent with an incomplete optimization. They should re-run
.opt()to full convergence (checkingopt.convergedand the gradient trajectory) before trusting any frequency as a real vibration.A linear molecule has only 2 independent rotational degrees of freedom (rotation about its own axis carries no energy, since the moment of inertia about that axis is zero), so only 5 of the 6 rigid-body directions are removed, leaving \(3N-5\) vibrations. A nonlinear molecule has all 3 rotations, removing 6 and leaving \(3N-6\). Water, being bent rather than linear, has the full 3 rotational degrees of freedom to remove; the mode a linear triatomic would have “extra” relative to water is exactly the bending motion that in water is already counted among its 3 vibrations, not a missing one.
Hellmann–Feynman makes the first derivative stationary-in-the-orbitals, so orbital response cancels; differentiating that stationarity condition a second time is precisely what reintroduces the orbitals’ own response to a second nuclear displacement, which only a CPHF solve can supply.
With energies, forces, and now vibrational/thermochemical analysis in hand, the remaining guide chapters turn to the environment — solvation & dispersion — and then visualization, logging, and the full properties suite.