Zero Point Energy (and finite-temperature vibrational free energy) from frozen phonons using Quantum ESPRESSO pw.x output.
The approach is deliberately simple: no symmetry reduction, no DFPT, just central finite differences on a user-selected set of atoms, a full 3N×3N dynamical matrix at Γ, and numpy for diagonalisation. This is appropriate when you only care about a small adsorbate on a frozen surface and want results without the overhead of a full phonon calculation.
- Generate displaced configurations (
generate_conf.py): for each selected atom and each Cartesian direction, write a QEpw.xinput with the atom displaced by ±δ. - Run QE: standard
pw.xSCF withtprnfor = .true.on all displaced configurations. - Compute frequencies (
compute_freq.py): read forces from the QE XML outputs, build the dynamical matrix via central finite differences, diagonalise it, and compute ZPE and thermal corrections.
The dynamical matrix element is:
where
Modes with
| Package | Purpose |
|---|---|
| ASE | Read QE input files, geometry manipulation |
| qeschema | Parse QE XML output |
| numpy | Linear algebra |
| load_calculator | Parse QE input namelists (copy included) |
Install Python dependencies:
pip install ase qeschema numpypython generate_conf.py INDICES [options]Positional arguments:
| Argument | Description |
|---|---|
INDICES |
Space-separated 0-based indices of the atoms to displace |
Options:
| Flag | Default | Description |
|---|---|---|
-f, --filename |
pw.in |
QE input file with the equilibrium geometry |
-d, --displ |
0.01 |
Displacement magnitude [Å] |
--outdir |
./ |
Root directory for the displacement folders |
--outfile |
pw.in |
Filename for each displaced input |
--extra_cp |
— | Extra files to copy into each folder (e.g. job submission script) |
--debug |
— | Verbose output |
This creates one folder per displacement: dpos_<atom>_<coord>/ and dneg_<atom>_<coord>/, each containing a ready-to-run QE input.
Example — displace atoms 0, 1, 2 (an adsorbed H₂O molecule) with 0.02 Å:
python generate_conf.py 0 1 2 -f pw.in -d 0.02 --outdir vib/ --extra_cp job.shRun pw.x in each displacement folder. The XML output file (pwscf.xml by default) must be present in every folder before proceeding.
python compute_freq.py INDICES [options]Options:
| Flag | Default | Description |
|---|---|---|
-f, --filename |
pw.in |
Equilibrium QE input file (for masses and geometry check) |
-d, --displ |
0.01 |
Displacement used in step 1 [Å] — must match |
--indir |
./ |
Directory containing the dpos_*/dneg_* folders |
--outdir |
./ |
Directory for output files |
--qeout |
pwscf.xml |
Name of the QE XML output (same for all runs) |
-T, --temperature |
298.15 |
Temperature for thermal corrections [K] |
--debug |
— | Verbose output |
Example — continuing from the example above:
python compute_freq.py 0 1 2 -f pw.in -d 0.02 --indir vib/ --outdir vib/results/Output files (written to --outdir):
| File | Content |
|---|---|
eigenvalues.dat |
Eigenvalues, frequencies (THz, cm⁻¹), ZPE, TS, A_vib per mode |
eigenvectors.dat |
Eigenvectors (columns) of the dynamical matrix |
force_posdx.dat |
Force matrix for positive displacements [Eh/a₀] |
force_negdx.dat |
Force matrix for negative displacements [Eh/a₀] |
force_const.dat |
Force constant matrix [Eh/a₀²] |
dyna_matr.dat |
Mass-weighted dynamical matrix [Eh/(a₀² Da)] |
Three reference calculations are included in test_case-*/. Run:
python test_easyZPE.py| System | Atoms displaced | Expected ZPE |
|---|---|---|
| H₂ molecule | 0, 1 | ~0.268 eV |
| H₂O molecule | 0, 1, 2 | ~0.566 eV |
| H₂O on Ni surface | 56, 57, 58 | ~0.60–0.65 eV |
The test for generate_conf re-generates all displaced geometries for H₂O and checks positions against the committed reference inputs.
- No symmetry: all 3N degrees of freedom are displaced and computed independently. For a single adsorbate this is fine; for many atoms it scales poorly.
- Surface atoms are frozen: only the atoms you list are displaced. The surrounding surface is kept fixed, which is the usual frozen-surface approximation.
- Near-zero modes: translations and frustrated translations of the adsorbate will appear as near-zero or slightly imaginary modes due to DFT residual forces. They are skipped in ZPE/Avib sums automatically and flagged in the output.
- Displacement size: 0.01 Å is a reasonable default. If you see large imaginary frequencies on real modes, tighter SCF convergence (
conv_thr) helps more than changing δ. - Thermal quantities:
TSandA_vibcan be unreliable for very soft modes (< ~50 cm⁻¹) that are not true translations — cell-size artefacts produce artificially large entropy contributions. ZPE is unaffected.
