Chapter 5
15 min read
Section 45 of 70

Optical Properties from DFT

Electronic Structure and Properties

Learning Objectives

By the end of this section you should be able to:

  1. Translate any feature of an optical absorption spectrum into the band-structure language built in §5.1: explain colour, transparency, and absorption edges as statements about En(k)E_n(\mathbf{k}).
  2. Write down — and read — the complex dielectric function ε(ω)=ε1(ω)+iε2(ω)\varepsilon(\omega) = \varepsilon_1(\omega) + i\,\varepsilon_2(\omega) and connect it to refractive index n(ω)n(\omega), extinction coefficient k(ω)k(\omega), absorption coefficient α(ω)\alpha(\omega), and reflectivity R(ω)R(\omega).
  3. Derive ε2(ω)\varepsilon_2(\omega) from Fermi's golden rule, and identify the three ingredients that sculpt every peak: the joint density of states, the momentum matrix element, and the broadening.
  4. Use the Kramers–Kronig relation to argue why ε1(ω)\varepsilon_1(\omega) is uniquely determined by ε2(ω)\varepsilon_2(\omega) — and why a wrong gap in DFT poisons both real and imaginary parts at every frequency.
  5. Run an optical calculation in VASP: set LOPTICS=.TRUE., choose NBANDS, NEDOS, and CSHIFT sensibly, and extract ε1,ε2,n,k,α\varepsilon_1, \varepsilon_2, n, k, \alpha from vasprun.xml.
One-line preview: the optical spectrum of a crystal is a one-dimensional histogram of vertical (k-conserving) jumps from occupied to unoccupied bands, weighted by how dipole-friendly each jump is. Everything else — refractive index, colour, reflectivity — is downstream.

What Optics Asks of a Crystal

Shine a laser of frequency ω\omega on a crystal and three things can happen at the interface: a fraction of the light is reflected, a fraction is transmitted, and a fraction is absorbed inside the slab. The relative weights of these three outcomes are what every optical experiment ultimately measures, and all three are encoded in a single complex-valued function of one scalar variable:

ε(ω)  =  ε1(ω)+iε2(ω)\varepsilon(\omega) \;=\; \varepsilon_1(\omega) + i\,\varepsilon_2(\omega)

the frequency-dependent dielectric function. The real part ε1\varepsilon_1 describes how polarisable the electron cloud is at frequency ω\omega — it controls phase velocity, refractive index, and the lensing of light. The imaginary part ε2\varepsilon_2 describes how dissipative the crystal is at that frequency — it controls absorption, the Beer–Lambert decay of intensity, and ultimately the colour you see.

The pivot of this section

Optics in a crystal is not a separate subject; it is the band structure read at a different angle. To get ε2(ω)\varepsilon_2(\omega), integrate over all k\mathbf{k} the rate at which a photon of energy ω\hbar\omega excites an electron from a filled band to an empty band, with momentum conserved. To get ε1\varepsilon_1, Kramers–Kronig-transform ε2\varepsilon_2. Everything else follows by algebra.


The Photon as a Vertical Arrow

From §5.1 you already know the geometry: a photon at visible frequency carries crystal momentum kphoton103A˚1k_\text{photon} \sim 10^{-3}\,\text{Å}^{-1}, which on the Brillouin-zone scale of 1A˚1\sim 1\,\text{Å}^{-1} is essentially zero. Conservation of crystal momentum therefore forces every photon-driven transition to be vertical in the band structure: kfinal=kinitial\mathbf{k}_\text{final} = \mathbf{k}_\text{initial}. On a band diagram this is a strictly upward arrow whose tail sits on an occupied band and whose head sits on an empty band.

The arrow has two readings. Length is the photon energy ω\hbar\omega. Position is a wavevector k\mathbf{k} in the Brillouin zone. A photon of energy ω\hbar\omegafinds a home in the crystal at any k\mathbf{k} where the gap between an occupied band vv and an empty band cc matches:

Ec(k)Ev(k)  =  ω.E_c(\mathbf{k}) - E_v(\mathbf{k}) \;=\; \hbar\omega.

The set of k\mathbf{k} where this equation holds is a 2-D iso-energy surface inside the BZ. Sweep ω\hbar\omega upward and the surface sweeps through the zone, gathering area. The amount of area gathered, weighted by the dipole strength of each jump, is the absorption spectrum. That single sentence is what the rest of this section unpacks.

Why 'vertical' — a 30-second derivation

Conservation of momentum reads kc=kv+qphoton\mathbf{k}_c = \mathbf{k}_v + \mathbf{q}_\text{photon}. For a 2-eV photon qphoton=ω/c103A˚1|\mathbf{q}_\text{photon}| = \omega/c \approx 10^{-3}\,\text{Å}^{-1}, which is a millionth of the Brillouin-zone diameter. To the accuracy a band-structure plot can show, qphoton0\mathbf{q}_\text{photon} \to 0. Hence vertical.


Interactive — Vertical Transitions in a 1-D Band Structure

Below is a 1-D toy crystal with two parabolic bands. Drag the photon energy slider ω\hbar\omega: amber arrows appear at every kk where Ec(k)Ev(k)=ωE_c(k) - E_v(k) = \hbar\omega. Drag the gap slider to widen or narrow the bands. Toggle Indirect to push the conduction-band minimum away from Γ\Gamma — the smallest verticalgap then sits above the true (indirect) gap, and there is a forbidden window where the photon is transparent even though there are electronic states available.

