Tutorial

In this tutorial, we compute the proton (\(^1\mathrm{H}\)) NMR relaxation properties of a polymer–water mixture directly from a molecular dynamics trajectory generated using LAMMPS. We use NMRDfromMD to calculate the longitudinal and transverse relaxation times (\(T_1\) and \(T_2\)), extract the frequency-dependent relaxation rates (\(R_1(f)\) and \(R_2(f)\)), and separate the intra- and intermolecular contributions to the relaxation.

By the end of this tutorial, you will understand both the basic NMRDfromMD workflow and the physical meaning of the quantities computed by the package.

NMR relaxation originates from fluctuations of the local magnetic fields generated by neighbouring nuclear spins. In liquids, these fluctuations are driven by molecular motion, making relaxation measurements sensitive to rotational diffusion, translational diffusion, and molecular structure. Molecular dynamics simulations provide the atomic trajectories needed to predict these relaxation properties directly.

To follow the tutorial, MDAnalysis, NumPy, and Matplotlib must be installed.

Note

If you’d like to learn LAMMPS and generate your own trajectories, beginner tutorials are available here [27].

System

PEG-water mixture simulated with LAMMPS - Dipolar NMR relaxation time calculation PEG-water mixture simulated with LAMMPS - Dipolar NMR relaxation time calculation

The simulated system consists of a bulk mixture containing 420 \(\mathrm{H_2O}\) molecules and 30 \(\mathrm{PEG\ 300}\) polymer molecules. Water is described using the \(\mathrm{TIP4P}-\epsilon\) force field [28]. \(\mathrm{PEG\ 300}\) denotes polyethylene glycol chains with a molar mass of \(300~\mathrm{g/mol}\).

The trajectory was recorded during a \(10~\text{ns}\) production run performed using the open-source code LAMMPS in the \(NpT\) ensemble with a timestep of \(1~\text{fs}\). The temperature was set to \(T = 300~\text{K}\) and the pressure to \(p = 1~\text{atm}\).

Atomic positions were saved to the production.xtc trajectory every \(2~\mathrm{ps}\).

Only the saved configurations are analysed by NMRDfromMD. Consequently, the temporal resolution of the calculated correlation functions is determined by the trajectory sampling interval (\(2~\mathrm{ps}\)), rather than by the \(1~\mathrm{fs}\) integration timestep used during the molecular dynamics simulation. This distinction is important because all correlation functions computed later are limited by this sampling interval.

File preparation

To access the LAMMPS input files and pre-computed trajectory data, either download the zip archive or clone the peg-water-mixture dataset repository:

git clone https://github.com/NMRDfromMD/dataset-peg-water-mixture.git

The required trajectory files are located in the data/ directory.

Important

The trajectory files are stored using Git Large File Storage (Git LFS). This means that after cloning the repository, you must download the actual trajectory data before using it.

If Git LFS is not installed, install it first:

apt install git-lfs
git lfs install

Then retrieve the trajectory files:

git lfs pull

Alternatively, you can regenerate the trajectory by rerunning the LAMMPS simulation scripts provided in the repository.

Import the simulation data into Python

Open a new Python script or Notebook, and define the path to the data files:

datapath = "mypath/polymer-in-water/data/"

Then, import NumPy, MDAnalysis, and the NMRD module of NMRDfromMD:

import numpy as np
import MDAnalysis as mda
from nmrdfrommd import NMRD

From the trajectory files, create a universe by loading the configuration file and trajectory:

u = mda.Universe(datapath+"production.data",
                 datapath+"production.xtc")

The Universe is the central object in MDAnalysis. It combines the system topology (atom identities, masses, molecule definitions, etc.) with the time-dependent atomic coordinates. In this tutorial, NMRDfromMD uses the Universe to access both the molecular structure and the atomic trajectories required to compute dipolar correlation functions and NMR relaxation properties.

Before starting the analysis, it is useful to verify that the system has been correctly loaded and to inspect its basic composition.

n_TOT = u.atoms.n_residues
n_H2O = u.select_atoms("type 6 7").n_residues
n_PEG = u.select_atoms("type 1 2 3 4 5").n_residues

