Mean squared displacement¶
MeanSquaredDisplacement gives the mean squared displacement of the atoms
of a trajectory against the lag between frames, from which the diffusion
coefficient follows.
import numpy as np
import mdapy as mp
from mdapy.analysis import MeanSquaredDisplacement
# 100 frames of 50 atoms on a random walk in a 10 Å box, positions wrapped
rng = np.random.default_rng(0)
box = mp.Box(10.0)
walk = np.cumsum(rng.normal(scale=0.1, size=(100, 50, 3)), axis=0)
frames = [mp.System(box.wrap(x)[0], box, elements="Ar") for x in walk]
result = MeanSquaredDisplacement().compute(frames) # or a Trajectory
result.lag, result.msd # lag in frames, MSD in Ų
dt = 0.1 # ps between frames
slope = np.polyfit(result.lag[1:50] * dt, result.msd[1:50], 1)[0]
diffusion = slope / 6 # Ų/ps, about 0.05
from mdapy.visualization import plot
with plot.style():
fig, ax = plot.figure("single")
plot.msd(result, timestep=dt, ax=ax, fit=(1, 50))
plot.save(fig, "images/msd", formats=("png",))

The random walk spreads linearly in time; the slope of the fit over 6 is the diffusion coefficient in the legend.
Parameters¶
| Name | Type | Default | Unit | Meaning |
|---|---|---|---|---|
window |
bool | True |
average over every time origin, or measure from the first frame only | |
unwrap |
bool | True |
follow the atoms through the periodic boundaries, or take the positions as they are, already unwrapped |
compute(frames) takes a sequence of systems, such as a list or a
Trajectory, with the same atoms in
every frame. Where the first frame has an id column, as a LAMMPS dump
does, every frame needs one and the atoms are matched by their ids, in
whatever order each frame lists them; without ids, the atoms are in the same
order in every frame.
With unwrap=True, each atom is followed from frame to frame through the
periodic boundaries:
- by its image flags, the
imagecolumn, as in a LAMMPS dump withix iy iz, where two consecutive frames both have them; - otherwise by the periodic image nearest to its position in the previous frame, which needs every atom to move less than half the box between two frames.
The box may change from frame to frame. With unwrap=False, for positions
already unwrapped, such as xu yu zu of a LAMMPS dump, the positions are
used as they are.
Results¶
A DisplacementResult:
| Attribute | Type | Meaning |
|---|---|---|
lag |
(T,) int64 | the lag \(m\) in frames, from 0 to T − 1; the lag time is \(m\) times the time between frames |
msd |
(T,) float64 | the mean squared displacement in Ų for each lag |
components |
(T, 3) float64 | its x, y and z parts in Ų |
Errors¶
compute raises InvalidParameter without frames, for frames with
different numbers of atoms, for frames without atoms, and, where the first
frame has ids, for repeated ids or a frame without the same ids.
Definition¶
With the positions \(\mathbf r_i(k)\) of the \(N\) atoms in frame \(k\) of \(T\),
unwrapped as above, the mean squared displacement averaged over every time
origin (window=True) is
computed with fast Fourier transforms as described by
Calandrini et al. (2011); from
the first frame only (window=False) it is
The average over every origin is smoother, but the largest lags have few origins: \(T - m\), only one at \(m = T - 1\). For atoms that diffuse with the coefficient \(D\), \(\text{MSD} \to 6 D t\) at long times \(t\), and each component tends to \(2 D t\).
Please cite
With window=True, Calandrini et al. (2011).
See also¶
- LAMMPS
compute msd, the same aswindow=False. - OVITO Calculate displacements after Unwrap trajectories.