05 - Density of States#

This script calculates and plots the electronic density of states (DOS)for silicon, demonstrating both total DOS and atom-projected DOS.

import numpy as np
from ase.build import bulk

from vasp import Vasp

# Try to import matplotlib for plotting
try:
    import matplotlib.pyplot as plt
    HAS_MATPLOTLIB = True
except ImportError:
    HAS_MATPLOTLIB = False
    print("Note: matplotlib not found. Plots will be skipped.")

Step 1: Self-consistent calculation#

print("=" * 60)
print("Density of States Calculation")
print("=" * 60)
print()

atoms = bulk('Si', 'diamond', a=5.43)

print("Step 1: Self-consistent ground state calculation")
print()

calc_scf = Vasp(
    label='results/dos/scf',
    atoms=atoms,
    xc='PBE',
    encut=400,
    kpts=(8, 8, 8),  # Dense k-grid for accurate DOS
    ismear=1,
    sigma=0.1,
    lwave=True,   # Need wavefunction for DOS
    lcharg=True,  # Need charge density
)

energy = calc_scf.potential_energy
print(f"  Total energy: {energy:.6f} eV")
print(f"  Fermi level: {calc_scf.results.get('fermi_level', 'N/A'):.4f} eV")
print()
============================================================
Density of States Calculation
============================================================

Step 1: Self-consistent ground state calculation

  Total energy: 4.020136 eV
  Fermi level: 5.8238 eV

Step 2: DOS calculation (non-self-consistent)#

print("Step 2: Non-self-consistent DOS calculation")
print()

calc_dos = Vasp(
    label='results/dos/dos',
    atoms=atoms,
    xc='PBE',
    encut=400,
    kpts=(12, 12, 12),  # Even denser grid for smooth DOS
    ismear=-5,          # Tetrahedron method for accurate DOS
    icharg=11,          # Read charge from previous calculation
    lorbit=11,          # Projected DOS with all quantum numbers
    nedos=2001,         # Number of DOS points

    # Reference the previous calculation
    lwave=False,
    lcharg=False,
)

# Copy charge density from SCF calculation
import os
import shutil

scf_dir = calc_scf.directory
dos_dir = calc_dos.directory
os.makedirs(dos_dir, exist_ok=True)

for f in ['CHGCAR', 'CHG']:
    src = os.path.join(scf_dir, f)
    dst = os.path.join(dos_dir, f)
    if os.path.exists(src):
        shutil.copy(src, dst)

energy_dos = calc_dos.potential_energy
print("  Calculation complete")
print()
Step 2: Non-self-consistent DOS calculation

  Calculation complete

Step 3: Read and analyze DOS#

print("Step 3: Reading DOS data")
print()

dos_data = calc_dos.read_doscar()

energy_grid = dos_data['energy']
total_dos = dos_data['total_dos']
fermi = dos_data['fermi']

print(f"  Energy range: {energy_grid[0]:.2f} to {energy_grid[-1]:.2f} eV")
print(f"  Fermi level: {fermi:.4f} eV")
print(f"  Number of points: {len(energy_grid)}")
print()

# Calculate band gap from DOS
band_gap = calc_dos.get_band_gap_from_doscar(tol=0.1)
print(f"  Band gap (from DOS): {band_gap:.3f} eV")
print("  Experimental band gap: 1.12 eV")
print("  Note: DFT-PBE underestimates band gaps")
print()
Step 3: Reading DOS data

  Energy range: -7.20 to 16.76 eV
  Fermi level: 6.4849 eV
  Number of points: 2001

  Band gap (from DOS): 0.839 eV
  Experimental band gap: 1.12 eV
  Note: DFT-PBE underestimates band gaps
/home/jovyan/vasp/vasp/mixins/analysis.py:407: VisibleDeprecationWarning: Creating an ndarray from ragged nested sequences (which is a list-or-tuple of lists-or-tuples-or ndarrays with different lengths or shapes) is deprecated. If you meant to do this, you must specify 'dtype=object' when creating the ndarray.
  projected.append(np.array(atom_dos))

