11 - Phonons#

This script demonstrates phonon calculations using the finite displacement method with Phonopy. Calculates phonon dispersion and density of states for silicon.

Requires: pip install phonopy

import numpy as np
from ase.build import bulk

from vasp import Vasp

# Check for phonopy
try:
    import phonopy
    from phonopy import Phonopy
    from phonopy.structure.atoms import PhonopyAtoms
    HAS_PHONOPY = True
except ImportError:
    HAS_PHONOPY = False
    print("Note: phonopy not found. Install with: pip install phonopy")

# Check for matplotlib
try:
    import matplotlib.pyplot as plt
    HAS_MATPLOTLIB = True
except ImportError:
    HAS_MATPLOTLIB = False

print("=" * 60)
print("Phonon Calculations")
print("=" * 60)
print()

if not HAS_PHONOPY:
    print("This example requires phonopy. Please install it:")
    print("  pip install phonopy")
    print()
    print("Phonon calculations allow you to compute:")
    print("  - Vibrational frequencies")
    print("  - Thermal properties (heat capacity, entropy)")
    print("  - Zero-point energy")
    print("  - Phonon band structure and DOS")
    exit(0)
============================================================
Phonon Calculations
============================================================

Part 1: Create structure and supercell#

print("Part 1: Setup structure")
print("-" * 40)
print()

# Create silicon primitive cell
si = bulk('Si', 'diamond', a=5.43)
print(f"Primitive cell: {len(si)} atoms")
print()

# Define supercell matrix
supercell_matrix = [[2, 0, 0], [0, 2, 0], [0, 0, 2]]
print(f"Supercell: 2×2×2 = {2**3} primitive cells")
print()

# Convert ASE atoms to Phonopy format
def ase_to_phonopy(atoms):
    """Convert ASE Atoms to PhonopyAtoms."""
    return PhonopyAtoms(
        symbols=atoms.get_chemical_symbols(),
        cell=atoms.cell,
        scaled_positions=atoms.get_scaled_positions(),
    )

phonopy_atoms = ase_to_phonopy(si)
phonon = Phonopy(phonopy_atoms, supercell_matrix)

# Generate displaced supercells
phonon.generate_displacements(distance=0.01)  # 0.01 Å displacement
supercells = phonon.supercells_with_displacements

print(f"Number of displaced supercells: {len(supercells)}")
print(f"Atoms per supercell: {len(supercells[0])}")
print()
Part 1: Setup structure
----------------------------------------

Primitive cell: 2 atoms

Supercell: 2×2×2 = 8 primitive cells

Number of displaced supercells: 1
Atoms per supercell: 16

Part 2: Calculate forces for each displacement#

print("Part 2: Force calculations")
print("-" * 40)
print()

def phonopy_to_ase(phonopy_atoms):
    """Convert PhonopyAtoms to ASE Atoms."""
    from ase import Atoms
    return Atoms(
        symbols=phonopy_atoms.symbols,
        cell=phonopy_atoms.cell,
        scaled_positions=phonopy_atoms.scaled_positions,
        pbc=True,
    )

forces_sets = []

for i, sc in enumerate(supercells):
    print(f"  Calculating displacement {i+1}/{len(supercells)}...")

    atoms = phonopy_to_ase(sc)

    calc = Vasp(
        label=f'results/phonons/disp_{i:03d}',
        atoms=atoms,
        xc='PBE',
        encut=400,
        kpts=(4, 4, 4),  # Reduced k-points for supercell
        ismear=0,
        sigma=0.05,
        ibrion=-1,  # No ionic relaxation
        nsw=0,
    )

    atoms.calc = calc
    forces = atoms.get_forces()
    forces_sets.append(forces)

print()
print(f"Collected forces for {len(forces_sets)} configurations")
print()
Part 2: Force calculations
----------------------------------------

  Calculating displacement 1/1...

Collected forces for 1 configurations

Part 3: Calculate phonon properties#

print("Part 3: Phonon analysis")
print("-" * 40)
print()

# Set forces in phonopy
phonon.forces = forces_sets

