# Vibration Analysis¶

In the previous chapters, we have studied the various energies that can be obtained from locally stable structures.

In this chapter, we will look at the physical properties obtained by analyzing the curvature of the PES (Potential Energy Surface) around the local minimum.

First, let’s look at the vibration analysis for systems without periodic boundaries (e.g., organic molecules). The physical property obtained from the vibration analysis can be measured in reality with, such as IR spectra.

## Harmonic approximation¶

Let us consider approximating the potential energy \(V\) around a local stable point, which we have obtained using `Calculator`

.

The one-variable function \(f(x)\) around a given point \(x_0\) can be expressed as follows using the Taylor expansion

where \(\Delta x = x - x_0\).

Similarly, the multivariable function \(f(\mathbf{x})\) can be expressed as

where \(\Delta \mathbf{x} = \mathbf{x} - \mathbf{x_0}\).

Now, applying this equation to the potential energy \(V(\mathbf{r})\), at the point \(\mathbf{r_0}\) where the structure is stable. Since the force \(\mathbf{F}_i = \frac{\partial V(\mathbf{r_0})}{\partial r_i}\) is zero, the terms in the first derivative are zero. The expansion to the second order yields

The second derivative of energy, \(\frac{\partial^2 V(\mathbf{r_0})}{\partial r_i \partial r_j}\), is called **Hessian** or **force constant matrix**.

The potential energy surface that considers only second-order terms has the same energy as a system that is connected by springs, and thus, it is called the harmonic approximation.

The following example is shown in 5-9 harmonic approxmation – Shinshu Univ., Physical Chemistry Lab., Adsorption Group. The red line represents the Morse potential \(D(1-e^{- \beta x})^2\), and the blue line represents the harmonic oscillator potential \((1/2)kx^2\) centered at the stable point at \(x=0\). It can be seen that the approximation holds to some extent near \(x=0\).

Figure 1. Morse potential (red line) and harmonic oscillator potential (blue line) Cite from Shinshu Univ., Physical Chemistry Lab., Adsorption Group Iiyama & Futamura Laboratory.

## Vibration¶

The potential energy \(V(\mathbf{r})\) is a \(3N\)-dimensional function for a system containing \(N\) atoms because each atom has three degrees of freedom in \(x, y, z\). Hence, Hessian \(\frac{\partial^2 V(\mathbf{r_0})}{\partial r_i \partial r_j}\) is a \(3N \times 3N\)-dimensional matrix. By diagonalizing the Hessian matrix, \(3N\) eigenvalues and eigenvectors are obtained.

Each eigenvalue corresponds to the strength of the spring \(k\) and each eigenvector corresponds to the direction of the spring, i.e. vibration mode. Among the \(3N\) degrees of freedom, there are 3 degrees of freedom that represent the translational motion of the entire molecule. In addition, the number of degrees of freedom for the rotation of the entire molecule around its center of mass is 2 for linear molecules and 3 for nonlinear molecules. Finally, The number of vibration modes (reference vibrations) is as follows after subtracting the translational and rotational degrees of freedom.

Linear molecule: \(3N-5\)

Nonlinear molecule: \(3N-6\)

Let’s look at examples. The `Vibration`

module in ASE can be used to perform vibration analysis.

### H2O¶

Since H2O is a nonlinear molecule consisting of three atoms, there should be six translational and rotational degrees of freedom and three vibration modes.

When performing a vibration analysis, we first optimize the structure of the system till the force is zero.

```
[1]:
```

```
from ase.build import molecule
from ase.optimize import LBFGS, BFGS, FIRE
import pfp_api_client
from pfp_api_client.pfp.calculators.ase_calculator import ASECalculator
from pfp_api_client.pfp.estimator import Estimator, EstimatorCalcMode
#estimator = Estimator(calc_mode=EstimatorCalcMode.MOLECULE, model_version="latest")
estimator = Estimator(calc_mode=EstimatorCalcMode.CRYSTAL, model_version="v2.0.0")
calculator = ASECalculator(estimator)
atoms = molecule("H2O")
atoms.calc = calculator
LBFGS(atoms).run(fmax=0.0001)
```

