Time Correlation Functions
Many physical observables depend on the (Fourier transform of the) Time Correlation Functions (TCF) of some microscopic quantities. Here are some examples:
Infrared, Raman and Sum Frequency Generation spectra
frequency dependent dielectric constant and dielectric susceptibility
Vibrational Density Of States
The way to compute these quantities depends on their definition of course, but a general way to evaluate the TCF from the time series of microscopic quantities wil lbe provided in this following.
Cross Correlation
The Time Correlation Functions (TCF) between two observables \(A,B\) is defined through the cross correlation (similar, but not the same of the convolutuon) of their time series:
A naive implementation of the previous formula would have a computation cost scaling as \(\mathcal{O}\left(N\right)\) where \(N\) is the size of the arrays (assumed to be of the same length for simplicity).
However, we can exploit an analogous of the convolution theorem which allows to express the TCF as:
where \(\mathcal{F}\left[ \cdot \right]\) is the Fourier transform of a function defined as:
Function Documentation
- tcf.tcf.autocorrelate(A: ndarray, axis: int | None = 0) ndarray[source]
Compute the autocorrelation function of an array along the specified axis.
- Parameters:
A (np.ndarray) – array containing the time series
axis (Optional[int], optional) – axis corresponding to time, defaults to 0
- Returns:
autocorrelation function of the array input
- Return type:
np.ndarray
import numpy as np
from typing import Optional
def autocorrelate(A: np.ndarray, axis: Optional[int] = 0) -> np.ndarray:
"""
Compute the autocorrelation function of an array along the specified axis.
:param A: array containing the time series
:type A: np.ndarray
:param axis: axis corresponding to time, defaults to 0
:type axis: Optional[int], optional
:return: autocorrelation function of the array input
:rtype: np.ndarray
"""
len_a = A.shape[axis]
len_fft = 2 * len_a
# Normalization factor (decreasing window size)
norm_tcf = np.arange(len_a, 0, -1, dtype=int)
# Expand normalization dimensions
norm_tcf = np.expand_dims(norm_tcf, tuple(i for i in range(A.ndim) if i != axis))
# Compute FFT of A
ftA = np.fft.rfft(A, axis=axis, n=len_fft)
# Power spectrum (multiply by complex conjugate)
ftA *= np.conj(ftA)
# Inverse FFT to get the auto-correlation
autocorr = np.fft.irfft(ftA, axis=axis, n=len_fft)
# Slice the auto-correlation to original signal length
autocorr = np.take(autocorr, np.arange(len_a), axis=axis)
return autocorr / norm_tcf
Usage Examples
import numpy as np
from tcf import autocorrelate
# Create an array with 1000 random numbers
arr = np.random.rand(1000)
# Compute the autocorrelation function
autocorr = autocorrelate(arr)
# Plot the autocorrelation
import matplotlib.pyplot as plt
plt.plot(autocorr)
plt.xlabel('Lag')
plt.ylabel('Autocorrelation')
plt.show()
Attention
In practical calculations, we do not have infinite time series with the time \(t\) spanning in \((-\infty,+\infty)\), but the time ranges from \(t \in [0,T]\) only.
Moreover, the numerical routines actually implement a monolateral Fourier transform \(\tilde{\mathcal{F}}[ \cdot ]\) of the following form:
Infrared Spectrum
The Vibrational Infrared Absorption spectrum \(S\left(\omega\right)\), at thermal equilibrium, can be evaluated using the time series of the dipole using the following expression:
where
\(\alpha\left(\omega\right)\) is the Beer-Lambert absorption coefficient,
\(n\left(\omega\right)\) is the refractive index of the material,
\(\beta = \frac{1}{k_B T}\) is the thermodynamic beta,
\(c\) is the speed of light,
\(\Omega\) is the system volume
\(\varepsilon_0\) is the vacuum permittivity,
and clearly
- It is important to stress a couple of things:
the TCF should actually be calculated for the fluctuation of the dipole w.r.t. to its average value:
\[\boldsymbol{\mu}\star\boldsymbol{\mu}(t) \longrightarrow C_{\delta\boldsymbol{\mu},\delta\boldsymbol{\mu}}(t) \quad \text{with} \quad \delta\boldsymbol{\mu} = \boldsymbol{\mu} - \braket{\boldsymbol{\mu}}\]and this means that, before computing any Fourier transform, one should remove the mean value of the time series. However, we will keep using the same notation as before for simplicity.
the definition of the infrared spectrum requires an integration over time which actually range in \((-\infty,+\infty)\).
The TCF of the dipole with itself is also time-reversible, i.e.
\[\left(\boldsymbol{\mu}\star\boldsymbol{\mu}\right)(t) = \left(\boldsymbol{\mu}\star\boldsymbol{\mu}\right)(-t)\]and this implies that its Fourier transform in purely real.
However, in numerical implementation we can only evaluate the monolateral Fourier transform \(\tilde{\mathcal{F}}[ \cdot ]\), whose output is complex since no integration over time for \(t\lt 0\) occurs.
For this reason, to guarantee the correct calculation of the infrared spectrum, what is actually implemented numerically is
\[\begin{split}S\left(\omega\right) = & \frac{\beta\omega^2}{6c\Omega\varepsilon_0} \, 2 \, {\rm Re} \tilde{\mathcal{F}}\left[\boldsymbol{\mu}\star\boldsymbol{\mu}\right]\left(\omega\right) \\ = & \frac{\beta\omega^2}{3c\Omega\varepsilon_0} {\rm Re} \int_{0}^{+\infty} e^{-i \omega t} \left(\boldsymbol{\mu}\star\boldsymbol{\mu}\right)\left(t\right) \, dt\end{split}\]
A common trick
\[S\left(\omega\right) = \frac{\beta}{3c\Omega\varepsilon_0} {\rm Re} \int_{0}^{+\infty} e^{-i \omega t} \left(\dot{\boldsymbol{\mu}}\star\dot{\boldsymbol{\mu}}\right)\left(t\right) \, dt\]