-3-2-101234Energy (eV)-1π/a-0.5π/aΓ0.5π/a1π/aCrystal momentum kVBMCBMℏω = 2.00 eVℏω = 2.00 eVMin vertical gap: 1.500 eVIndirect (true) gap: 1.500 eVvertical transitions allowed at k = -0.37, 0.37 π/a
Cyan curve: valence band. Purple curve: conduction band. Amber arrows: vertical (k-conserving) photon transitions. Toggle “Indirect” to see why the absorption edge sits above the true gap when the CBM is shifted.
  1. With indirect off, sweep ω\hbar\omega from below the gap upward. The first arrow appears exactly at ω=Eg\hbar\omega = E_g at k=0k = 0. Above that, two arrows appear, symmetric about k=0k = 0. That is the direct-gap absorption edge.
  2. Switch indirect on. The arrows shift, and the smallest one no longer sits at k=0k = 0 — the true minimum gap (red dashed line) is now diagonal. First-order (vertical) absorption begins at a higher energy than the true gap. The gap below this onset is bridged only with phonon assistance and is therefore weak.
  3. Push EgE_g to 0.5 eV (infrared), to 1.5 eV (visible red — like Mn:CdSe), and to 3.0 eV (UV). The colour of the crystal is not a separate property; it is the position of the first arrow.

The Dielectric Function: What DFT Actually Computes

Maxwell's equations in matter relate the electric displacement D\mathbf{D} and the electric field E\mathbf{E} through D(ω)=ε0ε(ω)E(ω)\mathbf{D}(\omega) = \varepsilon_0\,\varepsilon(\omega)\,\mathbf{E}(\omega). For an anisotropic crystal ε\varepsilon is a 3×3 tensor; cubic crystals like zincblende CdSe have only one independent component (the diagonal), uniaxial wurtzite CdSe has two (in-plane and out-of-plane). VASP writes the full tensor for you in any case.

The complex refractive index n~(ω)=n(ω)+ik(ω)\tilde n(\omega) = n(\omega) + i\,k(\omega) is the square root:

n~(ω)2=ε(ω),n2k2=ε1,2nk=ε2.\tilde n(\omega)^2 = \varepsilon(\omega), \qquad n^2 - k^2 = \varepsilon_1, \qquad 2 n k = \varepsilon_2.

Solving this 2×2 algebra for nn and kk in terms of ε1,ε2\varepsilon_1, \varepsilon_2 gives the formulas you will see in the VASP code below:

n=12(ε+ε1),k=12(εε1),ε=ε12+ε22.n = \sqrt{\tfrac{1}{2}\big(|\varepsilon| + \varepsilon_1\big)}, \quad k = \sqrt{\tfrac{1}{2}\big(|\varepsilon| - \varepsilon_1\big)}, \quad |\varepsilon| = \sqrt{\varepsilon_1^2 + \varepsilon_2^2}.

Two derived quantities then drop out by algebra. The absorption coefficient α(ω)=2ωk(ω)/c\alpha(\omega) = 2\omega k(\omega)/c — Beer's law. The normal-incidence reflectivity R(ω)=[(n1)2+k2]/[(n+1)2+k2]R(\omega) = \big[(n-1)^2 + k^2\big]/\big[(n+1)^2 + k^2\big] — the fraction of intensity bounced off a flat surface.

QuantityFormulaWhat it tells you
ε₁(ω)real partPolarisability, refractive index, lensing
ε₂(ω)imaginary partAbsorption channel — vertical transitions
n(ω)√((|ε|+ε₁)/2)Phase velocity = c/n; Snell's law
k(ω)√((|ε|−ε₁)/2)Field decay in the medium
α(ω) = 2ωk/cfrom kIntensity decay e⁻ᵅᶻ — Beer–Lambert
R(ω)(n−1)²+k²)/((n+1)²+k²)Reflectivity at normal incidence

ε₂(ω) from Fermi's Golden Rule

The starting point is first-order perturbation theory for an electron in a Bloch state coupled to the vector potential of a photon. Fermi's golden rule gives the rate at which an electron in band vv at wavevector k\mathbf{k} jumps to band cc at the same k\mathbf{k} when bathed in a monochromatic field. Sum that rate over all initial states, divide by the photon flux, and you arrive at the textbook result:

ε2αβ(ω)  =  4π2e2m2ω21Ωc,vBZdk(2π)3  ψckpαψvkψvkpβψckδ ⁣(Ec(k)Ev(k)ω).\varepsilon_2^{\,\alpha\beta}(\omega) \;=\; \frac{4\pi^2 e^2}{m^2 \omega^2}\,\frac{1}{\Omega} \sum_{c,v} \int_{\text{BZ}} \frac{d\mathbf{k}}{(2\pi)^3}\; \langle \psi_{c\mathbf{k}} | p_\alpha | \psi_{v\mathbf{k}} \rangle\, \langle \psi_{v\mathbf{k}} | p_\beta | \psi_{c\mathbf{k}} \rangle\, \delta\!\big(E_c(\mathbf{k}) - E_v(\mathbf{k}) - \hbar\omega\big).

Three pieces of physics live in that formula. Each one is a knob you can turn in a real DFT calculation.