# Calculate force constants
phonon.produce_force_constants()
print("Force constants calculated")
print()

# Set up mesh for DOS
mesh = [20, 20, 20]
phonon.run_mesh(mesh)
print(f"Phonon mesh: {mesh}")

# Calculate total DOS
phonon.run_total_dos()
total_dos = phonon.total_dos

# Get frequencies at Gamma
phonon.run_qpoints([[0, 0, 0]])
gamma_freqs = phonon.get_qpoints_dict()['frequencies'][0]

print()
print("Phonon frequencies at Γ point (THz):")
for i, freq in enumerate(gamma_freqs):
    mode_type = "acoustic" if abs(freq) < 0.5 else "optical"
    print(f"  Mode {i+1}: {freq:8.4f} THz ({mode_type})")

print()

# Optical phonon frequency
optical_freqs = [f for f in gamma_freqs if abs(f) > 0.5]
if optical_freqs:
    print(f"Optical phonon frequency: {optical_freqs[0]:.2f} THz")
    print("Experimental: 15.5 THz")
print()
Part 3: Phonon analysis
----------------------------------------

Force constants calculated

Phonon mesh: [20, 20, 20]

Phonon frequencies at Γ point (THz):
  Mode 1:  -0.0000 THz (acoustic)
  Mode 2:  -0.0000 THz (acoustic)
  Mode 3:  -0.0000 THz (acoustic)
  Mode 4:  15.3804 THz (optical)
  Mode 5:  15.3804 THz (optical)
  Mode 6:  15.3804 THz (optical)

Optical phonon frequency: 15.38 THz
Experimental: 15.5 THz

Part 4: Thermal properties#

print("Part 4: Thermal properties")
print("-" * 40)
print()

# Calculate thermal properties
phonon.run_thermal_properties(t_min=0, t_max=800, t_step=10)

# Get thermal properties as dict with keys: temperatures, free_energy, entropy, heat_capacity
tp = phonon.get_thermal_properties_dict()
temps = tp['temperatures']
free_energy = tp['free_energy']
entropy = tp['entropy']
heat_capacity = tp['heat_capacity']

# Room temperature values
idx_300K = np.argmin(np.abs(temps - 300))

print("At T = 300 K:")
print(f"  Free energy: {free_energy[idx_300K]:.3f} kJ/mol")
print(f"  Entropy: {entropy[idx_300K]:.3f} J/mol/K")
print(f"  Heat capacity: {heat_capacity[idx_300K]:.3f} J/mol/K")
print("  Experimental Cv: ~20 J/mol/K")
print()

# Zero-point energy
zpe = free_energy[0]  # At T=0
print(f"Zero-point energy: {zpe:.3f} kJ/mol")
print()
Part 4: Thermal properties
----------------------------------------

At T = 300 K:
  Free energy: 6.402 kJ/mol
  Entropy: 40.505 J/mol/K
  Heat capacity: 39.514 J/mol/K
  Experimental Cv: ~20 J/mol/K

Zero-point energy: 11.907 kJ/mol

Part 5: Phonon band structure#

print("Part 5: Phonon band structure")
print("-" * 40)
print()

# Set up band structure path for FCC
band_labels = ['$\\Gamma$', 'X', 'U|K', '$\\Gamma$', 'L']
bands = [
    [[0, 0, 0], [0.5, 0, 0.5]],        # Gamma -> X
    [[0.5, 0, 0.5], [0.625, 0.25, 0.625]],  # X -> U
    [[0.375, 0.375, 0.75], [0, 0, 0]],  # K -> Gamma
    [[0, 0, 0], [0.5, 0.5, 0.5]],       # Gamma -> L
]

phonon.run_band_structure(bands, with_eigenvectors=False, labels=band_labels)
band_dict = phonon.get_band_structure_dict()

print("Band structure calculated along:")
print("  Γ → X → U|K → Γ → L")
print()
Part 5: Phonon band structure
----------------------------------------

Band structure calculated along:
  Γ → X → U|K → Γ → L

Part 6: Plot results#

