Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

IR spectroscopy

The infrared (IR) absorption process involves the absorption of light with low photon energy (from approx. 800 nm to approx. 1000 μ\mum) which promotes the excitation of molecular vibrations. Of the entire IR photon energy range, the photons which excite fundamental vibrations of covalent bonds have energies in the range 2.5-25 μ\mum Norman et al. (2018)Centrone (2015). The absorbed photon energies (i.e., IR absorption peak positions) correspond to the molecular vibrational frequencies, while the IR intensities are related to the IR linear absorption cross-section Norman et al. (2018)

σk(ω)=π3ϵ0c(μQk)2γk(ωk0ω)2+γk2\sigma_k(\omega)= \frac{\pi}{3\epsilon_0c}\left(\frac{\partial \mu}{\partial Q_k}\right)^2\frac{\gamma_{k}}{(\omega_{k0}-\omega)^2+\gamma_{k}^2}\,

Here, μ\mu is the molecular dipole moment of the electronic ground state, QkQ_k is the normal coordinate corresponding to the vibrational mode kk, ω\omega is the angular frequency of the absorbed electromagnetic radiation, ωk0\omega_{k0} is the angular frequency difference between the excited vibrational state k|k\rangle and the ground state, and γk\gamma_{k} is the half-width broadening associated with the inverse lifetime of k|k\rangle. As can be seen from the equation, the IR absorption cross-section depends on the derivative of the dipole moment with respect to the normal coordinate QkQ_k. This means that the only modes which have non-zero IR absorption cross-sections are those associated with a change in the dipole moment Siebert & Hildebrandt (2007).

These molecular vibrations constitute fingerprints of different functional groups present in the system and thus are important for chemical characterization and molecular identification.

The first step in an IR spectrum calculation is to determine the normal modes and frequencies. These are obtained from the eigenstates and eigenvalues of the Hessian matrix which collects all the second order derivatives of the energy EE with respect to the nuclear coordinates. For the ground state, the Hessian can be determined analytically, or numerically based on the analytical gradient. The dipole moment gradient, required to compute IR absorption cross-sections, can also be determined analytically.

import numpy as np
import py3Dmol as p3d
import veloxchem as vlx
from veloxchem.veloxchemlib import bohr_in_angstroms
# Define the molecule and basis set
molecule_string = """3

  O 0.000 0.000  0.000
  H 0.000 0.000  0.950
  H 0.896 0.000 -0.317"""

molecule = vlx.Molecule.read_xyz_string(molecule_string)
basis = vlx.MolecularBasis.read(molecule, "sto-3g", ostream=None)
Loading...
scf_drv = vlx.ScfRestrictedDriver()
scf_results = scf_drv.compute(molecule, basis)

Now we can run vibrational analysis

vibanalysis_drv = vlx.VibrationalAnalysis(scf_drv)
vib_results = vibanalysis_drv.compute(molecule, basis)
vibanalysis_drv.print_hessian(molecule)
                                           Analytical Hessian (Hartree/Bohr**2)                                           
                                          --------------------------------------                                          
                                                                                                                          
  Coord.       1 O(x)          1 O(y)          1 O(z)          2 H(x)          2 H(y)          2 H(z)          
                                                                                                                          
  1 O(x)       0.77341754      0.00000000     -0.22069513     -0.05793219      0.00000000      0.05915305
  1 O(y)       0.00000000     -0.08462393     -0.00000000      0.00000000      0.04250781      0.00000000
  1 O(z)      -0.22069513     -0.00000000      0.93182618     -0.07097759      0.00000000     -0.79549474
  2 H(x)      -0.05793219      0.00000000     -0.07097759      0.05352688     -0.00000000     -0.01075530
  2 H(y)       0.00000000      0.04250781      0.00000000     -0.00000000     -0.02940298     -0.00000000
  2 H(z)       0.05915305      0.00000000     -0.79549474     -0.01075530     -0.00000000      0.80288015
  3 H(x)      -0.71548535     -0.00000000      0.29167272      0.00440531     -0.00000000     -0.04839775
  3 H(y)      -0.00000000      0.04211612      0.00000000      0.00000000     -0.01310483     -0.00000000
  3 H(z)       0.16154207      0.00000000     -0.13633144      0.08173289      0.00000000     -0.00738541
                                                                                                                          
                                                                                                                          
  Coord.       3 H(x)          3 H(y)          3 H(z)          
                                                                                                                          
  1 O(x)      -0.71548535     -0.00000000      0.16154207
  1 O(y)      -0.00000000      0.04211612      0.00000000
  1 O(z)       0.29167272      0.00000000     -0.13633144
  2 H(x)       0.00440531      0.00000000      0.08173289
  2 H(y)      -0.00000000     -0.01310483      0.00000000
  2 H(z)      -0.04839775     -0.00000000     -0.00738541
  3 H(x)       0.71108004      0.00000000     -0.24327497
  3 H(y)       0.00000000     -0.02901129     -0.00000000
  3 H(z)      -0.24327497     -0.00000000      0.14371685
                                                                                                                          
                                                                                                                          