```
Step Time Energy fmax
*Force-consistent energies used in optimization.
LBFGS: 0 07:03:03 -10.020077* 0.2280
LBFGS: 1 07:03:04 -10.020929* 0.1044
LBFGS: 2 07:03:04 -10.021161* 0.0718
LBFGS: 3 07:03:04 -10.021430* 0.0014
LBFGS: 4 07:03:04 -10.021426* 0.0000
```

```
[1]:
```

```
True
```

The vibration modes can be calculated with the `Vibrations`

of ASE. In this module, Hessian is approximately calculated from the difference of the force when the position of the atom is slightly changed, as shown in the following equation.

Here, \(\Delta{r_i}\) is a vector that denotes the slight change of the `i`

th coordinates. The `vib.run()`

perform the above calculation and diagonalize Hessian, and `vib.summary()`

outputs the square root of the eigenvalues.

The `vib.run()`

creates the directory specified by `name`

and caches the calculation results. Therefore, if a cache file remains when the next calculation is performed, the new calculation result will not be reflected. Cached files can be deleted with `vib.clean()`

.

```
[2]:
```

```
from ase.vibrations import Vibrations
vib = Vibrations(atoms, indices=None, delta=0.01, name="vib-h2o", nfree=2)
vib.clean()
vib.run()
vib.summary()
```

```
---------------------
# meV cm^-1
---------------------
0 6.3i 50.5i
1 0.2i 1.6i
2 0.1i 0.6i
3 0.1i 0.5i
4 3.3 26.5
5 3.5 28.4
6 202.6 1634.4
7 463.5 3738.1
8 474.0 3823.4
---------------------
Zero-point energy: 0.573 eV
```

The results show that the eigenenergies of the `#0`

to `#5`

modes, which actually correspond to translational and rotational modes, are almost zero. We also see that the eigenenergies of `#6-#8`

modes are greater than zero.

[Note]

The eighvalue with “i” are imaginary number. It indicates an upward convex quadratic form if we expressed the potential energy surface as a quadratic function. The existence of an imaginary eigenvalue suggests the existence of a point with even lower energy. It is caused by the undesirable behavior around the local stable point during structural optimization. However, this time the value is not large and can be regarded as almost zero.

Let’s look at each vibration mode.

Using `vib.write_mode()`

, a trajectory file is created for each vibration mode under the current directory with the name specified by `name`

.

```
[3]:
```

```
vib.write_mode()
```

In the following code, you can see how each vibration mode behaves by changing the value of mode from 0 to 8.

In fact, the values of mode from 0 to 5 indicate translational or rotational motion, while the values from 6 to 8 show that the vibration modes are described as follows.

mode 6: Vibration in which the angle of the H2O changes (bending vibration)

mode 7: Vibration in which the bond length between HOs changes simultaneously (symmetric stretch vibration)

mode 8: Vibration in which the bond lengths between HOs change alternately (asymmetric stretch vibration)

Each vibration can also be confirmed by IR spectra. You can also refer below for the reference.

```
[4]:
```

```
from ase.io.trajectory import Trajectory
from pfcc_extras.visualize.view import view_ngl
mode = 6
traj = Trajectory(f"vib-h2o.{mode}.traj")
view_ngl(traj, representations=["ball+stick"])
```

You can also save each vibration mode as an animated png file as follows

```
[5]:
```

```
from tqdm.auto import tqdm
from pfcc_extras.visualize.povray import traj_to_apng
for mode in tqdm(range(9)):
traj = Trajectory(f"vib-h2o.{mode}.traj")
traj_to_apng(traj, f"output/vib-h2o.{mode}.png", rotation="90x,90y,180z", clean=True, n_jobs=16)
```

