import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
plt.rcParams.update({'figure.dpi': 120, 'font.size': 11})
try:
from pyscf import gto, scf, dft
PYSCF_AVAILABLE = True
except ImportError:
PYSCF_AVAILABLE = False
print("PySCF nicht gefunden. Installiere mit: uv add pyscf")10 Ü3 · Dichtefunktionaltheorie
LDA, GGA, Hybrid-Funktionale und Elektronendichte
10.1 Lernziele
Nach dieser Übung kannst du:
- DFT-Rechnungen mit LDA, GGA (PBE) und Hybrid-Funktionalen (B3LYP) durchführen
- Energien und HOMO/LUMO-Gaps mit HF und unterschiedlichen Funktionalen vergleichen
- Die Elektronendichte auf einem 3D-Gitter berechnen und schneiden
- Geometrieoptimierungen mit DFT durchführen
- Den Einfluss der Selbstwechselwirkungskorrektur (SIC) diskutieren
10.2 Aufgabe 1 · Vergleich von Funktionalen für H₂O
Berechne die Gesamtenergie und den HOMO/LUMO-Gap von H₂O mit HF und verschiedenen DFT-Funktionalen.
if not PYSCF_AVAILABLE:
raise SystemExit("PySCF benötigt")
mol_h2o = gto.Mole(
atom="O 0 0 0; H 0.757 0.586 0; H -0.757 0.586 0",
basis='6-31g*',
verbose=0
)
mol_h2o.build()
n_electrons = mol_h2o.nelectron
homo_idx = n_electrons // 2 - 1 # (geschlossenschalig, 0-indiziert)
methods = {
'HF': lambda mol: scf.RHF(mol),
'LDA': lambda mol: dft.RKS(mol, xc='lda,vwn'),
'PBE': lambda mol: dft.RKS(mol, xc='pbe,pbe'),
'BLYP': lambda mol: dft.RKS(mol, xc='b88,lyp'),
'B3LYP': lambda mol: dft.RKS(mol, xc='b3lyp'),
'PBE0': lambda mol: dft.RKS(mol, xc='pbe0'),
'M06-2X': lambda mol: dft.RKS(mol, xc='m062x'),
}
data = []
for name, builder in methods.items():
mf = builder(mol_h2o)
mf.verbose = 0
E = mf.kernel()
eps = mf.mo_energy
homo = eps[homo_idx] * 27.211
lumo = eps[homo_idx + 1] * 27.211
gap = lumo - homo
data.append({'Methode': name, 'E [Hartree]': E,
'HOMO [eV]': homo, 'LUMO [eV]': lumo, 'Gap [eV]': gap})
print(f"{name:8s}: E={E:.4f} Eh HOMO={homo:.2f} eV LUMO={lumo:.2f} eV Gap={gap:.2f} eV")
df = pd.DataFrame(data)
print()
print(df.to_string(index=False))HF : E=-76.0091 Eh HOMO=-13.54 eV LUMO=5.84 eV Gap=19.38 eV
LDA : E=-75.8409 Eh HOMO=-6.26 eV LUMO=1.23 eV Gap=7.50 eV
PBE : E=-76.3198 Eh HOMO=-6.18 eV LUMO=1.29 eV Gap=7.48 eV
BLYP : E=-76.3855 Eh HOMO=-6.07 eV LUMO=1.16 eV Gap=7.23 eV
B3LYP : E=-76.4068 Eh HOMO=-7.91 eV LUMO=1.87 eV Gap=9.78 eV
PBE0 : E=-76.3238 Eh HOMO=-8.28 eV LUMO=2.36 eV Gap=10.64 eV
M06-2X : E=-76.3721 Eh HOMO=-10.22 eV LUMO=3.28 eV Gap=13.50 eV
Methode E [Hartree] HOMO [eV] LUMO [eV] Gap [eV]
HF -76.009128 -13.539739 5.841790 19.381528
LDA -75.840921 -6.264228 1.232827 7.497055
PBE -76.319775 -6.182715 1.294792 7.477507
BLYP -76.385493 -6.069906 1.161357 7.231262
B3LYP -76.406789 -7.913954 1.865413 9.779367
PBE0 -76.323827 -8.281060 2.358992 10.640052
M06-2X -76.372096 -10.222013 3.277934 13.499947
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(13, 5))
colors = plt.cm.Set2(np.linspace(0, 1, len(df)))
# HOMO/LUMO-Energien
x = np.arange(len(df))
ax1.bar(x - 0.2, df['HOMO [eV]'], 0.35, label='HOMO', color='steelblue', alpha=0.8)
ax1.bar(x + 0.2, df['LUMO [eV]'], 0.35, label='LUMO', color='darkorange', alpha=0.8)
ax1.axhline(-12.62, ls='--', color='blue', alpha=0.5, label='IP exp. (H₂O)')
ax1.set_xticks(x)
ax1.set_xticklabels(df['Methode'], rotation=30, ha='right')
ax1.set_ylabel('Orbitalenergie / eV')
ax1.set_title('HOMO/LUMO-Energien')
ax1.legend()
# HOMO-LUMO-Gap
ax2.bar(x, df['Gap [eV]'], color=[c for c in colors], alpha=0.85)
ax2.set_xticks(x)
ax2.set_xticklabels(df['Methode'], rotation=30, ha='right')
ax2.set_ylabel('HOMO-LUMO-Gap / eV')
ax2.set_title('HOMO-LUMO-Gap')
for i, v in enumerate(df['Gap [eV]']):
ax2.text(i, v + 0.1, f'{v:.2f}', ha='center', fontsize=9)
plt.suptitle('H₂O/6-31G* – Vergleich von HF und DFT-Funktionalen')
plt.tight_layout()
plt.show()
10.3 Aufgabe 2 · Elektronendichte visualisieren
Berechne die Elektronendichte \(\rho(\mathbf{r})\) auf einem 2D-Schnitt durch das Molekül.
from pyscf import dft as pydft
mol = mol_h2o
mf_b3lyp = dft.RKS(mol, xc='b3lyp')
mf_b3lyp.verbose = 0
mf_b3lyp.kernel()
# Dichtematrix im AO-Basis
dm = mf_b3lyp.make_rdm1()
# 2D-Gitter in der Molekülebene (xz-Ebene, y=0)
N_grid = 100
x_vals = np.linspace(-4, 4, N_grid) # in Bohr
z_vals = np.linspace(-3, 5, N_grid)
# Gitterpunkte als (N², 3)-Array
xx, zz = np.meshgrid(x_vals, z_vals)
grid_coords = np.column_stack([xx.ravel(), np.zeros(N_grid**2), zz.ravel()])
# AO-Werte auf dem Gitter berechnen
ao_vals = mol.eval_gto('GTOval_sph', grid_coords) # (N_points, N_ao)
# Elektronendichte: rho = sum_ij P_ij * phi_i * phi_j
rho = np.einsum('pi,ij,pj->p', ao_vals, dm, ao_vals)
rho = rho.reshape(N_grid, N_grid)
# Kernpositionen
atom_coords_bohr = mol.atom_coords() # in Bohr
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(13, 5))
# Lineare Skala
cf1 = ax1.contourf(xx, zz, rho, levels=30, cmap='Blues')
cs1 = ax1.contour(xx, zz, rho, levels=15, colors='white', alpha=0.4, linewidths=0.5)
plt.colorbar(cf1, ax=ax1, label='ρ(r) / e Bohr⁻³')
for i, (sym, coord) in enumerate([(mol.atom_symbol(i), atom_coords_bohr[i]) for i in range(mol.natm)]):
ax1.plot(coord[0], coord[2], 'ro', ms=8)
ax1.text(coord[0]+0.1, coord[2]+0.1, sym, color='red', fontsize=10, fontweight='bold')
ax1.set_xlabel('x / Bohr')
ax1.set_ylabel('z / Bohr')
ax1.set_title('Elektronendichte ρ(r) – lineare Skala')
ax1.set_aspect('equal')
# Logarithmische Skala
rho_log = np.log10(np.maximum(rho, 1e-6))
cf2 = ax2.contourf(xx, zz, rho_log, levels=30, cmap='RdYlBu_r')
plt.colorbar(cf2, ax=ax2, label='log₁₀[ρ(r)]')
for i, (sym, coord) in enumerate([(mol.atom_symbol(i), atom_coords_bohr[i]) for i in range(mol.natm)]):
ax2.plot(coord[0], coord[2], 'ko', ms=8)
ax2.text(coord[0]+0.1, coord[2]+0.1, sym, color='black', fontsize=10, fontweight='bold')
ax2.set_xlabel('x / Bohr')
ax2.set_ylabel('z / Bohr')
ax2.set_title('Elektronendichte ρ(r) – log Skala')
ax2.set_aspect('equal')
plt.suptitle('H₂O B3LYP/6-31G* – Elektronendichte (xz-Ebene)')
plt.tight_layout()
plt.show()
# Gesamtanzahl Elektronen überprüfen (Integral der Dichte)
# Grobe Schätzung via Riemann-Summe
dx_val = x_vals[1] - x_vals[0]
dz_val = z_vals[1] - z_vals[0]
# Achtung: dies ist nur der Beitrag aus der xz-Schnittebene (×1 Bohr Tiefe)
print(f"Integral der Dichte in der xz-Ebene (×1 Bohr): {np.sum(rho)*dx_val*dz_val:.2f} Elektronen")
print(f"Gesamtelektronen (PySCF): {mol.nelectron}")
Integral der Dichte in der xz-Ebene (×1 Bohr): 11.49 Elektronen
Gesamtelektronen (PySCF): 10
10.4 Aufgabe 3 · Dispersionskorrektur (DFT-D3)
Standard-DFT-Funktionale beschreiben Van-der-Waals-Wechselwirkungen schlecht. Die Dispersionskorrektur nach Grimme (D3) behebt dies semiempirisch.
Aufgabe: Berechne die Wechselwirkungsenergie des Wasserdimers (H₂O···H₂O) mit und ohne D3-Korrektur.
# Wasserdimer (Geometrie nahe Minimum)
dimer_geom = """
O 0.000 0.000 0.000
H 0.757 0.586 0.000
H -0.757 0.586 0.000
O 0.000 0.000 2.900
H 0.757 0.586 2.900
H -0.757 0.586 2.900
"""
monomer_geom = "O 0 0 0; H 0.757 0.586 0; H -0.757 0.586 0"
# Ohne D3
mol_dim = gto.Mole(atom=dimer_geom, basis='6-31g*', verbose=0); mol_dim.build()
mol_mon = gto.Mole(atom=monomer_geom, basis='6-31g*', verbose=0); mol_mon.build()
mf_dim_b3lyp = dft.RKS(mol_dim, xc='b3lyp'); mf_dim_b3lyp.verbose = 0
E_dim = mf_dim_b3lyp.kernel()
mf_mon_b3lyp = dft.RKS(mol_mon, xc='b3lyp'); mf_mon_b3lyp.verbose = 0
E_mon = mf_mon_b3lyp.kernel()
E_int_b3lyp = (E_dim - 2 * E_mon) * 2625.5 # kJ/mol
# Mit D3 (falls dftd3 installiert, sonst manuelle Näherung)
try:
from pyscf.dftd3 import dftd3
mf_dim_d3 = dft.RKS(mol_dim, xc='b3lyp')
mf_dim_d3 = dftd3(mf_dim_d3)
mf_dim_d3.verbose = 0
E_dim_d3 = mf_dim_d3.kernel()
mf_mon_d3 = dft.RKS(mol_mon, xc='b3lyp')
mf_mon_d3 = dftd3(mf_mon_d3)
mf_mon_d3.verbose = 0
E_mon_d3 = mf_mon_d3.kernel()
E_int_d3 = (E_dim_d3 - 2 * E_mon_d3) * 2625.5
d3_label = 'B3LYP-D3'
except Exception:
E_int_d3 = None
d3_label = 'B3LYP-D3 (nicht verfügbar)'
# Experimentell: ~21 kJ/mol (SAPT Benchmark)
E_ref = -21.0 # kJ/mol (Bindungsenergie, negativ weil stabilisierend)
print("Wechselwirkungsenergie des Wasserdimers:")
print(f" B3LYP/6-31G*: {E_int_b3lyp:.2f} kJ/mol")
if E_int_d3 is not None:
print(f" B3LYP-D3: {E_int_d3:.2f} kJ/mol")
else:
print(f" {d3_label}")
print(f" Referenz (SAPT): ~{E_ref} kJ/mol")Wechselwirkungsenergie des Wasserdimers:
B3LYP/6-31G*: 12.42 kJ/mol
B3LYP-D3 (nicht verfügbar)
Referenz (SAPT): ~-21.0 kJ/mol
10.5 Aufgabe 4 · Austausch-Korrelations-Potential
Visualisiere das Austausch-Korrelations-Potential \(v_{xc}(r)\) entlang einer Linie durch das H₂-Molekül.
from pyscf.dft import numint
# H₂ entlang der z-Achse
mol_h2 = gto.Mole(
atom="H 0 0 -0.371; H 0 0 0.371",
basis='cc-pvdz', verbose=0
)
mol_h2.build()
z_line = np.linspace(-4, 4, 300)
grid_h2 = np.column_stack([np.zeros(300), np.zeros(300), z_line])
# Nur LDA-Funktionale: brauchen nur rho (kein Gradient nötig)
# GGA/Hybrid benötigen rho + Gradienten (aufwändigere Berechnung)
xc_functionals = {'LDA (VWN)': 'lda,vwn', 'LDA (PW)': 'lda,pw', 'LDA (PZ)': 'lda,pz'}
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
ni = numint.NumInt()
for xc_name, xc in xc_functionals.items():
mf = dft.RKS(mol_h2, xc=xc)
mf.verbose = 0
mf.kernel()
dm = mf.make_rdm1()
# AO-Werte auf Linie → Dichte
ao_v = mol_h2.eval_gto('GTOval_sph', grid_h2)
rho_line = np.einsum('pi,ij,pj->p', ao_v, dm, ao_v)
rho_line = np.maximum(rho_line, 1e-12) # numerische Stabilität
# LDA eval_xc: rho muss 1D sein
exc, vxc, _, _ = ni.eval_xc(xc, rho_line, spin=0)
vxc_vals = vxc[0] # v_xc für spin-up = spin-down (RKS)
axes[0].plot(z_line, rho_line, label=xc_name, lw=2)
axes[1].plot(z_line, vxc_vals, label=xc_name, lw=2)
for ax in axes:
ax.axvline(-0.371*1.889, ls=':', color='gray', alpha=0.6, label='H-Kern')
ax.axvline(+0.371*1.889, ls=':', color='gray', alpha=0.6)
ax.set_xlabel('z / Bohr')
ax.legend(fontsize=9)
axes[0].set_title('Elektronendichte ρ(z)')
axes[0].set_ylabel('ρ / e Bohr⁻³')
axes[1].set_title('xc-Potential v_xc(z) – LDA-Varianten')
axes[1].set_ylabel('v_xc / Hartree')
plt.suptitle('H₂ – Dichte und xc-Potential (LDA) entlang Bindungsachse')
plt.tight_layout()
plt.show()
10.5.1 Reflexionsfragen
- Warum beschreibt LDA die Elektronendichte von Molekülen dennoch so gut, obwohl es nur vom lokalen Wert \(\rho(\mathbf{r})\) abhängt?
- Was ist der konzeptuelle Unterschied zwischen HF und DFT bezüglich der gesuchten Grösse (Wellenfunktion vs. Elektronendichte)?
- Warum ist der HOMO-LUMO-Gap bei DFT-Funktionalen systematisch kleiner als bei HF?
- Welchen Vorteil bieten Hybrid-Funktionale wie B3LYP gegenüber reinen GGAs, und warum sind sie rechenaufwändiger?
10.6 Zusammenfassung
In dieser Übung hast du:
- LDA, GGA (PBE/BLYP) und Hybrid-Funktionale (B3LYP, PBE0) für H₂O verglichen
- Die Elektronendichte auf einem 2D-Gitter visualisiert
- Van-der-Waals-Wechselwirkungen im Wasserdimer untersucht
- Das xc-Potential verschiedener Funktionale verglichen