03 - Structure Relaxation#

This tutorial demonstrates geometry optimization in VASP, including both ionic relaxation and full cell relaxation.

import numpy as np
from ase.build import bulk

from vasp import Vasp

Create a distorted silicon structure#

print("=" * 60)
print("Structure Relaxation Example")
print("=" * 60)
print()

# Start with experimental lattice constant (we'll let VASP find the PBE value)
atoms = bulk('Si', 'diamond', a=5.43)

# Slightly distort the structure to give the optimizer something to do
# Move one atom off its ideal position
atoms.positions[1] += [0.1, 0.05, -0.08]

print("Initial structure:")
print(f"  Lattice constant: {atoms.cell[0, 1]:.4f} Å")
print(f"  Atom 1 position: {atoms.positions[0]}")
print(f"  Atom 2 position: {atoms.positions[1]}")
print()
============================================================
Structure Relaxation Example
============================================================

Initial structure:
  Lattice constant: 2.7150 Å
  Atom 1 position: [0. 0. 0.]
  Atom 2 position: [1.4575 1.4075 1.2775]

Part 1: Ionic Relaxation (ISIF=2)#

print("=" * 60)
print("Part 1: Ionic Relaxation (ISIF=2)")
print("=" * 60)
print()
print("Relaxing atomic positions at fixed cell...")
print()

calc_ionic = Vasp(
    label='results/relax_ionic',
    atoms=atoms.copy(),
    xc='PBE',
    encut=400,
    kpts=(4, 4, 4),
    ismear=1,
    sigma=0.1,

    # Relaxation settings
    ibrion=2,      # Conjugate gradient
    isif=2,        # Relax ions only, not cell
    nsw=50,        # Maximum steps
    ediffg=-0.02,  # Force convergence: |F| < 0.02 eV/Å

    lwave=False,
    lcharg=False,
)

energy_ionic = calc_ionic.potential_energy
forces_ionic = calc_ionic.results.get('forces')
max_force_ionic = np.max(np.abs(forces_ionic)) if forces_ionic is not None else 0

print("Results after ionic relaxation:")
print(f"  Energy: {energy_ionic:.6f} eV")
print(f"  Maximum force: {max_force_ionic:.4f} eV/Å")
print("  Relaxed positions:")
print(f"    Atom 1: {calc_ionic.atoms.positions[0]}")
print(f"    Atom 2: {calc_ionic.atoms.positions[1]}")
print()
============================================================
Part 1: Ionic Relaxation (ISIF=2)
============================================================

Relaxing atomic positions at fixed cell...

Results after ionic relaxation:
  Energy: -10.841449 eV
  Maximum force: 0.0144 eV/Å
  Relaxed positions:
    Atom 1: [ 0.05072978  0.02450544 -0.03995486]
    Atom 2: [1.40677022 1.38299456 1.31745486]

Part 2: Full Cell Relaxation (ISIF=3)#

print("=" * 60)
print("Part 2: Full Cell Relaxation (ISIF=3)")
print("=" * 60)
print()
print("Relaxing both atoms and cell parameters...")
print()

# Start fresh with a slightly wrong lattice constant
atoms_full = bulk('Si', 'diamond', a=5.50)  # 1% too large

calc_full = Vasp(
    label='results/relax_full',
    atoms=atoms_full.copy(),
    xc='PBE',
    encut=400,
    kpts=(4, 4, 4),
    ismear=1,
    sigma=0.1,

    # Relaxation settings
    ibrion=2,      # Conjugate gradient
    isif=3,        # Relax ions AND cell (volume + shape)
    nsw=50,        # Maximum steps
    ediffg=-0.02,  # Force convergence

    lwave=False,
    lcharg=False,
)

energy_full = calc_full.potential_energy
forces_full = calc_full.results.get('forces')
max_force_full = np.max(np.abs(forces_full)) if forces_full is not None else 0

# Calculate relaxed lattice constant
# For diamond structure, a = 2 * cell[0,1] / sqrt(2) ... but simpler to just read cell
relaxed_cell = calc_full.atoms.get_cell()
# For FCC-like cell, extract lattice constant
a_relaxed = relaxed_cell[0, 1] * 2  # Approximate

print("Results after full relaxation:")
print(f"  Energy: {energy_full:.6f} eV")
print(f"  Maximum force: {max_force_full:.4f} eV/Å")
print("  Initial lattice constant: 5.50 Å")
print(f"  Relaxed cell parameter: {relaxed_cell[0, 1]:.4f} Å")
print()
============================================================
Part 2: Full Cell Relaxation (ISIF=3)
============================================================

Relaxing both atoms and cell parameters...

Results after full relaxation:
  Energy: -10.847590 eV
  Maximum force: 0.0000 eV/Å
  Initial lattice constant: 5.50 Å
  Relaxed cell parameter: 2.7344 Å

Summary#

print("=" * 60)
print("Summary")
print("=" * 60)
print()
print("Relaxation type comparison:")
print(f"  Ionic only (ISIF=2): E = {energy_ionic:.4f} eV")
print(f"  Full cell (ISIF=3):  E = {energy_full:.4f} eV")
print()
print("Key points:")
print("  - Use ISIF=2 for surfaces (fixed vacuum)")
print("  - Use ISIF=3 for bulk structures")
print("  - PBE typically overestimates lattice constants by ~1%")
print("  - Check OUTCAR to verify convergence")
print()
print("Next: Try 04_equation_of_state/ to calculate bulk modulus.")
============================================================
Summary
============================================================

Relaxation type comparison:
  Ionic only (ISIF=2): E = -10.8414 eV
  Full cell (ISIF=3):  E = -10.8476 eV

Key points:
  - Use ISIF=2 for surfaces (fixed vacuum)
  - Use ISIF=3 for bulk structures
  - PBE typically overestimates lattice constants by ~1%
  - Check OUTCAR to verify convergence

Next: Try 04_equation_of_state/ to calculate bulk modulus.