```
[Parallel(n_jobs=16)]: Using backend ThreadingBackend with 16 concurrent workers.
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 3.4s remaining: 0.0s
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 3.4s finished
[Parallel(n_jobs=16)]: Using backend ThreadingBackend with 16 concurrent workers.
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 3.2s remaining: 0.0s
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 3.2s finished
[Parallel(n_jobs=16)]: Using backend ThreadingBackend with 16 concurrent workers.
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 3.6s remaining: 0.0s
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 3.6s finished
[Parallel(n_jobs=16)]: Using backend ThreadingBackend with 16 concurrent workers.
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 2.4s remaining: 0.0s
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 2.4s finished
[Parallel(n_jobs=16)]: Using backend ThreadingBackend with 16 concurrent workers.
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 2.8s remaining: 0.0s
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 2.8s finished
[Parallel(n_jobs=16)]: Using backend ThreadingBackend with 16 concurrent workers.
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 3.5s remaining: 0.0s
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 3.5s finished
[Parallel(n_jobs=16)]: Using backend ThreadingBackend with 16 concurrent workers.
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 3.4s remaining: 0.0s
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 3.4s finished
[Parallel(n_jobs=16)]: Using backend ThreadingBackend with 16 concurrent workers.
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 3.1s remaining: 0.0s
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 3.1s finished
[Parallel(n_jobs=16)]: Using backend ThreadingBackend with 16 concurrent workers.
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 3.9s remaining: 0.0s
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 3.9s finished
```

**H2O vibration modes**

### CO2¶

CO2 consists of the same three atoms as H2O, but is a linear molecule. Therefore, it is expected to have five translational and rotational modes and four vibration modes.

Let’s check it out here. As before, we will perform the structural optimization before the vibration analysis.

```
[6]:
```

```
atoms = molecule("CO2")
atoms.calc = calculator
LBFGS(atoms).run(fmax=0.001)
vib = Vibrations(atoms, indices=None, delta=0.01, name="vib-co2", nfree=2)
vib.clean()
vib.run()
vib.summary()
```

```
Step Time Energy fmax
*Force-consistent energies used in optimization.
LBFGS: 0 07:05:51 -17.706260* 0.2452
LBFGS: 1 07:05:51 -17.706731* 0.1111
LBFGS: 2 07:05:51 -17.706857* 0.0013
LBFGS: 3 07:05:51 -17.706858* 0.0000
---------------------
# meV cm^-1
---------------------
0 0.6i 4.7i
1 0.6i 4.7i
2 0.2i 2.0i
3 0.9 7.2
4 0.9 7.2
5 71.0 572.6
6 71.0 572.6
7 165.0 1330.9
8 289.2 2332.6
---------------------
Zero-point energy: 0.299 eV
```

```
[7]:
```

```
vib.write_mode()
```

In fact, the five modes corresponding to translation and rotation (`#0-#4`

) have almost zero eigenenergy, while the eigenmodes corresponding to vibration (`#5-#8`

) have values greater than zero.

Let us look at them. The vibration of `#5`

and `#6`

have the same eigenenergies, indicating that the vibration modes (= eigenvectors) are degenerate.

mode 5, 6: Vibrations that change the angle of CO2, showing degenerate changes in the two directions.

mode 7: Vibrations in which the bond lengths between the COs change simultaneously

mode 8: Vibrations in which the bond lengths between the COs change alternately.

```
[8]:
```

```
from ase.io.trajectory import Trajectory
from pfcc_extras.visualize.view import view_ngl
mode = 5
traj = Trajectory(f"vib-co2.{mode}.traj")
view_ngl(traj, representations=["ball+stick"])
```

```
[9]:
```

```
from tqdm.auto import tqdm
from pfcc_extras.visualize.povray import traj_to_apng
for mode in tqdm(range(9)):
traj = Trajectory(f"vib-co2.{mode}.traj")
traj_to_apng(traj, f"output/vib-co2.{mode}.png", rotation="30x,30y,30z", clean=True, n_jobs=16)
```