print(f"The total number of molecules is {n_TOT} ({n_H2O} H2O, {n_PEG} PEG)")

Executing the script using Python will return:

The total number of molecules is 450 (420 H2O, 30 PEG)

This output confirms that the simulation contains the expected 450 molecules, correctly partitioned into 420 water molecules and 30 PEG chains.

Let us also print information concerning the trajectory, namely the timestep, timestep and the total duration of the simulation, total_time:

timestep = np.int32(u.trajectory.dt)
total_time = np.int32(u.trajectory.totaltime)

print(f"The timestep is {timestep} ps")
print(f"The total simulation time is {total_time//1000} ns")

Executing the script using Python will return:

The timestep is 2 ps
The total simulation time is 10 ns

In MDAnalysis, the timestep refers to the time interval between two stored frames in the trajectory file, not the integration timestep used in the molecular dynamics simulation. In this tutorial, the trajectory was generated with LAMMPS using a 1 fs integration timestep, but atomic configurations were written to the production.xtc file every 2 ps. As a result, the timestep reported by MDAnalysis corresponds to this 2 ps sampling interval, which determines the temporal resolution of all subsequent correlation functions and relaxation calculations.

Run the \(^1\text{H}\) NMR relaxation analysis

The NMR relaxation calculation is performed on selected nuclei. Here we create three atom groups: the hydrogen atoms belonging to PEG, the hydrogen atoms belonging to water, and the combined set containing all hydrogen atoms:

H_PEG = u.select_atoms("type 3 5")
H_H2O = u.select_atoms("type 7")
H_ALL = H_PEG + H_H2O

Next, we run NMRDfromMD for all hydrogen atoms:

nmr_all = NMRD(
    u=u,
    atom_group=H_ALL,
    number_i=20)
nmr_results = nmr_all.run_analysis()

On a standard laptop (Intel Core i9-12900H processor), this step typically takes 1-2 minutes.

The runtime depends mainly on three factors: (i) the number of selected reference atoms (number_i), (ii) the number of atoms in the system, and (iii) the number of saved trajectory frames.

The parameter number_i controls how many reference hydrogen atoms are randomly selected for the calculation. Computing the dipolar interaction for every hydrogen atom can become computationally expensive in large systems. Instead, NMRDfromMD samples a subset of reference atoms while retaining all neighbouring atoms. This strategy provides an unbiased estimate of the relaxation rates while reducing computational cost. This sampling reduces the computational cost at the expense of increased statistical uncertainty.

Increasing number_i improves the statistical precision of the calculated relaxation rates, while setting number_i = 0 includes all eligible atoms in the calculation.

All calculated values are stored within the nmr_results dictionary. Lets first extract the NMR relaxation time \(T_1\) in the limit \(f \to 0\), add the following lines to the Python script:

T1 = nmr_results["T1"]

print(f"The NMR relaxation time is T1 = {T1:.2f} s")

The output should be similar to:

The NMR relaxation time is T1 = 1.59 s

The exact value may vary slightly between runs because the reference atoms are selected randomly whenever number_i > 0. Increasing number_i reduces this statistical uncertainty, whereas setting number_i = 0 performs the calculation for every hydrogen atom in the selected group.

Extract the \(^1\text{H}\)-NMR spectra

Although the zero-frequency relaxation time is often reported experimentally, NMRDfromMD computes the complete relaxation dispersion over a wide frequency range. The longitudinal and transverse relaxation rates,

\[R_1(f)=\frac{1}{T_1(f)}, \qquad R_2(f)=\frac{1}{T_2(f)},\]

are available for every frequency \(f\) (in MHz) as nmr_all.R1 and nmr_all.R2. The corresponding frequencies are stored in nmr_all.f.

R1_spectrum = nmr_results["R1"]
R2_spectrum = nmr_results["R2"]
f = nmr_results["f"]

The spectra \(R_1 (f)\) and \(R_2 (f)\) can then be plotted as a function of \(f\) using pyplot:

from matplotlib import pyplot as plt

