# Vibrational spectra from MD

This tutorial shows how to calculate vibrational spectra (infrared spectra and power spectra) from molecular dynamics trajectories.

Here is a video of convering contents of this tutorial. You can find it on Youtube.

## Theory

The infrared spectra are calculated using the fast Fourier transform of the autocorrelation function of dipole moments:

$$P(\nu)\propto\frac{\nu\text{tanh}(h\nu/2k_{\text{B}}T)}{n(\nu)}\int\langle\mathbf{\mu}(\tau)\mathbf{\mu}(t+\tau) \rangle _{\tau}e^{-i2\pi\nu t} \text{d}t$$,

where $$\langle \mathbf{\mu}(\tau)\mathbf{\mu}(t+\tau) \rangle _{\tau}$$ is the autocorrelation function of dipole moments, $$\nu$$ is the frequency, $$h$$ is the Planck constant, $$k_{\text{B}}$$ is the Boltzmann constant, $$T$$ is the temperature and $$n(\nu)$$ is the refractive index which is 1 in gas phase. In our implementation, a Hann window function is applied to the autocorrelation function before the fast Fourier transform. The users can tune autocorrelation depth to control the width of peaks. Zero padding can also be applied to the autocorrelation function to improve the resolution.

The power spectra can be calculated similarly from the autocorrelation function of velocities:

$$P(\nu)\propto\int \langle\mathbf{v}(\tau)\mathbf{v}(t+\tau)\rangle_{\tau}e^{-i2\pi\nu t} \text{d}t$$,

where $$\langle\mathbf{v}(\tau)\mathbf{v}(t+\tau)\rangle_{\tau}$$ is the autocorrelation function of velocities.

The theories of calculating vibrational spectra from MD trajectories are from these two papers: DOI: 10.1039/C3CP44302G and DOI: 10.1039/C6CP05849C.

The details of the implementation of MD and vibrational spectra derived from it in MLatom are described in this papper (DOI: 10.1039/D3CP03515H). Please cite it when you use this feature in your publications.

Note

Some of QM methods (e.g., methods interfaced to Gaussian and MNDO) in MLatom provide dipole moments automatically when the user is doing single point calculations. Except for AIQM1, all the pre-trained ML models do not provide dipole moments. AIQM1 gives ODM2* dipole moments which are only available when using the MNDO program (not Sparrow). Always use these methods if you want to calculate infrared spectra. All the methods can be used to calculate power spectra which only need velocities.

## Using input file/command line

Then we can look at how to calculate vibrational spectra via input file/command line. The input file shows how to calculate infrared spectrum of ethanol from the molecular dynamics trajectory in h5md format. Here we provide a trajectory of ethanol, which was propagated at 300K in Nose-Hoover thermostat using AIQM1. The time step is 0.5 fs. The MD trajectory can be downloaded here: ethanol_traj.h5 (h5md format), ethanol_traj.xyz (xyz coordinates), ethanol_traj.vxyz (xyz velocities) and ethanol_traj.dp (dipole moments).

MD2vibr                     # 1. requests vibrational spectra from MD trajectory
trajH5MDin=ethanol_traj.h5  # 2. file with MD trajectory in h5md format
dt=0.5                      # 3. time step of trajectory; unit: fs
start_time=0                # 4. use trajectory starting from 0 fs
end_time=10000              # 5. use trajectory ending at 10000 fs
autocorrelationDepth=1024   # 6. autocorrelation depth; unit: fs
output=ir                   # 8. output infrared spectrum


You will find two files in your folder. One is ir.png which is the plot of the infrared spectrum and the other one is ir.npy which saves the array of the spectrum. The calculated infrared spectrum should look like this:

The keyword output is used to control which kind of vibrational spectrum to calculate. output=ir gives infrared spectrum and output=ps gives power spectrum. Below is the input file that calculates power spectrum.

MD2vibr                     # 1. requests vibrational spectra from MD trajectory
trajH5MDin=ethanol_traj.h5  # 2. file with MD trajectory in h5md format
dt=0.5                      # 3. time step of trajectory; unit: fs
start_time=0                # 4. use trajectory starting from 0 fs
end_time=10000              # 5. use trajectory ending at 10000 fs
autocorrelationDepth=1024   # 6. autocorrelation depth; unit: fs
output=ps                   # 8. output power spectrum


The power spectrum should look like this:

Apart from MD trajectories in h5md format, users can also provide dipole moments (for infrared spectrum) or velocities (for power spectrum) in plain text, where keyword trajdpin or trajVXYZin is used. Below are examples of two cases.

