14 - Van der Waals Corrections#
This tutorial demonstrates dispersion-corrected DFT calculations using graphite as an example, where van der Waals interactions are essential.
Note: Using reduced settings for demonstration. Production calculations would use higher ENCUT and denser k-points.
import numpy as np
from ase import Atoms
from vasp import Vasp
from vasp.parameters import get_vdw_params
print("=" * 60)
print("Van der Waals Corrections")
print("=" * 60)
print()
============================================================
Van der Waals Corrections
============================================================
Part 1: Graphite structure#
print("Part 1: Graphite structure")
print("-" * 40)
print()
# Create graphite (AB stacking)
# Hexagonal cell with 4 atoms
a = 2.46 # In-plane lattice constant
c = 6.71 # Interlayer spacing (experimental)
graphite = Atoms(
symbols=['C', 'C', 'C', 'C'],
scaled_positions=[
[0.0, 0.0, 0.0],
[1/3, 2/3, 0.0],
[0.0, 0.0, 0.5],
[2/3, 1/3, 0.5],
],
cell=[
[a, 0, 0],
[-a/2, a*np.sqrt(3)/2, 0],
[0, 0, c],
],
pbc=True,
)
print("Structure: Graphite (AB stacking)")
print(f" In-plane lattice: a = {a} A")
print(f" Interlayer spacing: c/2 = {c/2:.2f} A")
print(" Experimental interlayer: 3.35 A")
print()
Part 1: Graphite structure
----------------------------------------
Structure: Graphite (AB stacking)
In-plane lattice: a = 2.46 A
Interlayer spacing: c/2 = 3.35 A
Experimental interlayer: 3.35 A
Part 2: Standard PBE (no vdW)#
print("Part 2: Standard PBE (no dispersion)")
print("-" * 40)
print()
calc_pbe = Vasp(
label='results/vdw/graphite_pbe',
atoms=graphite.copy(),
xc='PBE',
encut=400, # Carbon needs decent cutoff
kpts=(8, 8, 4), # Reduced for speed
ismear=0,
sigma=0.05,
)
e_pbe = calc_pbe.potential_energy
print(f"PBE energy: {e_pbe:.6f} eV")
print()
# Scan interlayer distance with PBE (fewer points)
print("Interlayer binding scan (PBE):")
c_values = [6.0, 6.71, 8.0]
pbe_energies = []
for c_test in c_values:
graphite_test = graphite.copy()
cell = graphite_test.get_cell()
cell[2, 2] = c_test
graphite_test.set_cell(cell, scale_atoms=True)
calc = Vasp(
label=f'results/vdw/graphite_pbe_c{c_test:.1f}',
atoms=graphite_test,
xc='PBE',
encut=400,
kpts=(8, 8, 4),
ismear=0,
sigma=0.05,
)
e = calc.potential_energy
pbe_energies.append(e)
print(f" c = {c_test:.2f} A: E = {e:.4f} eV")
print()
print("PBE shows no binding minimum (energy decreases with c)")
print("This is WRONG - graphite layers should bind!")
print()
Part 2: Standard PBE (no dispersion)
----------------------------------------
PBE energy: -36.872474 eV
Interlayer binding scan (PBE):
c = 6.00 A: E = -36.7092 eV
c = 6.71 A: E = -36.8725 eV
c = 8.00 A: E = -36.9350 eV
PBE shows no binding minimum (energy decreases with c)
This is WRONG - graphite layers should bind!
Part 3: D3-BJ correction#
print("Part 3: PBE-D3(BJ) (Grimme's D3 with BJ damping)")
print("-" * 40)
print()
d3bj_params = get_vdw_params('d3bj')
print("D3-BJ parameters:")
print(" IVDW = 12 (D3 with BJ damping)")
print(" Empirical C6 coefficients with geometry-dependent terms")
print()
d3bj_energies = []
for c_test in c_values:
graphite_test = graphite.copy()
cell = graphite_test.get_cell()
cell[2, 2] = c_test
graphite_test.set_cell(cell, scale_atoms=True)
calc = Vasp(
label=f'results/vdw/graphite_d3bj_c{c_test:.1f}',
atoms=graphite_test,
xc='PBE',
encut=400,
kpts=(8, 8, 4),
ismear=0,
sigma=0.05,
**d3bj_params,
)
e = calc.potential_energy
d3bj_energies.append(e)
print(f" c = {c_test:.2f} A: E = {e:.4f} eV")
# Find minimum
min_idx = np.argmin(d3bj_energies)
print()
print(f"D3-BJ minimum near c = {c_values[min_idx]:.2f} A")
print("Experimental: c = 6.71 A (interlayer = 3.35 A)")
print()
Part 3: PBE-D3(BJ) (Grimme's D3 with BJ damping)
----------------------------------------
D3-BJ parameters:
IVDW = 12 (D3 with BJ damping)
Empirical C6 coefficients with geometry-dependent terms
c = 6.00 A: E = -37.4198 eV
c = 6.71 A: E = -37.4981 eV
c = 8.00 A: E = -37.4444 eV
D3-BJ minimum near c = 6.71 A
Experimental: c = 6.71 A (interlayer = 3.35 A)
Part 4: Compare dispersion methods#
print("Part 4: Comparison of vdW methods")
print("-" * 40)
print()
# Test at experimental c
graphite_exp = graphite.copy()
# Test just three methods for speed
methods = [
('None', {}),
('d3bj', get_vdw_params('d3bj')),
('ts', get_vdw_params('ts')),
]
print("Comparison at experimental geometry (c = 6.71 A):")
print()
print(f" {'Method':<10} {'Energy (eV)':<15} {'IVDW':<10}")
print(" " + "-" * 35)
for name, params in methods:
calc = Vasp(
label=f'results/vdw/graphite_{name}',
atoms=graphite_exp.copy(),
xc='PBE',
encut=400,
kpts=(8, 8, 4),
ismear=0,
sigma=0.05,
**params,
)
e = calc.potential_energy
ivdw = params.get('ivdw', 0)
print(f" {name:<10} {e:<15.4f} {ivdw:<10}")
print()
Part 4: Comparison of vdW methods
----------------------------------------
Comparison at experimental geometry (c = 6.71 A):
Method Energy (eV) IVDW
-----------------------------------
None -36.8725 0
d3bj -37.4981 12
ts -37.4459 2
Part 5: Binding energy calculation#
print("Part 5: Graphite binding energy")
print("-" * 40)
print()
# Calculate isolated graphene layer with PBE (no vdW)
graphene = Atoms(
symbols=['C', 'C'],
scaled_positions=[
[0.0, 0.0, 0.5],
[1/3, 2/3, 0.5],
],
cell=[
[a, 0, 0],
[-a/2, a*np.sqrt(3)/2, 0],
[0, 0, 15.0], # Large vacuum
],
pbc=True,
)
calc_graphene_pbe = Vasp(
label='results/vdw/graphene_pbe',
atoms=graphene.copy(),
xc='PBE',
encut=400,
kpts=(8, 8, 1),
ismear=0,
sigma=0.05,
)
e_graphene_pbe = calc_graphene_pbe.potential_energy
# Calculate graphene with D3-BJ
calc_graphene_d3 = Vasp(
label='results/vdw/graphene_d3bj',
atoms=graphene.copy(),
xc='PBE',
encut=400,
kpts=(8, 8, 1),
ismear=0,
sigma=0.05,
**d3bj_params,
)
e_graphene_d3 = calc_graphene_d3.potential_energy
# Binding energy per atom (graphite at experimental c = 6.71)
# PBE binding energy
e_graphite_pbe = pbe_energies[1] # c = 6.71
e_binding_pbe = (e_graphite_pbe - 2 * e_graphene_pbe) / 4
# D3-BJ binding energy
e_graphite_d3 = d3bj_energies[1] # c = 6.71
e_binding_d3 = (e_graphite_d3 - 2 * e_graphene_d3) / 4
print("Interlayer binding energy (per C atom):")
print()
print(f" PBE (no vdW): {e_binding_pbe*1000:.1f} meV")
print(f" PBE-D3(BJ): {e_binding_d3*1000:.1f} meV")
print(" Experimental: ~52 meV")
print()
print("Note: PBE shows weak/no binding - this is the vdW problem!")
print()
Part 5: Graphite binding energy
----------------------------------------
Interlayer binding energy (per C atom):
PBE (no vdW): 15.3 meV
PBE-D3(BJ): -52.3 meV
Experimental: ~52 meV
Note: PBE shows weak/no binding - this is the vdW problem!
Summary#
print("=" * 60)
print("Summary")
print("=" * 60)
print()
print("Available dispersion corrections:")
print()
print(" Empirical (pairwise):")
print(" - D2: Original Grimme, fixed C6")
print(" - D3: Geometry-dependent C6")
print(" - D3-BJ: D3 with Becke-Johnson damping (recommended)")
print(" - TS: Tkatchenko-Scheffler, uses electron density")
print()
print(" Nonlocal (density-based):")
print(" - vdW-DF family: Physical but expensive")
print(" - SCAN+rVV10: Meta-GGA with nonlocal correlation")
print()
print("Recommendations:")
print(" - D3-BJ: Good accuracy, low cost (default choice)")
print(" - TS-SCS: Better for metals and surfaces")
print(" - vdW-DF: When electron density matters")
print()
print("Demo settings used:")
print(" - ENCUT = 400 eV (adequate for C)")
print(" - Reduced k-points (8x8x4)")
print(" - Fewer c values scanned")
print()
print("Next: Try 15_workflows/ for automated calculation pipelines.")
============================================================
Summary
============================================================
Available dispersion corrections:
Empirical (pairwise):
- D2: Original Grimme, fixed C6
- D3: Geometry-dependent C6
- D3-BJ: D3 with Becke-Johnson damping (recommended)
- TS: Tkatchenko-Scheffler, uses electron density
Nonlocal (density-based):
- vdW-DF family: Physical but expensive
- SCAN+rVV10: Meta-GGA with nonlocal correlation
Recommendations:
- D3-BJ: Good accuracy, low cost (default choice)
- TS-SCS: Better for metals and surfaces
- vdW-DF: When electron density matters
Demo settings used:
- ENCUT = 400 eV (adequate for C)
- Reduced k-points (8x8x4)
- Fewer c values scanned
Next: Try 15_workflows/ for automated calculation pipelines.