```
[Parallel(n_jobs=16)]: Using backend ThreadingBackend with 16 concurrent workers.
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 3.6s remaining: 0.0s
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 3.6s finished
[Parallel(n_jobs=16)]: Using backend ThreadingBackend with 16 concurrent workers.
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 3.2s remaining: 0.0s
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 3.2s finished
[Parallel(n_jobs=16)]: Using backend ThreadingBackend with 16 concurrent workers.
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 3.7s remaining: 0.0s
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 3.7s finished
[Parallel(n_jobs=16)]: Using backend ThreadingBackend with 16 concurrent workers.
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 4.3s remaining: 0.0s
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 4.3s finished
[Parallel(n_jobs=16)]: Using backend ThreadingBackend with 16 concurrent workers.
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 3.9s remaining: 0.0s
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 3.9s finished
[Parallel(n_jobs=16)]: Using backend ThreadingBackend with 16 concurrent workers.
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 2.8s remaining: 0.0s
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 2.8s finished
[Parallel(n_jobs=16)]: Using backend ThreadingBackend with 16 concurrent workers.
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 2.9s remaining: 0.0s
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 2.9s finished
[Parallel(n_jobs=16)]: Using backend ThreadingBackend with 16 concurrent workers.
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 2.8s remaining: 0.0s
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 2.8s finished
[Parallel(n_jobs=16)]: Using backend ThreadingBackend with 16 concurrent workers.
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 2.8s remaining: 0.0s
[Parallel(n_jobs=16)]: Done 30 out of 30 | elapsed: 2.8s finished
```

**CO2 vibration modes**

### CH3OH¶

Let us go on to the same vibration analysis with CH3OH, a molecule with a slightly more complex shape.

Since it is a nonlinear molecule with six atoms, we see that the first six are translational and vibration modes, and the remaining 12 are vibration modes.

```
[10]:
```

```
atoms = molecule("CH3OH")
atoms.calc = calculator
LBFGS(atoms).run(fmax=0.001)
vib = Vibrations(atoms, indices=None, delta=0.01, name="vib-ch3oh", nfree=2)
vib.clean()
vib.run()
vib.summary()
```

```
Step Time Energy fmax
*Force-consistent energies used in optimization.
LBFGS: 0 07:06:50 -22.437713* 0.2576
LBFGS: 1 07:06:50 -22.440612* 0.1603
LBFGS: 2 07:06:51 -22.442340* 0.0916
LBFGS: 3 07:06:51 -22.442566* 0.0798
LBFGS: 4 07:06:51 -22.442927* 0.0472
LBFGS: 5 07:06:51 -22.443046* 0.0273
LBFGS: 6 07:06:51 -22.443099* 0.0211
LBFGS: 7 07:06:51 -22.443153* 0.0208
LBFGS: 8 07:06:51 -22.443180* 0.0144
LBFGS: 9 07:06:51 -22.443185* 0.0091
LBFGS: 10 07:06:52 -22.443186* 0.0072
LBFGS: 11 07:06:52 -22.443197* 0.0091
LBFGS: 12 07:06:52 -22.443192* 0.0055
LBFGS: 13 07:06:52 -22.443194* 0.0014
LBFGS: 14 07:06:52 -22.443196* 0.0009
---------------------
# meV cm^-1
---------------------
0 1.4i 11.4i
1 0.9i 7.1i
2 0.2i 1.4i
3 0.1i 1.0i
4 0.0i 0.1i
5 1.2 9.8
6 33.1 267.2
7 123.2 993.3
8 129.4 1043.8
9 137.8 1111.6
10 161.4 1301.6
11 169.7 1368.5
12 172.0 1387.3
13 177.8 1434.4
14 361.7 2917.1
15 369.0 2976.1
16 376.3 3034.7
17 463.2 3736.2
---------------------
Zero-point energy: 1.338 eV
```