IngredientMeaningSet by
δ(E_c − E_v − ℏω)k-resolved energy conservation — the vertical-arrow conditionBand structure E_n(k)
⟨ψ_c|p|ψ_v⟩Momentum matrix element — strength of dipole couplingWavefunctions (orbital character, parity)
1/ω²Photon-energy prefactorDimensions; weakens UV peaks
Σ_{c,v} ∫ dk/(2π)³Sum over all valence/conduction pairs at every kSum over BZ — needs a dense k-grid

Why the formula has m² in the denominator

The momentum matrix element p\langle p \rangle carries dimensions of action·length⁻¹ (not energy), because we coupled to the velocity operator p/m\mathbf{p}/m rather than to r\mathbf{r}. The two formulations are related by p/m=(i/)[H,r]\mathbf{p}/m = (i/\hbar)[H, \mathbf{r}] and are equivalent for periodic systems — but the velocity form is what works inside a plane-wave code, because r\mathbf{r} is not a well-defined operator on Bloch states. This is why VASP's LOPTICS reports the longitudinal dielectric function via the momentum matrix.


The Joint Density of States — Where Peaks Come From

Set the matrix element to a constant and the prefactors aside. What survives in the formula is the joint density of states (JDOS):

Jcv(ω)  =  BZdk(2π)3  δ ⁣(Ec(k)Ev(k)ω).J_{cv}(\omega) \;=\; \int_{\text{BZ}} \frac{d\mathbf{k}}{(2\pi)^3}\; \delta\!\big(E_c(\mathbf{k}) - E_v(\mathbf{k}) - \hbar\omega\big).

Geometrically, Jcv(ω)J_{cv}(\omega) measures the 2-D area in the Brillouin zone on which the vertical-transition energy equals ω\hbar\omega, divided by the BZ volume. It is to optics what the regular DOS g(E)g(E) of §5.2 is to thermodynamics — the geometric factor that decides where the spectrum has structure.

Two universal features follow:

  • Square-root edge. Near a direct, parabolic minimum gap, expand EcEv=Eg+2k2/2μE_c - E_v = E_g + \hbar^2 k^2 / 2\mu with reduced mass μ=memh/(me+mh)\mu = m_e^* m_h^* / (m_e^* + m_h^*). The JDOS in 3-D is J(ω)ωEgJ(\omega) \propto \sqrt{\hbar\omega - E_g} for ωEg\hbar\omega \geq E_g, and zero below. Every direct-gap absorption edge has this shape.
  • Van Hove singularities. At wavevectors where k(EcEv)=0\nabla_{\mathbf{k}} (E_c - E_v) = 0, the JDOS develops integrable kinks: jumps in 1-D, logarithmic divergences in 2-D, square-root cusps in 3-D. The named peaks of a real spectrum (E0,E1,E2E_0, E_1, E_2 in semiconductors) are van Hove singularities of the conduction– valence energy difference.

An analogy that survives in 3-D

Think of Ec(k)Ev(k)E_c(\mathbf{k}) - E_v(\mathbf{k}) as the height function on a 3-D landscape (the Brillouin zone). The JDOS is the area of the iso-height contour. Where the landscape has a saddle, the contour is forced to detour around it and the area spikes — that is your van Hove peak. Optical spectroscopy is topography of the conduction–valence gap.


Interactive — Building ε(ω) from a JDOS

The widget below builds ε2(ω)\varepsilon_2(\omega) from a parabolic-JDOS model with adjustable gap EgE_g and Lorentzian broadening Γ\Gamma. It then computes ε1(ω)\varepsilon_1(\omega) numerically from ε2\varepsilon_2 via the Kramers–Kronig integral — the two curves are not independent. Switch the matrix element between "direct-allowed" (the canonical ωEg\sqrt{\hbar\omega - E_g} edge) and "forbidden" (a softer (ωEg)3/2(\hbar\omega - E_g)^{3/2} onset) to see how the spectrum reshapes.

Transition matrix element
-202468ε(ω)0123456Photon energy ℏω (eV)Egε₁(ω) — refraction (KK)ε₂(ω) — absorptionα(ω) ∝ ω·ε₂/n (scaled)
ε₂ is built from a parabolic joint density of states, broadened by Γ. ε₁ is computed numerically from ε₂ via the Kramers–Kronig relation — the two curves are not independent. Compare “allowed” (sharp √-edge) with “forbidden” (delayed onset, weaker peak).

Pay attention to four things as you slide:

  1. The threshold. ε2(ω)\varepsilon_2(\omega) is exactly zero for ω<Eg\hbar\omega < E_g. The crystal is transparent below the gap. This is why pure silicon (Eg=1.1E_g = 1.1 eV) is opaque to visible light but transparent in the mid-infrared.
  2. The shape near the threshold. Allowed transitions give a vertical-tangent square-root rise; forbidden ones give a horizontal-tangent (EEg)3/2(E - E_g)^{3/2} rise. That is a fingerprint you can read off an experimental absorption spectrum.
  3. The Kramers–Kronig coupling. ε1\varepsilon_1 develops a peak just below the absorption onset and dips to a minimum just above — this is the universal dispersion shape every refractive-index curve shows near an absorption band.
  4. Broadening. Increasing Γ\Gamma smooths peaks but conserves the area under ε2(ω)\varepsilon_2(\omega) (the f-sum rule). In a real DFT calculation, Γ\Gamma is set by the CSHIFT tag.

