Skip to main content

NmrIredPlan

Compute iRED (isotropic reorientational eigenmode dynamics) vectors and correlation matrices for NMR relaxation analysis.

Constructor

from warp_md import NmrIredPlan

plan = NmrIredPlan(
    vector_pairs,
    order=2,
    length_scale=0.1,
    pbc="none",
    corr_mode="tensor",
    return_corr=True
)
vector_pairs
list
required
List of atom index pairs [(atom1, atom2), ...] defining bond vectors (e.g., N-H bonds)
order
int
default:"2"
Legendre polynomial order (1 or 2)
length_scale
float
default:"0.1"
Coordinate scaling factor (use 0.1 to convert Å to nm)
pbc
str
default:"none"
Periodic boundary conditions: "none" or "orthorhombic"
corr_mode
str
default:"tensor"
Correlation calculation mode:
  • "tensor": Compute full correlation matrix
  • "timecorr": Compute time correlation functions
return_corr
bool
default:"True"
Return correlation data (True) or just vectors (False)

run() Method

When return_corr=True:
  • Returns: Tuple of (vectors, correlations)
    • vectors: 3D array of shape (n_frames, n_vectors, 3) containing unit vectors
    • correlations: Correlation matrix or time series
When return_corr=False:
  • Returns: 3D array of vectors only

Example: N-H Order Parameters

import warp_md as wmd
import numpy as np

system = wmd.System("protein.pdb")
traj = wmd.open_trajectory("md.nc", system)

# Define N-H bond vectors
atom_table = system.atom_table()
nh_pairs = []

for i, atom in enumerate(atom_table["name"]):
    if atom == "N":
        resid = atom_table["resid"][i]
        # Find corresponding H
        for j, h_atom in enumerate(atom_table["name"]):
            if h_atom == "H" and atom_table["resid"][j] == resid:
                nh_pairs.append((i, j))
                break

print(f"Found {len(nh_pairs)} N-H vectors")

# Calculate iRED vectors and correlation matrix
plan = wmd.NmrIredPlan(
    nh_pairs,
    order=2,
    length_scale=0.1,
    corr_mode="tensor",
    return_corr=True
)

vectors, corr_matrix = plan.run(traj, system)

# Calculate order parameters S2
n_frames = vectors.shape[0]
m = np.einsum('fpi,fpj->pij', vectors, vectors) / n_frames
tr_m2 = np.einsum('pij,pij->p', m, m)
s2 = 1.5 * tr_m2 - 0.5

print("Order parameters S²:")
for i, (n_idx, h_idx) in enumerate(nh_pairs[:10]):
    resid = atom_table["resid"][n_idx]
    print(f"  Residue {resid}: S² = {s2[i]:.3f}")
Order parameter S² ranges from 0 (isotropic motion) to 1 (completely rigid). Typical values:
  • Rigid secondary structure: S² > 0.8
  • Flexible loops: S² < 0.6
  • Disordered regions: S² < 0.4

Python Helper Functions

For convenience, use the high-level Python API:

nh_order_parameters()

Direct calculation of N-H order parameters.
from warp_md.analysis import nh_order_parameters

# Automatic detection using selection
s2 = nh_order_parameters(
    traj,
    system,
    vector_pairs=None,
    selection="protein and (name N or name H)",
    order=2,
    method="tensor"
)

print(f"Order parameters shape: {s2.shape}")
print(f"Mean S²: {s2.mean():.3f}")
vector_pairs
list
Explicit N-H pairs, or None to auto-detect from selection
selection
str
Atom selection string for auto-detection
method
str
default:"tensor"
Calculation method:
  • "tensor": Direct from correlation tensor
  • "timecorr_fit": Fit time correlation decay
tstep
float
default:"1.0"
Time step in ps (for method="timecorr_fit")
tcorr
float
default:"10000.0"
Correlation time cutoff in ps

Example: Mapping Order Parameters to Structure

import matplotlib.pyplot as plt
import numpy as np
from warp_md.analysis import nh_order_parameters

# Calculate order parameters
s2 = nh_order_parameters(
    traj,
    system,
    selection="protein and backbone",
    method="tensor"
)