# Plot settings
plt.figure(figsize=(8, 5))
plt.loglog(f, R1_spectrum, 'o', label='R1', markersize=5)
plt.loglog(f, R2_spectrum, 's', label='R2', markersize=5)
# Labels and Title
plt.xlabel("Frequency (MHz)", fontsize=12)
plt.ylabel("Relaxation Rates (s⁻¹)", fontsize=12)
# Grid and boundaries
plt.grid(True, which="both", linestyle='--', linewidth=0.7)
plt.xlim(80, 1e5)
plt.ylim(0.05, 2)
# Legend
plt.legend()
plt.tight_layout()
plt.show()

The resulting spectra should resemble the figure below (panel A). For an isotropic liquid, \(R_1(f)\) and \(R_2(f)\) are expected to approach similar values in the low-frequency limit. In this regime, both relaxation rates probe the low-frequency limit of the spectral density, which is dominated by long-time molecular reorientations and translational diffusion.

Because only number_i = 20 reference atoms are sampled here, the spectra exhibit noticeable statistical noise. Repeating the calculation with a larger value of number_i produces much smoother curves, as shown in panel B.

NMR results obtained from the LAMMPS simulation of water NMR results obtained from the LAMMPS simulation of water

Figure: (A) \(^1\text{H}\)-NMR relaxation rates \(R_1\) and \(R_2\) as a function of the frequency \(f\) for the \(\text{PEG-H}_2\text{O}\) bulk mixture. Results are given for a small value of number_i, \(n_i = 20\). (B) Same quantity as in panel A, but with \(n_i = 1300\).

Separating intra and intermolecular

So far, the relaxation rates were calculated without distinguishing between intra- and intermolecular interactions. One of the major advantages of molecular dynamics simulations is that every dipolar interaction can be classified according to whether it originates from two nuclei within the same molecule or from two different molecules. This separation is generally not accessible from experimental measurements alone.

Let us extract the intramolecular contributions to the relaxation for both water and PEG, separately:

nmr_h2o_intra = NMRD(
    u=u,
    atom_group=H_H2O,
    type_analysis="intra_molecular",
    number_i=200)
result_h2o_intra = nmr_h2o_intra.run_analysis()

nmr_peg_intra = NMRD(
    u=u,
    atom_group=H_PEG,
    type_analysis="intra_molecular",
    number_i=200)
result_peg_intra = nmr_peg_intra.run_analysis()

Similarly, we can also measure the intermolecular contributions:

nmr_h2o_inter = NMRD(
    u=u,
    atom_group=H_H2O,
    type_analysis="inter_molecular",
    number_i=20)
result_h2o_inter = nmr_h2o_inter.run_analysis()

nmr_peg_inter = NMRD(
    u=u,
    atom_group=H_PEG,
    type_analysis="inter_molecular",
    number_i=20)
result_peg_inter = nmr_peg_inter.run_analysis()

The intermolecular contribution is computed only between molecules belonging to the same chemical species. For example, nmr_h2o_inter includes interactions between different water molecules, but not between water and PEG molecules.

The water-PEG intermolecular contribution can be computed by selecting water hydrogen atoms as the reference group (atom_group) and PEG hydrogen atoms as the interacting partner group (neighbor_group):

nmr_h2o_peg = NMRD(
    u=u,
    atom_group=H_H2O,
    neighbor_group=H_PEG,
    number_i=20)
result_h2o_peg = nmr_h2o_peg.run_analysis()

In this case, the analysis is already restricted to intermolecular interactions between two different molecular species. Therefore, it is not necessary to explicitly set type_analysis="inter_molecular".

Comparing the calculated spectra reveals that the intramolecular contribution is larger than the intermolecular one for this system. More importantly, the two contributions exhibit distinct frequency dependences because they originate from different molecular motions. Intramolecular relaxation is mainly governed by rotational motion and internal molecular flexibility, whereas intermolecular relaxation reflects translational diffusion and inter-molecular collisions.

NMR results obtained from the LAMMPS simulation of water NMR results obtained from the LAMMPS simulation of water

Figure: Intramolecular \(^1\text{H}\)-NMR relaxation rates \(R_{1 \text{R}}\) (A) and Intermolecular \(^1\text{H}\)-NMR relaxation rates \(R_{1 \text{T}}\) (B) as a function of the frequency \(f\) for PEG and \(\text{H}_2\text{O}\) separately. Results are shown for \(n_i = 1280\).