MD2vibr                    # 1. requests vibrational spectra from MD trajectory
trajdpin=ethanol_traj.dp   # 2. file with dipole moments in plain text
dt=0.5                     # 3. time step of trajectory; unit: fs
start_time=0               # 4. use trajectory starting from 0 fs
end_time=10000             # 5. use trajectory ending at 10000 fs
autocorrelationDepth=1024  # 6. autocorrelation depth; unit: fs
output=ir                  # 8. output infrared spectrum

MD2vibr                       # 1. requests vibrational spectra from MD trajectory
trajVXYZin=ethanol_traj.vxyz  # 2. file with velocities in plain text
dt=0.5                        # 3. time step of trajectory; unit: fs
start_time=0                  # 4. use trajectory starting from 0 fs
end_time=10000                # 5. use trajectory ending at 10000 fs
autocorrelationDepth=1024     # 6. autocorrelation depth; unit: fs
output=ps                     # 8. output power spectrum


Note

In principle, our default recommendation is to use AIQM1 as it is usually the most accurate method for affordable cost. The dipole moments are extracted from the ODM2* part of AIQM1. However, they are only available when you use MNDO program as QM part. We recommend to install the MNDO to unlock the full potential of AIQM1. The XACS cloud uses Sparrow instead, which currently does not provide dipole moments and only provides numerical gradients limiting its applicability for MD. Another limitation is that AIQM1 is currently only applicable to the compounds with CHNO elements. ANI-1ccx is a good alternative on the XACS cloud if it can be applied but it does not provide dipole moments either (it has additional limitations to neutral closed-shell compounds in ground state).

### Other keywords

Keywords start_time and end_time control which part of trajectory is used to calculate the spectrum. By default, MLatom will use the whole trajectory. autocorrelationDepth controls the autocorrelation depth which affects the peak widths while zeropadding controls how many zeros are appended to calculated autocorrelation function which affects the resolution of the spectrum. These two settings are by default 1024 fs. MLatom will normalize the spectrum by the maximum intensity. If you want the original spectrum, you can use the keyword normalize_intensity=0.

## Using API

Below shows how to calculate vibrational spectra via MLatom API.

import mlatom as ml

traj = ml.data.molecular_trajectory()
moldb = ml.data.molecular_database()
moldb.molecules = [step.molecule for step in traj.steps]

# moldb = ml.data.molecular_database.from_xyz_file('ethanol_traj.xyz')
# with open('ethanol_traj.dp','r') as f:
# for ii in range(len(moldb)):
#     moldb[ii].dipole_moment = [float(each) for each in lines[ii].strip().split()]

# Calculate vibrational spectra
vibr = ml.vibrational_spectrum(molecular_database=moldb,dt=0.5)

# Infrared spectrum
freqs_ir,ints_ir = vibr.plot_infrared_spectrum(filename='ethanol_ir.png',  # file to save the figure
autocorrelation_depth=1024, # autocorrelation depth; unit: fs
lb=0,                       # start time; unit: fs
ub=10000,                   # end time; unit: fs
normalize=True,             # normalize the spectrum
return_spectrum=True,       # get the spectrum array from the function
format='modulus')           # use the modulus of the dipole moments
# Power spectrum
freqs_ps,ints_ps = vibr.plot_power_spectrum(filename='ethanol_ps.png',     # file to save the figure
autocorrelation_depth=1024,    # autocorrelation depth; unit: fs
lb=0,                          # start time; unit: fs
ub=10000,                      # end time; unit: fs
normalize=True,                # normalize the spectrum
return_spectrum=True)          # use the modulus of the dipole moments

# Save the spectra
np.save('ethanol_ir_saved.npy',np.array((freqs_ir,ints_ir)))
np.save('ethanol_ps_saved.npy',np.array((freqs_ps,ints_ps)))


The keywords are slightly different from that in the input file. The user can use lb and ub to control the start time and end time of the trajectory. autocorrelation_depth and zero_padding can be used to tune the autocorrelation depth and zero padding. Normalization to the maximum intensity is enabled if normalize=True. The user can get frequencies and intensities from the return value of the function by using return_spectrum=True. In infrared specturm, the user can either use the modulus or XYZ vector of dipole moments by using format='modulus' or format='vector'.

## Any questions or suggestions?

If you have further questions, criticism, and suggestions, we would be happy to receive them in English or Chinese via email, Slack (preferred), or WeChat (please send an email to request to add you to the XACS user support group).