Kramers–Kronig: Why ε₁ Is Not Independent of ε₂

Causality — the crystal cannot respond before the field arrives — forces ε(ω)\varepsilon(\omega) to be analytic in the upper half complex-frequency plane. A standard contour argument then gives the Kramers–Kronig relation:

ε1(ω)1  =  2πP ⁣0ωε2(ω)ω2ω2dω.\varepsilon_1(\omega) - 1 \;=\; \frac{2}{\pi}\,\mathcal{P}\!\int_0^\infty \frac{\omega'\,\varepsilon_2(\omega')}{\omega'^2 - \omega^2}\,d\omega'.

Two consequences worth burning in:

  • You only need ε₂. If you have computed ε2\varepsilon_2 well, you do not need a separate calculation for ε1\varepsilon_1. VASP applies the KK transform internally to give you both.
  • Errors propagate everywhere. A wrong absorption edge — say, the notorious DFT band-gap underestimation that we will fix with hybrids in §6.4 — is not a localised error. It poisons ε1\varepsilon_1 at everyfrequency. Static dielectric constants, refractive indices, Hamaker coefficients of dispersion forces — all of them inherit the gap error through KK.

The f-sum rule — a sanity check you can run on any spectrum

Causality plus the high-frequency limit imply 0ωε2(ω)dω=(π/2)ωp2\int_0^\infty \omega\,\varepsilon_2(\omega)\,d\omega = (\pi/2)\,\omega_p^2, with ωp2=4πnee2/m\omega_p^2 = 4\pi n_e e^2/m the plasma frequency of the total electron density. After running an optical calculation, integrate ωε2\omega\,\varepsilon_2 from 0 to a generous cutoff and compare against this analytic value: agreement to a few percent is a strong indicator that NBANDS, NEDOS, and the k-grid are converged.


Selection Rules and the Momentum Matrix Element

The matrix element ψckpψvk\langle \psi_{c\mathbf{k}} | \mathbf{p} | \psi_{v\mathbf{k}} \rangle is the genetic sequence of the spectrum. Two of its features matter most:

  • Symmetry zeros. If the valence and conduction states at k\mathbf{k} belong to irreducible representations whose direct product does not contain the representation of p\mathbf{p}, the matrix element vanishes by symmetry. This is the same selection rule you met in the H-atom — no sss \to s dipole transition — applied to crystal point groups and §2.7 direct products.
  • Orbital character. The matrix element peaks when the valence orbital and conduction orbital share spatial overlap and have opposite parity (s ↔ p, p ↔ d). In CdSe the VBM is mostly Se-4p and the CBM is mostly Cd-5s; this s-p product islarge, which is one reason CdSe absorbs strongly.
CrystalVBM characterCBM characterSpectrum at edge
GaAsAs 4p (Γ₈)Ga 4s (Γ₆)Strong allowed √-edge at 1.42 eV
CdSe (zincblende)Se 4pCd 5sStrong allowed √-edge at 1.74 eV
Si (indirect)Si 3p (Γ′₂₅)Si 3s near X (X₁)Vertical onset at 3.4 eV (E₀); indirect 1.1 eV is phonon-assisted, weak
MoS₂ (monolayer)Mo 4dMo 4dDirect edge at K, but d-d → weak; saved by spin-orbit splitting

Practical implication for VASP

When you set LOPTICS=.TRUE., VASP computes the momentum matrix elements within the PAW formalism — which means it adds the on-site contributions inside each augmentation sphere that a naive plane-wave calculation would miss. This is one place where the all-electron flavour of PAW (§4.8) really earns its keep: the matrix elements at the band edges are dominated by orbital character close to the nucleus, exactly the region where PAW corrections matter.


From ε(ω) to What an Experimentalist Sees

A spectroscopist does not measure ε\varepsilon directly; they measure intensity ratios. Three derived quantities bridge the theorist'sε\varepsilon to those measurements:

ExperimentMeasuresComputed from ε(ω) as
Transmission spectroscopyα(ω) — absorption coefficientα = 2ωk/c with k = √((|ε|−ε₁)/2)
Ellipsometryn(ω) and k(ω) directlyn,k from ε via the same algebra
Reflectivity (UV-Vis)R(ω) at normal incidenceR = ((n−1)² + k²) / ((n+1)² + k²)
Energy-loss spectroscopyIm[−1/ε(ω)]ε₂ / (ε₁² + ε₂²) — peaks at plasmons
Static dielectric ε₀ε₁(0)Read off ε₁ at ω=0 — converges if KK has enough headroom

Notice the round trip. DFT computes ε\varepsilon; algebra produces n,k,α,Rn, k, \alpha, R; the experimentalist measures one of those. To compare with experiment you simply look up the right algebraic combination of ε1\varepsilon_1 and ε2\varepsilon_2. That is why optical-output figures in the VASP literature usually plot the same underlying spectrum in three or four guises on the same page.


VASP — LOPTICS, NBANDS, and the Optical Tensor

Optical calculations in VASP run on top of a converged self- consistent ground state, exactly like the band-structure recipe of §5.1. The two extras you need are an LOPTICS flag and a much larger NBANDS than usual, because the sum over conduction states must be carried high enough into the empty manifold for the spectrum to be saturated up to your largest photon energy of interest.

Step 1 — converged SCF (as before)

📝text
1# INCAR  (step 1: self-consistent ground state)
2SYSTEM = CdSe zincblende — SCF
3PREC   = Accurate
4ENCUT  = 400
5EDIFF  = 1E-7        # tighter than usual — KK is sensitive to noise
6ISMEAR = 0
7SIGMA  = 0.05
8LCHARG = .TRUE.      # write CHGCAR for step 2
9LWAVE  = .TRUE.      # write WAVECAR for step 2

Step 2 — non-SCF + LOPTICS

📝text
1# INCAR  (step 2: optical / dielectric tensor)
2SYSTEM   = CdSe zincblende — optics
3PREC     = Accurate
4ENCUT    = 400
5ICHARG   = 11        # read CHGCAR; non-self-consistent
6ISMEAR   = 0
7SIGMA    = 0.05
8NBANDS   = 96        # ≈ 4–6× number of valence bands; converge!
9LOPTICS  = .TRUE.    # compute frequency-dependent dielectric tensor
10NEDOS    = 2000      # frequency-grid points for ε(ω)
11CSHIFT   = 0.10      # Lorentzian broadening Γ in eV
12LREAL    = .FALSE.   # full reciprocal-space projectors — accuracy first
📝text
1# KPOINTS  (step 2: dense uniform grid; bigger than the SCF grid)
2Γ-centred dense grid for optical convergence
30
4Gamma
512 12 12
60  0  0

Why the k-grid in step 2 is larger than in step 1

ε₂(ω) is an integral over the Brillouin zone, and the integrand — a δ-function broadened by CSHIFT — is much sharper than the ground-state energy density. The SCF k-grid only needs to converge total energy and forces; the optics grid needs to converge the JDOS, which often takes 2–3× more k-points along each axis. Always converge optics with respect to the k-mesh, NBANDS, NEDOS, and CSHIFT independently.

Four convergence dials, in order of brutality

  1. NBANDS: include enough conduction bands to saturate the highest ω\hbar\omega you care about. Rule of thumb: NBANDS ≈ 4–6 × valence bands. Too few and the f-sum rule fails by 10–30%.
  2. k-grid: typically 2× denser than the SCF grid. For zincblende CdSe a 12³ Γ-centred grid is a sensible start.
  3. NEDOS: frequency resolution. 2000 points to 30 eV gives 15 meV resolution — plenty for visible-range physics.
  4. CSHIFT: 0.05–0.10 eV is standard. Smaller values give sharper peaks but require denser k-grids; larger values smear physics that should be visible. Never set CSHIFT below the k-grid's natural energy resolution.

Reading the output: vasprun.xml → pymatgen

Everything ends up in vasprun.xml. Pymatgen exposes it as a 3-tuple (ωi,ε1ab(ωi),ε2ab(ωi))(\omega_i, \varepsilon_1^{ab}(\omega_i), \varepsilon_2^{ab}(\omega_i)) with six independent tensor components per frequency. The script below reads that tuple, derives n,k,αn, k, \alpha from the formulas of the previous section, and saves a publication- ready figure in eight executable lines.

optics.py — extract ε₁, ε₂, n, k, α from vasprun.xml
🐍optics.py
1from pymatgen.io.vasp import Vasprun

Pymatgen is the de-facto post-processing library for VASP. Vasprun is a parser for vasprun.xml — the single XML file VASP writes that contains every input setting, every computed eigenvalue, every k-point, the converged charge density metadata, and (when LOPTICS=.TRUE.) the frequency-dependent dielectric tensor. We import only the parser class to avoid pulling in the rest of pymatgen.

EXECUTION STATE
📚 pymatgen.io.vasp = Pymatgen submodule containing VASP-specific parsers: Vasprun (vasprun.xml), Outcar, Procar, Chgcar, Eigenval, etc.
Vasprun = Class that lazily parses vasprun.xml. Exposes attributes such as .dielectric, .eigenvalues, .tdos, .efermi, .final_structure.
2import numpy as np

NumPy provides ndarray — a fast, vectorised numerical-array type. We will turn pymatgen's Python lists of dielectric tensors into NumPy arrays so we can use vectorised arithmetic (np.sqrt, **2, *, /) on the entire frequency grid at once instead of writing a Python for-loop.

EXECUTION STATE
np = Conventional alias for numpy. Provides np.array, np.sqrt, np.linspace, broadcasting, slicing — everything we need for the optical-constant arithmetic.
3import matplotlib.pyplot as plt

Matplotlib's pyplot is the standard plotting interface in scientific Python. We will use it to draw ε₂(ω) and α(ω) on a single figure and save the result as a 200-DPI PNG.

EXECUTION STATE
plt = Conventional alias for matplotlib.pyplot. plt.plot draws a line, plt.legend adds a legend, plt.savefig writes the figure to disk.
5vr = Vasprun("vasprun.xml", parse_potcar_file=False)

Constructs a Vasprun object that parses vasprun.xml in the current directory. The argument parse_potcar_file=False tells pymatgen NOT to look for a POTCAR file alongside vasprun.xml — we don't need element pseudopotential metadata for optics, and skipping the lookup avoids a warning when POTCAR is absent.

EXECUTION STATE
📚 Vasprun(filename, parse_potcar_file) = Constructor: opens the XML file, parses input/output blocks, eigenvalues, dielectric data, etc. The parse is lazy where it can be — large fields like projected eigenvalues are only loaded when you ask for them.
⬇ arg 1: "vasprun.xml" = Path to the file VASP writes. ~50 MB for a Mn:CdSe optical run with NEDOS=2000.
⬇ arg 2: parse_potcar_file=False = Skip the POTCAR validation. Set False whenever you only need eigenvalues, dielectric, or DOS — speeds up parsing and silences a noisy warning when POTCAR is missing.
⬆ result: vr = A Vasprun instance. Attributes you'll actually touch: vr.dielectric, vr.eigenvalues, vr.efermi, vr.final_structure, vr.tdos.
6diel = vr.dielectric

Extracts the frequency-dependent dielectric data VASP wrote when LOPTICS=.TRUE.. The attribute is a 3-tuple: a list of photon energies ω in eV, a list of 6-component real-part tensors ε₁(ω) at each ω, and a matching list of imaginary-part tensors ε₂(ω). The 6 components are the symmetric Cartesian elements xx, yy, zz, xy, yz, zx.

EXECUTION STATE
vr.dielectric = Type: tuple( list[float], list[list[float]], list[list[float]] ) — (energies, real, imag). For NEDOS=2000 each list has 2000 entries.
→ diel[0] = Photon energies ω (eV). Spans 0 to roughly OMEGAMAX (about 30 eV by default), in NEDOS evenly-spaced steps.
→ diel[1] = Real part. Each entry is a 6-list [ε₁_xx, ε₁_yy, ε₁_zz, ε₁_xy, ε₁_yz, ε₁_zx] (eV-independent units).
→ diel[2] = Imaginary part, same 6-component layout.
⬆ result: diel = Bundled tuple, ready to slice into NumPy arrays.
8energies = np.array(diel[0])

Wraps the list of photon energies in a NumPy array. From here on we can do vectorised arithmetic — for example energies * k_xx — instead of iterating element by element. The energy grid is the x-axis of every plot in this analysis.

EXECUTION STATE
📚 np.array() = Builds a NumPy ndarray from a Python iterable. Float-typed by default. Example: np.array([0.0, 0.5, 1.0]).shape → (3,).
⬇ arg: diel[0] = Python list of NEDOS photon energies (eV).
⬆ result: energies = ndarray of shape (NEDOS,). Example for NEDOS=2000, OMEGAMAX≈30: array([0.000, 0.015, 0.030, …, 29.985]).
9eps1_xx = np.array([t[0] for t in diel[1]])

List comprehension that pulls the xx component (index 0 of the 6-component tensor) at every frequency, then wraps the resulting list in a NumPy array. For a cubic crystal like zincblende CdSe ε is isotropic and ε_xx = ε_yy = ε_zz, so xx alone is the whole story; for hexagonal CdSe (wurtzite) you would also need ε_zz separately.

EXECUTION STATE
→ t in diel[1] = Each t is a 6-list [xx, yy, zz, xy, yz, zx] of ε₁ components at one frequency.
→ t[0] = Pick the xx component. For CdSe zincblende: numerical values of order 6 at low ω, peak ~10 near the first absorption peak, decaying toward 1 at high ω.
📚 np.array([...]) = Same constructor as line 8, applied to the harvested xx column.
⬆ result: eps1_xx = ndarray of shape (NEDOS,). Example values near a 1.74 eV gap: ε₁(0) ≈ 6.5, ε₁(2.5 eV) ≈ 5.0, ε₁(6 eV) ≈ 1.2.
10eps2_xx = np.array([t[0] for t in diel[2]])

Same pattern, applied to the imaginary part. ε₂_xx(ω) is the absorption channel: zero below the optical gap, then a square-root onset (for direct-allowed transitions) followed by a complicated structure of van Hove peaks.

EXECUTION STATE
→ diel[2] = Imaginary-part tensor list — one 6-vector per frequency.
→ t[0] = ε₂_xx at one frequency. Identically zero below the optical gap; peaks at energies where the JDOS has van Hove singularities.
⬆ result: eps2_xx = ndarray (NEDOS,). For CdSe: zero up to ω ≈ 1.74 eV, then rises to ε₂ ≈ 8 around 2.5 eV (E₁ peak), ≈ 10 around 4.5 eV (E₂ peak), then decays.
12mod = np.sqrt(eps1_xx**2 + eps2_xx**2)

Computes |ε(ω)| element-wise on the frequency grid. Since ε(ω) = ε₁ + iε₂ is a complex number at each ω, |ε|² = ε₁² + ε₂². We will need this modulus in the next two lines to peel ε(ω) into its real-valued amplitude (n) and damping (k) parts.

EXECUTION STATE
📚 np.sqrt() = Element-wise square root for ndarrays. NaN if input is negative — but ε₁²+ε₂² ≥ 0 always, so we are safe here.
** (power) = NumPy element-wise exponentiation. eps1_xx**2 squares each entry of the array.
→ eps1_xx**2 + eps2_xx**2 = ε₁(ω)² + ε₂(ω)². For CdSe at ω = 2.5 eV: 5.0² + 8.0² = 89.0.
⬆ result: mod = |ε(ω)| = ndarray (NEDOS,). For CdSe at 2.5 eV: √89.0 ≈ 9.43. At 0 eV (transparent): √(6.5² + 0²) = 6.5.
13n_xx = np.sqrt((mod + eps1_xx) / 2.0)

Refractive index n(ω). Algebraically, ε(ω) = (n + ik)² where n, k are real. Solving for n gives n = √((|ε| + ε₁)/2). At zero frequency (transparent regime) ε₂ = 0, so |ε| = ε₁ and n = √ε₁ — the familiar √(static dielectric) result.

EXECUTION STATE
(mod + eps1_xx) / 2.0 = Half the sum of |ε| and ε₁. Example at ω=2.5 eV: (9.43 + 5.0)/2 = 7.215.
📚 np.sqrt() = Element-wise √. n is always real and ≥ 1 in our visible-range regime.
⬆ result: n_xx = ndarray (NEDOS,). CdSe at ω=0: n ≈ √6.5 ≈ 2.55. CdSe at ω=2.5 eV: √7.215 ≈ 2.69. Both numbers match measured CdSe refractive indices.
→ physical meaning = n is the slowdown factor of light in the medium: phase velocity = c/n. The fact that n drifts with ω is what makes a prism disperse white light.
14k_xx = np.sqrt((mod - eps1_xx) / 2.0)

Extinction coefficient k(ω) — the imaginary part of the refractive index. From ε = (n + ik)² we get k = √((|ε| − ε₁)/2). It is exactly zero whenever ε₂ = 0 (i.e. below the optical gap, transparent). Above the gap it scales roughly with √ε₂.

EXECUTION STATE
(mod - eps1_xx) / 2.0 = Half the difference. At ω=0 (transparent): mod = ε₁, so the difference is 0, so k = 0 — no absorption.
⬆ result: k_xx = ndarray (NEDOS,). CdSe at 2.5 eV: (9.43 − 5.0)/2 = 2.215, k = √2.215 ≈ 1.49.
→ why care? = k drives the exponential decay of light intensity inside the crystal: I(z) = I₀ e^(−αz), where α = 2ωk/c. The bigger k, the thinner the absorbing slab needed to be opaque.
15alpha = (2 * energies * k_xx) / 1.973e-5

Absorption coefficient α(ω) in inverse centimetres. The textbook formula is α = 2ωk/c, but VASP gives ω in eV. Using ℏc = 1.973·10⁻⁵ eV·cm converts the unit cleanly: α[cm⁻¹] = 2·ω[eV]·k / ℏc[eV·cm]. The factor of 2 comes from the intensity (∝ |E|²) decaying twice as fast as the field amplitude.

EXECUTION STATE
2 * energies = ndarray; each element is 2·ω in eV. Example at ω=2.5: 5.0.
* k_xx = Multiply element-wise by extinction coefficient. At ω=2.5 eV: 5.0 · 1.49 = 7.45.
1.973e-5 = ℏc in eV·cm. Numerically: ℏc = 1973 eV·Å = 1.973·10⁻⁵ eV·cm. This is a fundamental constant — memorise it; it shows up everywhere optics meets condensed matter.
⬆ result: alpha = ndarray (NEDOS,). CdSe at ω=2.5 eV: 7.45 / 1.973·10⁻⁵ ≈ 3.78·10⁵ cm⁻¹. That is a 26-nm absorption depth — perfectly consistent with a strong direct-gap semiconductor.
17plt.plot(energies, eps2_xx, label=r"$\varepsilon_2(\omega)$")

Plots ε₂(ω) as a line — the canonical absorption spectrum from DFT. The label uses raw-string LaTeX so matplotlib renders it as the Greek epsilon-with-subscript-2.

EXECUTION STATE
📚 plt.plot(x, y, label) = Pyplot line plotter. x and y are array-likes of equal length; label is the legend string.
⬇ x: energies = Photon-energy axis (NEDOS,) in eV.
⬇ y: eps2_xx = Imaginary dielectric function (NEDOS,). Zero below gap, peaks above.
⬇ label=r"$\varepsilon_2(\omega)$" = Raw string so backslash isn't an escape character; matplotlib's mathtext renders $...$ as LaTeX. Result in legend: 'ε₂(ω)'.
18plt.plot(energies, alpha, label=r"$\alpha(\omega)$")

Plots α(ω) on the same axes. In a real paper you would put α on a secondary y-axis (it's 4 orders of magnitude bigger than ε₂); here we leave it co-plotted for compactness.

EXECUTION STATE
⬇ y: alpha = Absorption coefficient (NEDOS,) in cm⁻¹. CdSe peaks at ~5·10⁵ cm⁻¹ near 4.5 eV.
→ tip = For a publication-quality figure use ax2 = plt.gca().twinx() so α and ε₂ get independent y-axes.
19plt.xlabel(...); plt.legend(); plt.savefig("optics.png", dpi=200)

Three commands chained on one physical line. xlabel sets the x-axis text, legend draws the labels we set in the two plot calls, and savefig writes the figure to disk at 200 DPI — high enough for slides and fine for print.

EXECUTION STATE
📚 plt.xlabel(s) = Set the x-axis label. We use a Unicode ℏ for compactness; matplotlib's default Unicode font handles it.
📚 plt.legend() = Auto-collects all label= strings from prior plt.plot calls and draws a legend box.
📚 plt.savefig(filename, dpi) = Writes a PNG (deduced from extension). dpi=200 is a sensible default — bigger files than dpi=100, but artefact-free in slides.
⬆ side effect = optics.png appears in the current directory. Inspect it; convince yourself the gap, the first peak, and the decay all match the band-structure picture before doing anything more sophisticated.
4 lines without explanation
1from pymatgen.io.vasp import Vasprun
2import numpy as np
3import matplotlib.pyplot as plt
4
5vr   = Vasprun("vasprun.xml", parse_potcar_file=False)
6diel = vr.dielectric                              # (energies, eps1_tensor, eps2_tensor)
7
8energies = np.array(diel[0])                      # photon energies ω in eV
9eps1_xx  = np.array([t[0] for t in diel[1]])      # real part — xx component
10eps2_xx  = np.array([t[0] for t in diel[2]])      # imaginary part — xx component
11
12mod    = np.sqrt(eps1_xx**2 + eps2_xx**2)
13n_xx   = np.sqrt((mod + eps1_xx) / 2.0)           # refractive index
14k_xx   = np.sqrt((mod - eps1_xx) / 2.0)           # extinction coefficient
15alpha  = (2 * energies * k_xx) / 1.973e-5         # absorption coefficient (1/cm)
16
17plt.plot(energies, eps2_xx, label=r"$\varepsilon_2(\omega)$")
18plt.plot(energies, alpha,   label=r"$\alpha(\omega)$")
19plt.xlabel("photon energy ℏω (eV)"); plt.legend(); plt.savefig("optics.png", dpi=200)

One-line gut check on the output

Open optics.png and confirm three things: (i) ε2(ω<Eg)0\varepsilon_2(\omega < E_g) \approx 0 (DFT correctly predicts transparency below the gap), (ii) ε2\varepsilon_2 rises with a square-root tangent at EgE_g (allowed edge), (iii) the static dielectric ε1(0)\varepsilon_1(0) matches measured CdSe values (≈ 6.2). If any of these fails, your NBANDS or k-grid is not converged — fix that before reading peak positions.


What Standard DFT Optics Misses — Excitons and the Gap

Everything we have computed sits inside the independent-particle approximation: an electron and the hole it leaves behind do not interact. Real life is not so forgiving. The ejected electron sees a Coulomb attraction to its own hole, and the bound electron–hole pair — an exciton — has internal binding energy of order meV (Wannier excitons in GaAs) up to hundreds of meV (Frenkel excitons in molecular crystals). In an absorption spectrum, excitons show up as sharp below-gap peaks and a redistribution of oscillator strength piling up near the gap. Standard DFT-LOPTICS misses both effects.

What standard DFT-LOPTICS givesWhat it missesSection that fixes it
Independent-particle ε₂(ω)Excitonic binding & redistribution§5.10 — Bethe–Salpeter equation
DFT band gap (~70% of true gap)Quasi-particle correction§5.10 — GW approximation
Local PBE/LDA functionalSelf-interaction error in the gap§6.4 — hybrids (HSE06)
No phonon couplingPhonon-assisted (indirect) absorptionBeyond this book

Take the result of this section as the floor of optical accuracy: cheap, fast, and good enough to confirm directness, find peak positions to ~10–20%, and compute static dielectric constants within a factor of two. For Mn-doped CdSe quantum dots — Chapter 7 — independent-particle DFT will tell us where the absorption begins and confirm the directness of the gap; Chapter 6's hybrid functionals will then put the band gap in the right place; a Bethe–Salpeter calculation (briefly previewed in §5.10) is what you run if you need exciton-binding energies to within meV.


Summary

  • A photon in a crystal is a vertical arrow in the band structure: head on a conduction band, tail on a valence band, at the same k\mathbf{k}, with length ω\hbar\omega. The set of allowed k\mathbf{k} at each ω\omega defines an iso-energy surface in the BZ.
  • The complex dielectric function ε(ω)=ε1+iε2\varepsilon(\omega) = \varepsilon_1 + i\varepsilon_2 encodes everything: refractive index, absorption, reflectivity, colour, plasmons. ε2\varepsilon_2 is the dissipative channel; ε1\varepsilon_1 follows from it via Kramers–Kronig.
  • Fermi's golden rule gives ε2(1/ω2)c,vp2δ(EcEvω)\varepsilon_2 \propto (1/\omega^2)\sum_{c,v}\int |\langle p \rangle|^2 \delta(E_c - E_v - \hbar\omega). Three ingredients: the JDOS (geometry), the matrix element (chemistry), the energy-conservation δ (band structure).
  • Direct-allowed parabolic edges have a universal ωEg\sqrt{\hbar\omega - E_g} onset; van Hove singularities of the JDOS are where the named optical peaks (E0,E1,E2E_0, E_1, E_2) of every textbook spectrum live.
  • In VASP: LOPTICS=.TRUE., large NBANDS, dense k-grid, sensible CSHIFT. vasprun.xml hands you the full tensor; pymatgen turns it into n,k,α,Rn, k, \alpha, R in eight lines.
  • Standard DFT-LOPTICS misses excitons and underestimates the gap. Use it as a fast, qualitative spectrum; reach for hybrids (§6.4) for the gap and BSE (§5.10) for excitons when accuracy demands.
Section 5.5 Core Insight
"An optical spectrum is a band structure read along the vertical axis. Every peak is a place where the conduction-valence height function has a stationary contour; every silence is a region where it has none."
Coming next: §5.6 — Magnetic Ordering and Spin — where we duplicate the band structure into spin-up and spin-down channels, and learn how the same vertical-arrow logic tells us which materials are ferromagnetic, antiferromagnetic, or non-magnetic.
Loading comments...