Step 4: Plot DOS#

if HAS_MATPLOTLIB:
    fig, axes = plt.subplots(2, 1, figsize=(10, 8), sharex=True)

    # Shift energy to Fermi level
    e_shifted = energy_grid - fermi

    # Plot total DOS
    ax1 = axes[0]
    ax1.fill_between(e_shifted, 0, total_dos, alpha=0.3, label='results/Total DOS')
    ax1.plot(e_shifted, total_dos, 'b-', linewidth=1)
    ax1.axvline(x=0, color='k', linestyle='--', linewidth=1, label='results/Fermi level')
    ax1.set_ylabel('DOS (states/eV)', fontsize=12)
    ax1.set_title('Silicon Density of States', fontsize=14)
    ax1.legend(loc='upper right')
    ax1.set_xlim(-15, 10)
    ax1.grid(True, alpha=0.3)

    # Plot projected DOS if available
    ax2 = axes[1]
    if 'projected' in dos_data and dos_data['projected']:
        # Sum over all atoms
        pdos = np.array(dos_data['projected'])
        if len(pdos.shape) == 3:
            # Shape: (natoms, nedos, norbitals)
            pdos_sum = pdos.sum(axis=0)  # Sum over atoms

            # LORBIT=11 gives: s, py, pz, px, dxy, dyz, dz2, dxz, dx2-y2
            # Simplified: just plot s and p
            if pdos_sum.shape[1] >= 4:
                s_dos = pdos_sum[:, 0]
                p_dos = pdos_sum[:, 1:4].sum(axis=1)

                ax2.fill_between(e_shifted, 0, s_dos, alpha=0.3, label='results/s')
                ax2.fill_between(e_shifted, 0, p_dos, alpha=0.3, label='results/p')
                ax2.plot(e_shifted, s_dos, 'b-', linewidth=1)
                ax2.plot(e_shifted, p_dos, 'r-', linewidth=1)
        ax2.axvline(x=0, color='k', linestyle='--', linewidth=1)
        ax2.set_ylabel('Projected DOS (states/eV)', fontsize=12)
        ax2.legend(loc='upper right')
    else:
        ax2.text(0.5, 0.5, 'Projected DOS not available\n(requires LORBIT=11)',
                transform=ax2.transAxes, ha='center', va='center')

    ax2.set_xlabel('Energy - E_F (eV)', fontsize=12)
    ax2.set_xlim(-15, 10)
    ax2.grid(True, alpha=0.3)

    plt.tight_layout()
    plt.savefig('silicon_dos.png', dpi=150)
    print("Saved plot: silicon_dos.png")

print()
print("=" * 60)
print("DOS calculation complete!")
print("=" * 60)
print()
print("Key points:")
print("  - Use dense k-grids for smooth DOS")
print("  - Tetrahedron method (ISMEAR=-5) gives best DOS")
print("  - LORBIT=11 enables atom and orbital projections")
print("  - PBE underestimates band gaps (use HSE06 for accuracy)")
print()
print("Next: Try 06_band_structure/ to plot band structure along k-paths.")
/tmp/ipykernel_13237/85533621.py:22: VisibleDeprecationWarning: Creating an ndarray from ragged nested sequences (which is a list-or-tuple of lists-or-tuples-or ndarrays with different lengths or shapes) is deprecated. If you meant to do this, you must specify 'dtype=object' when creating the ndarray.
  pdos = np.array(dos_data['projected'])
No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
Saved plot: silicon_dos.png

============================================================
DOS calculation complete!
============================================================

Key points:
  - Use dense k-grids for smooth DOS
  - Tetrahedron method (ISMEAR=-5) gives best DOS
  - LORBIT=11 enables atom and orbital projections
  - PBE underestimates band gaps (use HSE06 for accuracy)

Next: Try 06_band_structure/ to plot band structure along k-paths.
../_images/1aba54288439f6d6f5e0926535589dc7a93aea59e3755a9013d25842f940daea.png