# Get residue numbers
atom_table = system.atom_table()
n_indices = [i for i, name in enumerate(atom_table["name"]) if name == "N"]
resids = [atom_table["resid"][i] for i in n_indices[:len(s2)]]

# Plot
plt.figure(figsize=(12, 4))
plt.plot(resids, s2, 'o-')
plt.axhline(0.8, color='r', linestyle='--', alpha=0.5, label='Rigid')
plt.axhline(0.6, color='orange', linestyle='--', alpha=0.5, label='Flexible')
plt.xlabel("Residue Number")
plt.ylabel("Order Parameter S²")
plt.ylim(0, 1)
plt.legend()
plt.title("Backbone Dynamics")
plt.grid(alpha=0.3)
plt.show()

# Identify flexible regions
flexible_residues = [resids[i] for i, val in enumerate(s2) if val < 0.6]
print(f"Flexible regions: {flexible_residues}")

jcoupling()

Compute scalar J-coupling constants from dihedral angles using Karplus equations.
from warp_md.analysis import jcoupling

# Define dihedral angles (4 atoms each)
dihedrals = [
    (10, 11, 12, 13),  # Example: H-N-C-H dihedral
    (20, 21, 22, 23),
]

j_values = jcoupling(
    traj,
    system,
    dihedral_indices=dihedrals,
    karplus=(6.4, -1.4, 1.9),  # A, B, C parameters
    phase_deg=0.0
)

print(f"J-coupling shape: {j_values.shape}")
print(f"Average J-coupling: {j_values.mean(axis=0)} Hz")
dihedral_indices
list
required
List of 4-atom tuples defining dihedral angles
karplus
tuple
default:"(6.4, -1.4, 1.9)"
Karplus equation parameters (A, B, C) for J = A cos²θ + B cosθ + C
kfile
str
Path to file containing Karplus parameters
phase_deg
float
default:"0.0"
Phase offset in degrees
return_dihedral
bool
default:"False"
Also return dihedral angles

Karplus Parameters

Common parameter sets:
# ³J(Hᴺ-Hᴿ) for proteins (Vuister & Bax)
karplus_hn_ha = (7.09, -1.42, 1.55)

# ³J(H-H) for nucleic acids
karplus_nucleic = (9.5, -1.1, 0.9)

# ³J(C-C) for carbohydrates
karplus_carb = (3.5, -0.9, 0.3)

Advanced Example: Validating Against Experiment

import numpy as np
import matplotlib.pyplot as plt
from warp_md.analysis import jcoupling

# Experimental J-coupling data
exp_j_couplings = {
    5: 8.2,   # Residue: J value (Hz)
    6: 7.5,
    7: 9.1,
    # ...
}

# Calculate from simulation
dihedrals = []
residues = []
for resid in exp_j_couplings.keys():
    # H-N-Ca-Ha dihedral
    dih = (
        system.select(f"resid {resid} and name H").indices[0],
        system.select(f"resid {resid} and name N").indices[0],
        system.select(f"resid {resid} and name CA").indices[0],
        system.select(f"resid {resid} and name HA").indices[0],
    )
    dihedrals.append(dih)
    residues.append(resid)

calc_j = jcoupling(
    traj,
    system,
    dihedral_indices=dihedrals,
    karplus=(7.09, -1.42, 1.55)
)

# Average over trajectory
calc_j_avg = calc_j.mean(axis=0)

# Compare
plt.figure(figsize=(8, 6))
exp_values = [exp_j_couplings[r] for r in residues]
plt.scatter(exp_values, calc_j_avg)
plt.plot([6, 10], [6, 10], 'r--', label='Perfect agreement')
plt.xlabel("Experimental ³J (Hz)")
plt.ylabel("Calculated ³J (Hz)")
plt.title("J-coupling Validation")
plt.legend()
plt.grid(alpha=0.3)
plt.show()

# Calculate RMSD
rmsd = np.sqrt(np.mean((np.array(exp_values) - calc_j_avg)**2))
print(f"RMSD from experiment: {rmsd:.2f} Hz")
NMR observables are excellent for validating MD simulations. Good agreement (RMSD < 1 Hz for J-couplings, S² within 0.1) indicates accurate force field and sampling.

Build docs developers (and LLMs) love