The normal modes and frequencies are then determined by diagonalizing the Hessian. First, the translation and rotational modes must be projected out, leaving 3N63N-6 (or 3N53N-5 for a linear molecule) vibrational modes for a system with NN atoms. This is done under the hood with the help of geomeTRIC.

The IR intensities are determined as discussed in more detail here.

def add_broadening(
    list_ex_energy,
    list_osci_strength,
    line_profile="Lorentzian",
    line_param=10,
    step=10,
):
    x_min = np.amin(list_ex_energy) - 50
    x_max = np.amax(list_ex_energy) + 50
    x = np.arange(x_min, x_max, step)
    y = np.zeros((len(x)))

    # go through the frames and calculate the spectrum for each frame
    for xp in range(len(x)):
        for e, f in zip(list_ex_energy, list_osci_strength):
            if line_profile == "Gaussian":
                y[xp] += f * np.exp(-(((e - x[xp]) / line_param) ** 2))
            elif line_profile == "Lorentzian":
                y[xp] += (
                    0.5
                    * line_param
                    * f
                    / (np.pi * ((x[xp] - e) ** 2 + 0.25 * line_param**2))
                )
    return x, y
# plot the IR spectrum
from matplotlib import pyplot as plt

plt.figure(figsize=(7, 4))
x1, y1 = vibanalysis_drv.vib_frequencies, vibanalysis_drv.ir_intensities
x1i, y1i = add_broadening(x1, y1, line_profile="Gaussian", line_param=20, step=10)
plt.plot(x1i, y1i)
plt.xlabel("wavenumber (cm**-1)")
plt.ylabel("IR intensity (km/mol)")
plt.title("Calculated IR sepctrum of the water molecule")
plt.tight_layout()
plt.show()
<Figure size 700x400 with 1 Axes>
Experimental spectrum

Let’s now visualize the three vibrational normal modes. The two high frequency modes are OH stretching modes (one symmetryc and one asymmetric), while the lower energy mode is a bond angle bending.

# To animate the normal mode we will need both the geometry and the displacements
def get_normal_mode(molecule, normal_mode):
    elements = molecule.get_labels()
    # Transform from au to A
    coords = molecule.get_coordinates_in_bohr() * bohr_in_angstroms()
    natm = molecule.number_of_atoms()

    vib_xyz = "%d\n\n" % natm
    nm = normal_mode.reshape(natm, 3)
    for i in range(natm):
        # add coordinates:
        vib_xyz += elements[i] + " %15.7f %15.7f %15.7f " % (
            coords[i, 0],
            coords[i, 1],
            coords[i, 2],
        )
        # add displacements:
        vib_xyz += "%15.7f %15.7f %15.7f\n" % (nm[i, 0], nm[i, 1], nm[i, 2])
    return vib_xyz
vib_1 = get_normal_mode(molecule, vibanalysis_drv.normal_modes[0])
vib_2 = get_normal_mode(molecule, vibanalysis_drv.normal_modes[1])
vib_3 = get_normal_mode(molecule, vibanalysis_drv.normal_modes[2])
print("This is the bending mode at 1927.09 cm-1.")
view = p3d.view(width=300, height=300)
view.addModel(vib_1, "xyz", {"vibrate": {"frames": 10, "amplitude": 0.75}})
view.setViewStyle({"style": "outline", "width": 0.05})
view.setStyle({"stick": {}, "sphere": {"scale": 0.25}})
view.animate({"loop": "backAndForth"})
view.rotate(-90, "x")
view.zoomTo()
view.show()
This is the bending mode at 1927.09 cm-1.
Loading...
print("This is the symmetric stretching mode at 4545.78 cm-1.")
view = p3d.view(width=300, height=300)
view.addModel(vib_2, "xyz", {"vibrate": {"frames": 10, "amplitude": 0.75}})
view.setViewStyle({"style": "outline", "width": 0.05})
view.setStyle({"stick": {}, "sphere": {"scale": 0.25}})
view.animate({"loop": "backAndForth"})
view.rotate(-90, "x")
view.zoomTo()
view.show()
This is the symmetric stretching mode at 4545.78 cm-1.
Loading...
print("This is the asymmetric stretching mode at 4896.44 cm-1.")
view = p3d.view(width=300, height=300)
view.addModel(vib_3, "xyz", {"vibrate": {"frames": 10, "amplitude": 0.75}})
view.setViewStyle({"style": "outline", "width": 0.05})
view.setStyle({"stick": {}, "sphere": {"scale": 0.25}})
view.animate({"loop": "backAndForth"})
view.rotate(-90, "x")
view.zoomTo()
view.show()
This is the asymmetric stretching mode at 4896.44 cm-1.
Loading...
References
  1. Norman, P., Ruud, K., & Saue, T. (2018). Principles and practices of molecular properties. John Wiley & Sons, Ltd.
  2. Centrone, A. (2015). Infrared imaging and spectroscopy beyond the diffraction limit. Annu. Rev. Anal. Chem., 8, 101–126. 10.1146/annurev-anchem-071114-040435
  3. Siebert, F., & Hildebrandt, P. (2007). Theory of Infrared Absorption and Raman Spectroscopy. In Vibrational Spectroscopy in Life Science (pp. 11–61). John Wiley. 10.1002/9783527621347.ch2