We have seen in the infrared tutorial that light can couple to the motion of atoms (phonons) in the frequency range corresponding to the spectral range of phonons, which is typically several THz. Light in the visible range (>400 THz) will not directly couple to the phonons. However, it can still exchange quanta of energy with the phonons through variations in the permittivity: the Raman effect.
This tutorial covers the basics to compute temperature-dependent first-order Raman spectra with TDEP.
What we need to compute is the Raman scattering cross section, Eq. (7) in Ref. [1],
which relates the intensity of incoming with with E-field vector
The Raman tensor is given by the susceptibility-susceptibility response function
with the polarizability also known from the dielectric tensor
in SI units, or
in atomic units.
In this tutorial, we will compute the Raman tensor by expanding the susceptibility to first order in the atomic displacements, i.e., the first-order dielectric response, Eq. (10) in [1], which we compute here by finite differences similar to Eq. (9) in Ref. [2]. By including anharmonicity, we can study the temperature dependence of the Raman spectrum as well, as well as polarization dependence.
In first order Raman, the Raman tensor is:
i.e., the change of the susceptibility with mode displacement
The Raman tensor can be obtained from atomic displacements (as we will do in this tutorial) via the eigenvectors:
where
The intensity contribution of mode
where
and this will be what experimentalists can measure. Mapping out the full dependency of the intensity as a function of the incoming/outgoing polarization is called polarization-orientation (PO) Raman. Eq. (1) is the topic of this tutorial.
To understand experimental Raman spectra for single crystals, we need to understand the Porto notation [Arguello1969]:
Figure from Ref. 4This
- X: Direction of incoming light (its
$\mathbf k$ vector) - Y: Polarization of incoming light
$\mathbf e_\mathrm{i}$ - X: Polarization of outgoing light
$\mathbf e_\mathrm{o}$ - Z: Direction of outgoing light (its
$\mathbf k$ vector)
We will deal only with the most common type of experiment, the backscattering experiment, where incoming and outgoing light have the same direction,
The cross-section
For an unpolarized backscattering experiment (e.g. no polarizers are useed for either probing and detecting), the spectrum is an average of the cross-sections for all the parallel and orthogonal pairs
For a powder sample, not only the considerations made so far hold, but we also have to make an average over all the possible wavevectors of the incoming light (isotropic average). The same result can be obtained by averaging the raman tensor elements as explained in [Knoop-arxiv2412.17711]. The result is an isotropically averaged spectrum.
Note that for powders of polar materials there is also another possible complication that is not taken into account in this tutorial: since the Raman tensor depends on the polarization of the mode, it might considerably change by varying the wavevector of the incoming light (which means probing long-wavelength phonons from different directions of the BZ), because of the LO-TO splitting. In order to solve this, a spherical integration over all the possible wavevectors should be performed (i.e. the entire process displaied in this tutorial should be done a very large number of times). Depending on the symmetry of your system and on the entity of the LO-TO splitting, this effect might be neglected.
We will compute Raman spectra for wurtzite gallium nitride (GaN) and compare to the experimental reference in Ref. [Siegle1995].
-
Have a converged set of 2nd and 3rd order force constants.
-
Have a DFT code ready that can compute the dielectric tensor
$\varepsilon$ for you (not necessary for the tutorial, but it is for production).
We will start with backscattering in
-
Go to the
example_GaN/folder -
Create a new working directory for the
$z$ directionmkdir raman_z cd raman_z -
Copy the infiles into
raman_z/:cp ../infile.lotosplitting ../infile.ucposcar ../infile.ssposcar ./
-
Copy your (converged) force constant outfiles that you got previously (see tutorial 01) and change their prefix to
infile. For time reasons, we will use some pre-computed ones:cp ../.assets/infile.forceconstant ../.assets/infile.forceconstant_thirdorder . -
Create the spectral functions:
lineshape --temperature 300 --qdirin 0 0 1
this should give the file
outfile.phonon_self_energy.hdf5that you already encountered in previous tutorials. -
Now we create the atomic displacements via:
tdep_displace_atoms infile.ucposcar
which will create positive and negative displacements for each atom and cartesian direction and write them to
outfile.ucposcar.displacement.00001.x.plus,outfile.ucposcar.displacement.00002.x.minus, etc. Tidy up the mess by moving these outfiles inside a dedicated folder (e.g.displacements):mkdir displacements mv outfile.ucposcar.displacement* displacements/ -
Now comes the DFPT part: convert these geometry files into the input format of the DFT code of your choice which is capable of computing the dielectric tensor (Born charges are not needed) and compute the dielectric tensor for each sample.
-
When you are finished with all dielectric calculations, parse them and write them in a new file called
infile.dielectric_tensor. The order to follow when writing the tensors in this file is the same used in the numbering of the outfiles obtained fromtdep_displace_atoms(that is the order of the atoms in theinfile.ucposcarand first + and then - displacement).
Note that the units of the dielectric tensor are not important (of course they must be consistent among the displacements), as they renormalize the whole spectrum intensity.
Copy or link the infile to your working directoryraman_z.
We will skip steps 7 and 8 now, due to time reasons, but naturally this is something you need to do for production runs. For the time being, we will use the hidden input file07_Raman/example_GaN/.assets/.infile.dielectric_tensor.cp ../.assets/infile.dielectric_tensor . -
Good, now we can compute the Raman tensors, and convolute them with the spectral functions, i.e., evaluate Eq. (1). There is a script to do this in several ways:
tdep_compute_raman_intensities
Done.
Several output files are produced by the last step
-
outfile.raman_activity_mode_001.csvThe mode index, the frequency (in THz and$cm^{-1}$ ), the isotropic and unpolarized Raman activities are listed for each mode. No information on the peak shape. -
outfile.raman_intensity_001.csvThe actual spectra: frequency (in$cm^{-1}$ ), the parallel and perpendicular intensities, and the unpolarized and isotropic intensities are listed. Here the parallel and perpendicular intensities are averages computed by only considering respectively the parallel and orthogonal pairs of polarizations lying on the plane orthogonal to the photon wavevector. -
outfile.raman_intensity_001_po.h5This hdf5 file has attributes:- direction1
- direction2
- direction3
where the first is the propagation direction of the incoming light, and the last two define the polarization plane of both the incoming and scattered light.
Then there are two coordinates:
- angle (size=361): the array of the possible angles of rotation of the (fixed) pairs of polarization around the propagation direction of the light (it uniquely identifies the pair of polarisations on the plane)
- frequency (size=1200): the frequency in
$cm^{-1}$
Finally, two data variables:
- parallel (shape=361,1200): Raman spectrum (1200 values, one for each frequency) for each angle of rotation of the fixed pair of parallel polarizations on the plane
- perpendicular (shape=361,1200): Raman spectrum (1200 values, one for each frequency) for each angle of rotation of the fixed pair of perpendicular polarizations on the plane
Here are some examples of python scripts to plot the spectra.
import numpy as np
from matplotlib import pyplot as plt
data = np.loadtxt(fname='outfile.raman_intensity_001.csv', skiprows=1, delimiter=',').T
freqs = data[0]
unpo = data[3]
iso = data[4]
Fig = plt.figure(figsize=(15,5))
Fig.add_subplot(1,2,1)
# UNPOLARIZED
plt.plot(freqs, unpo)
plt.xlim(0,800)
plt.title('Unpolarized spectrum')
plt.xlabel('Raman shift ($cm^{-1}$)')
plt.yticks([])
plt.ylabel('Intensity (a.u.)')
Fig.add_subplot(1,2,2)
# ISOTROPIC
plt.plot(freqs, iso)
plt.xlim(0,800)
plt.title('Isotropically averaged spectrum')
plt.xlabel('Raman shift ($cm^{-1}$)')
plt.yticks([])
plt.ylabel('Intensity (a.u.)')import numpy as np
import xarray as xr
from matplotlib import pyplot as plt
ds = xr.open_dataset('outfile.raman_intensity_001_po.h5')
angles = np.array(ds['angle'])
freqs = np.array(ds['frequency'])
paral = np.array(ds['parallel'])
perp = np.array(ds['perpendicular'])
#directions = np.array([ds.direction1, ds.direction2, ds.direction3]) # we don't need this
# z(xx)-z ------ parallel, theta = 0
i_0 = np.argmin(angles) # find the position of theta=0 in the angles array
spec1 = paral[i_0]
plt.plot(freqs, spec1)
plt.xlim(0,800)
plt.title('z(xx)-z')
plt.xlabel('Raman shift ($cm^{-1}$)')
plt.yticks([])
plt.ylabel('Intensity (a.u.)')
# z(xy)-z ------ perpendicular, theta = 0
plt.figure()
spec2 = paral[i_0]
plt.plot(freqs, spec2)
plt.xlim(0,800)
plt.title('z(xy)-z')
plt.xlabel('Raman shift ($cm^{-1}$)')
plt.yticks([])
plt.ylabel('Intensity (a.u.)')Task: Plot all the equivalents of the first 4 plots of Fig. 2b in [Siegle1995] and discuss your findings (hint: you need to run the steps of this tutorial again, changing a single parameter).
Congratulations, you have performed a complete description of first-order Raman scattering in wurtzite GaN at room temperature from first principles.
- [0]R. A. Cowley, P Phys Soc 84, 281 (1964)
- [1] N. Benshalom, G. Reuveni, R. Korobko, O. Yaffe, and O. Hellman, Phys Rev Mater 6, 033607 (2022)
- [2] J. M. Skelton, L. A. Burton, A. J. Jackson, F. Oba, S. C. Parker, and A. Walsh, Phys Chem Chem Phys 19, 12452 (2017)
- [3] N. Benshalom et al., arxiv 2204.12528 (2023)
- [4]C. A. Arguello, D. L. Rousseau, and S. P. S. Porto, Phys Rev 181, 1351 (1968)
- [5]H. Siegle et al., Solid State Commun. 96, 943 (1995)
- [6]K. Florian et al., arXiv:2412.17711