if HAS_MATPLOTLIB:
    print("Part 6: Generating plots")
    print("-" * 40)
    print()

    fig, axes = plt.subplots(2, 2, figsize=(12, 10))

    # Phonon DOS
    ax1 = axes[0, 0]
    freq_points = total_dos.frequency_points
    dos_values = total_dos.dos
    ax1.fill_between(freq_points, 0, dos_values, alpha=0.3)
    ax1.plot(freq_points, dos_values, 'b-', linewidth=1)
    ax1.set_xlabel('Frequency (THz)')
    ax1.set_ylabel('DOS')
    ax1.set_title('Phonon Density of States')
    ax1.set_xlim(0, None)
    ax1.grid(True, alpha=0.3)

    # Phonon band structure
    ax2 = axes[0, 1]
    distances = band_dict['distances']
    frequencies = band_dict['frequencies']

    for segment_distances, segment_freqs in zip(distances, frequencies):
        for band in range(segment_freqs.shape[1]):
            ax2.plot(segment_distances, segment_freqs[:, band], 'b-', linewidth=1)

    # Add labels
    special_points = [0]
    for d in distances:
        special_points.append(d[-1])
    ax2.set_xticks(special_points)
    ax2.set_xticklabels(band_labels)
    for sp in special_points:
        ax2.axvline(x=sp, color='gray', linestyle='-', linewidth=0.5)

    ax2.set_ylabel('Frequency (THz)')
    ax2.set_title('Phonon Band Structure')
    ax2.set_ylim(0, None)
    ax2.grid(True, axis='y', alpha=0.3)

    # Thermal properties
    ax3 = axes[1, 0]
    ax3.plot(temps, heat_capacity, 'r-', linewidth=2, label='results/Cv')
    ax3.axhline(y=24.94, color='k', linestyle='--', label='results/Dulong-Petit limit')
    ax3.set_xlabel('Temperature (K)')
    ax3.set_ylabel('Heat Capacity (J/mol/K)')
    ax3.set_title('Heat Capacity')
    ax3.legend()
    ax3.grid(True, alpha=0.3)

    # Free energy
    ax4 = axes[1, 1]
    ax4.plot(temps, free_energy, 'g-', linewidth=2)
    ax4.axhline(y=0, color='k', linestyle='-', linewidth=0.5)
    ax4.set_xlabel('Temperature (K)')
    ax4.set_ylabel('Helmholtz Free Energy (kJ/mol)')
    ax4.set_title('Vibrational Free Energy')
    ax4.grid(True, alpha=0.3)

    plt.tight_layout()
    plt.savefig('silicon_phonons.png', dpi=150)
    print("  Saved plot: silicon_phonons.png")
    print()
Part 6: Generating plots
----------------------------------------

  Saved plot: silicon_phonons.png
../_images/2945bdef15d596201e8f795fc9b1530664e727acdaac1e1cd3ee761de5f80343.png

Summary#

print("=" * 60)
print("Summary")
print("=" * 60)
print()
print("Silicon phonon properties:")
print(f"  Optical frequency at Γ: {optical_freqs[0]:.1f} THz (exp: 15.5 THz)")
print(f"  Zero-point energy: {zpe:.1f} kJ/mol")
print(f"  Heat capacity (300K): {heat_capacity[idx_300K]:.1f} J/mol/K")
print()
print("Key points:")
print("  - Use finite displacement method with Phonopy")
print("  - Supercell size affects accuracy")
print("  - Include ZPE for accurate thermodynamics")
print("  - Phonon dispersion reveals lattice dynamics")
print()
print("Next: Try 12_dft_plus_u/ for strongly correlated systems.")
============================================================
Summary
============================================================

Silicon phonon properties:
  Optical frequency at Γ: 15.4 THz (exp: 15.5 THz)
  Zero-point energy: 11.9 kJ/mol
  Heat capacity (300K): 39.5 J/mol/K

Key points:
  - Use finite displacement method with Phonopy
  - Supercell size affects accuracy
  - Include ZPE for accurate thermodynamics
  - Phonon dispersion reveals lattice dynamics

Next: Try 12_dft_plus_u/ for strongly correlated systems.