Let’s take a look at each vibration mode. We can see that the whole atom moves in a complex manner for some vibration modes.

```
[11]:
```

```
vib.write_mode()
```

```
[12]:
```

```
from ase.io.trajectory import Trajectory
from pfcc_extras.visualize.view import view_ngl
mode = 10
traj = Trajectory(f"vib-ch3oh.{mode}.traj")
view_ngl(traj, representations=["ball+stick"])
```

### CH4¶

Finally, let us look at CH4 as an example of a molecule with high symmetry.

Since it is a nonlinear molecule with five atoms, we see that the first six are translational and rotational modes and the remaining nine are vibration modes.

```
[13]:
```

```
atoms = molecule("CH4")
atoms.calc = calculator
LBFGS(atoms).run(fmax=0.001)
vib = Vibrations(atoms, indices=None, delta=0.01, name="vib-ch4", nfree=2)
vib.clean()
vib.run()
vib.summary()
```

```
Step Time Energy fmax
*Force-consistent energies used in optimization.
LBFGS: 0 07:07:11 -18.153517* 0.2289
LBFGS: 1 07:07:11 -18.155783* 0.1190
LBFGS: 2 07:07:11 -18.156646* 0.0022
LBFGS: 3 07:07:11 -18.156641* 0.0000
---------------------
# meV cm^-1
---------------------
0 3.5i 28.0i
1 3.5i 27.9i
2 3.5i 27.9i
3 0.3i 2.1i
4 0.3i 2.1i
5 0.3i 2.1i
6 141.7 1142.6
7 141.7 1142.6
8 141.7 1142.6
9 187.2 1509.9
10 187.2 1509.9
11 368.1 2968.8
12 379.8 3063.1
13 379.8 3063.1
14 379.8 3063.1
---------------------
Zero-point energy: 1.153 eV
```

```
[14]:
```

```
vib.write_mode()
```

Matlantis offers a function called `VibrationFeature`

that can be used for vibration analysis.

```
[15]:
```

```
from matlantis_features.features.common.vibration import VibrationFeature
atoms = molecule("CH4")
atoms.calc = calculator
LBFGS(atoms).run(fmax=0.001)
vib_feature = VibrationFeature(delta=0.01)
results = vib_feature(atoms)
```

```
Step Time Energy fmax
*Force-consistent energies used in optimization.
LBFGS: 0 07:07:17 -18.153517* 0.2289
LBFGS: 1 07:07:17 -18.155784* 0.1190
LBFGS: 2 07:07:17 -18.156648* 0.0022
LBFGS: 3 07:07:18 -18.156642* 0.0000
```

## Physical properties calculated after vibration analysis¶

The following physical properties can be calculated by using the results of vibration analysis

Thermochemistry calculation: This method can be used to calculate enthalpy, free energy, etc. under the ideal gas approximation. Details are given in later chapters.

IR spectra wavenumber: Matlantis also provides a function to calculate IR spectra.

## [Column] Effective range of harmonic approximation¶

Since the vibration analysis in this section and phonon analysis in the next section are based on the harmonic approximation, it is important to understand the structures and cases for which this approximation is valid. The harmonic approximation is based on the potential energy with spring \(1/2 k x^2\), to represent the potential energy near the stable structure. As shown in the figure below, the larger displacement of each atom from the origin, the larger force it will receive to pull it
back to the origin. In other words, it is valid for structures such as solids, where the atoms stay in their positions, but **not for fluids or gases, where each atom is not in a fixed place to begin with**.

This is also related to temperature. When a substance is above its melting point and in a liquid state, the harmonic approximation is not valid. As a rule of thumb, the accuracy of the harmonic oscillator approximation drops significantly as one approaches the melting point and it is only valid **in the low temperature range**.

## Next section¶

In this section, we have performed vibration analysis for systems without periodic boundary conditions, such as molecules.

In the next section, we will deal with **phonon**, which describes vibration in a system with periodic boundary conditions, such as crystals.