Skill Tracer
Skills › Scientific Agent Skills › molecular-dynamics › mdanalysis_analysis.md

mdanalysis_analysis.md

260 lines · skills/molecular-dynamics/references/mdanalysis_analysis.md at 5f2bc03

Open on GitHub
1# MDAnalysis Analysis Reference
2
3Reviewed against MDAnalysis 2.10.0 on 2026-10-01. Examples use local topology
4and trajectory files and are illustrative outside the synthetic regression checks.
5Atom order, molecule integrity, box vectors and time units must be checked first.
6

MDAnalysis Analysis Reference

Reviewed against MDAnalysis 2.10.0 on 2026-10-01. Examples use local topology and trajectory files and are illustrative outside the synthetic regression checks. Atom order, molecule integrity, box vectors and time units must be checked first.

Raw lines 1 to 6
7## MDAnalysis Universe and AtomGroup
8
9```python
10import MDAnalysis as mda
12# Load Universe
13u = mda.Universe("topology.pdb", "trajectory.dcd")
14# or for single structure:
15u = mda.Universe("structure.pdb")
17# Key attributes
18print(u.atoms.n_atoms) # Total atoms
19print(u.residues.n_residues) # Total residues
20print(u.trajectory.n_frames) # Number of frames
21print(u.trajectory.dt) # Time step in ps
22print(u.trajectory.totaltime) # Stored span, (n_frames - 1) * dt, in ps
23```

MDAnalysis Universe and AtomGroup

import MDAnalysis as mda

# Load Universe
u = mda.Universe("topology.pdb", "trajectory.dcd")
# or for single structure:
u = mda.Universe("structure.pdb")

# Key attributes
print(u.atoms.n_atoms)          # Total atoms
print(u.residues.n_residues)    # Total residues
print(u.trajectory.n_frames)   # Number of frames
print(u.trajectory.dt)         # Time step in ps
print(u.trajectory.totaltime)  # Stored span, (n_frames - 1) * dt, in ps
Raw lines 7 to 24
25## Atom Selection Language
27MDAnalysis uses a rich selection language:
29```python
30# Basic selections
31protein = u.select_atoms("protein")
32backbone = u.select_atoms("backbone") # CA, N, C, O
33calpha = u.select_atoms("protein and name CA")
34water = u.select_atoms("resname WAT or resname HOH or resname TIP3")
35ligand = u.select_atoms("resname LIG")
37# By residue number
38region = u.select_atoms("resid 10:50")
39specific = u.select_atoms("resid 45 and name CA")
41# By proximity
42near_ligand = u.select_atoms("protein and around 5.0 resname LIG", updating=True)
44# By property
45charged = u.select_atoms("resname ARG LYS ASP GLU")
46hydrophobic = u.select_atoms("resname ALA VAL LEU ILE PRO PHE TRP MET")
48# Boolean combinations
49active_site = u.select_atoms("(resid 100 102 145 200) and protein")
51# Inverse
52not_water = u.select_atoms("not (resname WAT HOH)")
53```
55Geometric selections use periodic dimensions by default; `updating=True`
56re-evaluates the selection each frame. Verify residue naming (`protein` uses
57recognized residue names) and use `resindex` plus a saved identity map when IDs
58repeat across chains.

Atom Selection Language

MDAnalysis uses a rich selection language:

# Basic selections
protein = u.select_atoms("protein")
backbone = u.select_atoms("backbone")  # CA, N, C, O
calpha = u.select_atoms("protein and name CA")
water = u.select_atoms("resname WAT or resname HOH or resname TIP3")
ligand = u.select_atoms("resname LIG")

# By residue number
region = u.select_atoms("resid 10:50")
specific = u.select_atoms("resid 45 and name CA")

# By proximity
near_ligand = u.select_atoms("protein and around 5.0 resname LIG", updating=True)

# By property
charged = u.select_atoms("resname ARG LYS ASP GLU")
hydrophobic = u.select_atoms("resname ALA VAL LEU ILE PRO PHE TRP MET")

# Boolean combinations
active_site = u.select_atoms("(resid 100 102 145 200) and protein")

# Inverse
not_water = u.select_atoms("not (resname WAT HOH)")

Geometric selections use periodic dimensions by default; updating=True re-evaluates the selection each frame. Verify residue naming (protein uses recognized residue names) and use resindex plus a saved identity map when IDs repeat across chains.

Raw lines 25 to 59
60## Common Analysis Modules

Common Analysis Modules

Raw lines 60 to 61
62### RMSD and RMSF
64```python
65from MDAnalysis.analysis import rms, align
67# Make molecules whole using trusted bonds before this structural analysis.
68# Keep original coordinates/boxes for separate periodic contact calculations.
69aligned = u.copy()
70aligned.trajectory[0]
71align.AlignTraj(aligned, aligned, select='backbone', in_memory=True).run()
73# RMSD
74R = rms.RMSD(aligned, select='backbone', groupselections=['protein and name CA'], ref_frame=0)
75R.run()
76# Shape (n_frames, 4): frame, time (ps), backbone RMSD, CA RMSD (Å).
77# CA RMSD uses the backbone fit; groupselections are not fitted independently.
79# RMSF (per-atom fluctuations)
80from MDAnalysis.analysis.rms import RMSF
81rmsf = RMSF(aligned.select_atoms('backbone')).run()
82# rmsf.results.rmsf: per-atom RMSF values in Angstroms
83```

RMSD and RMSF

from MDAnalysis.analysis import rms, align

# Make molecules whole using trusted bonds before this structural analysis.
# Keep original coordinates/boxes for separate periodic contact calculations.
aligned = u.copy()
aligned.trajectory[0]
align.AlignTraj(aligned, aligned, select='backbone', in_memory=True).run()

# RMSD
R = rms.RMSD(aligned, select='backbone', groupselections=['protein and name CA'], ref_frame=0)
R.run()
# Shape (n_frames, 4): frame, time (ps), backbone RMSD, CA RMSD (Å).
# CA RMSD uses the backbone fit; groupselections are not fitted independently.

# RMSF (per-atom fluctuations)
from MDAnalysis.analysis.rms import RMSF
rmsf = RMSF(aligned.select_atoms('backbone')).run()
# rmsf.results.rmsf: per-atom RMSF values in Angstroms
Raw lines 62 to 84
85### Radius of Gyration
87```python
88protein = aligned.select_atoms("protein") # Must be whole; valid atomic masses required.
89rg = []
90for ts in aligned.trajectory:
91 rg.append(protein.radius_of_gyration())
92import numpy as np
93print(f"Mean Rg: {np.mean(rg):.2f} Å")
94```

Radius of Gyration

protein = aligned.select_atoms("protein")  # Must be whole; valid atomic masses required.
rg = []
for ts in aligned.trajectory:
    rg.append(protein.radius_of_gyration())
import numpy as np
print(f"Mean Rg: {np.mean(rg):.2f} Å")
Raw lines 85 to 95
96### Secondary Structure Analysis
98```python
99from MDAnalysis.analysis.dssp import DSSP
101# DSSP secondary structure assignment per frame
102dssp = DSSP(u).run()
103# dssp.results.dssp: per-residue per-frame secondary structure codes
104# H = helix (including 3_10 and pi), E = strand, - = loop/unordered.
105# Requires complete, correctly ordered backbone atoms; not eight-state DSSP.
106```

Secondary Structure Analysis

from MDAnalysis.analysis.dssp import DSSP

# DSSP secondary structure assignment per frame
dssp = DSSP(u).run()
# dssp.results.dssp: per-residue per-frame secondary structure codes
# H = helix (including 3_10 and pi), E = strand, - = loop/unordered.
# Requires complete, correctly ordered backbone atoms; not eight-state DSSP.
Raw lines 96 to 107
108### Hydrogen Bonds
110```python
111from MDAnalysis.analysis.hydrogenbonds import HydrogenBondAnalysis
113# Illustrative BACKBONE-only selection; adapt names to the actual topology.
114# Explicit H selection avoids charge-based guessing on PDB files without charges.
115# With reliable bonds, prefer donors_sel=None to pair bonded donor and H atoms.
116hbonds = HydrogenBondAnalysis(
117 u,
118 donors_sel="protein and name N",
119 hydrogens_sel="protein and name H HN",
120 acceptors_sel="protein and name O",
121 d_h_cutoff=1.2, # donor-H distance (Å)
122 d_a_cutoff=3.0, # donor-acceptor distance (Å)
123 d_h_a_angle_cutoff=150 # D-H-A angle (degrees)
125hbonds.run()
127counts = hbonds.count_by_time() # One count per analyzed frame (including zeros).
128# The result table has one row per bond occurrence, not one row per frame.
129import pandas as pd
130df = pd.DataFrame(hbonds.results.hbonds,
131 columns=['frame', 'donor_ix', 'hydrogen_ix', 'acceptor_ix',
132 'DA_dist', 'DHA_angle'])
133```

Hydrogen Bonds

from MDAnalysis.analysis.hydrogenbonds import HydrogenBondAnalysis

# Illustrative BACKBONE-only selection; adapt names to the actual topology.
# Explicit H selection avoids charge-based guessing on PDB files without charges.
# With reliable bonds, prefer donors_sel=None to pair bonded donor and H atoms.
hbonds = HydrogenBondAnalysis(
    u,
    donors_sel="protein and name N",
    hydrogens_sel="protein and name H HN",
    acceptors_sel="protein and name O",
    d_h_cutoff=1.2,          # donor-H distance (Å)
    d_a_cutoff=3.0,          # donor-acceptor distance (Å)
    d_h_a_angle_cutoff=150   # D-H-A angle (degrees)
)
hbonds.run()

counts = hbonds.count_by_time()  # One count per analyzed frame (including zeros).
# The result table has one row per bond occurrence, not one row per frame.
import pandas as pd
df = pd.DataFrame(hbonds.results.hbonds,
                  columns=['frame', 'donor_ix', 'hydrogen_ix', 'acceptor_ix',
                           'DA_dist', 'DHA_angle'])
Raw lines 108 to 134
135### Principal Component Analysis (PCA)
137```python
138from MDAnalysis.analysis import pca
140# Requires atom types (normally inferred when reading PDB) and matched atom order.
141# Fit AND project the same already aligned coordinates. transform() does not align.
142pca_analysis = pca.PCA(aligned, select='backbone', align=False).run()
144# PC variances
145print(pca_analysis.results.variance[:5]) # Raw coordinate variance in Ų.
146print(100 * pca_analysis.results.cumulated_variance[:5]) # Cumulative percent.
148# Project trajectory onto PCs
149projected = pca_analysis.transform(aligned.select_atoms('backbone'), n_components=3)
150# Shape: (n_frames, n_components)
151```

Principal Component Analysis (PCA)

from MDAnalysis.analysis import pca

# Requires atom types (normally inferred when reading PDB) and matched atom order.
# Fit AND project the same already aligned coordinates. transform() does not align.
pca_analysis = pca.PCA(aligned, select='backbone', align=False).run()

# PC variances
print(pca_analysis.results.variance[:5])  # Raw coordinate variance in Ų.
print(100 * pca_analysis.results.cumulated_variance[:5])  # Cumulative percent.

# Project trajectory onto PCs
projected = pca_analysis.transform(aligned.select_atoms('backbone'), n_components=3)
# Shape: (n_frames, n_components)
Raw lines 135 to 152
153### Free Energy Surface (FES)
155This is a relative, coordinate-dependent `-RT log p` surface for equilibrated,
156unbiased sampling at one temperature. Biased/enhanced sampling requires the
157appropriate reweighting first. Empty bins are unsampled, not measured barriers.
158Bin size, coordinate Jacobians and autocorrelation affect interpretation; report
159replicate/block uncertainty. A projected histogram is not a binding free energy.
161```python
162import numpy as np
163import matplotlib.pyplot as plt
165def plot_free_energy_surface(x, y, bins=50, T=300, xlabel="PC1", ylabel="PC2",
166 output="fes.png"):
167 """
168 Compute 2D free energy surface from two order parameters.
169 FES = -kT * ln(P(x,y))
170 """
171 x, y = np.asarray(x, dtype=float), np.asarray(y, dtype=float)
172 if x.ndim != 1 or y.shape != x.shape or len(x) < 2:
173 raise ValueError("x and y must be equal-length 1D arrays with at least two samples")
174 if not np.all(np.isfinite(x)) or not np.all(np.isfinite(y)) or not np.isfinite(T) or T <= 0:
175 raise ValueError("Coordinates must be finite and T must be finite and positive")
176 R_gas = 0.00831446261815324 # kJ/mol/K (molar gas constant)
177 kT = R_gas * T
179 # 2D histogram
180 H, xedges, yedges = np.histogram2d(x, y, bins=bins, density=True)
181 H = H.T
183 # Free energy
184 H_safe = np.where(H > 0, H, np.nan)
185 fes = -kT * np.log(H_safe)
186 fes -= np.nanmin(fes) # Shift minimum to 0
188 # Plot
189 fig, ax = plt.subplots(figsize=(8, 6))
190 im = ax.pcolormesh(xedges, yedges, np.ma.masked_invalid(fes),
191 shading='flat', cmap='RdYlBu_r')
192 plt.colorbar(im, ax=ax, label='Free Energy (kJ/mol)')
193 ax.set_xlabel(xlabel)
194 ax.set_ylabel(ylabel)
195 plt.savefig(output, dpi=150, bbox_inches='tight')
196 return fig
197```

Free Energy Surface (FES)

This is a relative, coordinate-dependent -RT log p surface for equilibrated, unbiased sampling at one temperature. Biased/enhanced sampling requires the appropriate reweighting first. Empty bins are unsampled, not measured barriers. Bin size, coordinate Jacobians and autocorrelation affect interpretation; report replicate/block uncertainty. A projected histogram is not a binding free energy.

import numpy as np
import matplotlib.pyplot as plt

def plot_free_energy_surface(x, y, bins=50, T=300, xlabel="PC1", ylabel="PC2",
                              output="fes.png"):
    """
    Compute 2D free energy surface from two order parameters.
    FES = -kT * ln(P(x,y))
    """
    x, y = np.asarray(x, dtype=float), np.asarray(y, dtype=float)
    if x.ndim != 1 or y.shape != x.shape or len(x) < 2:
        raise ValueError("x and y must be equal-length 1D arrays with at least two samples")
    if not np.all(np.isfinite(x)) or not np.all(np.isfinite(y)) or not np.isfinite(T) or T <= 0:
        raise ValueError("Coordinates must be finite and T must be finite and positive")
    R_gas = 0.00831446261815324  # kJ/mol/K (molar gas constant)
    kT = R_gas * T

    # 2D histogram
    H, xedges, yedges = np.histogram2d(x, y, bins=bins, density=True)
    H = H.T

    # Free energy
    H_safe = np.where(H > 0, H, np.nan)
    fes = -kT * np.log(H_safe)
    fes -= np.nanmin(fes)  # Shift minimum to 0

    # Plot
    fig, ax = plt.subplots(figsize=(8, 6))
    im = ax.pcolormesh(xedges, yedges, np.ma.masked_invalid(fes),
                       shading='flat', cmap='RdYlBu_r')
    plt.colorbar(im, ax=ax, label='Free Energy (kJ/mol)')
    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    plt.savefig(output, dpi=150, bbox_inches='tight')
    return fig
Raw lines 153 to 198
199## Trajectory Formats Supported
201| Format | Extension | Notes |
202|--------|-----------|-------|
203| DCD | `.dcd` | CHARMM/NAMD binary; widely used |
204| XTC | `.xtc` | GROMACS compressed |
205| TRR | `.trr` | GROMACS full precision |
206| NetCDF | `.nc` | AMBER format |
207| LAMMPS | `.lammpstrj` | LAMMPS dump |
208| HDF5 | `.h5md` | H5MD standard |
209| PDB | `.pdb` | Multi-model PDB |

Trajectory Formats Supported

FormatExtensionNotes
DCD.dcdCHARMM/NAMD binary; widely used
XTC.xtcGROMACS compressed
TRR.trrGROMACS full precision
NetCDF.ncAMBER format
LAMMPS.lammpstrjLAMMPS dump
HDF5.h5mdH5MD standard
PDB.pdbMulti-model PDB
Raw lines 199 to 210
211## MDAnalysis Interoperability
213```python
214# Convert to numpy
215positions = u.atoms.positions.copy() # Snapshot in Å: shape (N, 3)
217# Write to PDB
218with mda.Writer("frame_10.pdb", u.atoms.n_atoms) as W:
219 u.trajectory[10] # Move to frame 10
220 W.write(u.atoms)
222# Write a topology with exactly the subset atom order before its trajectory.
223protein = u.select_atoms("protein")
224protein.write("protein_topology.pdb")
225# Write a uniformly sampled trajectory subset, preserving the frame interval.
226# DCD cannot represent arbitrary timestamps; separately record the time origin.
227with mda.Writer("protein_traj.dcd", protein.n_atoms, dt=u.trajectory.dt) as W:
228 for ts in u.trajectory:
229 W.write(u.select_atoms("protein"))
231# Convert to MDTraj (for compatibility)
232# import mdtraj as md
233# traj = md.load("trajectory.dcd", top="topology.pdb")
234```

MDAnalysis Interoperability

# Convert to numpy
positions = u.atoms.positions.copy()  # Snapshot in Å: shape (N, 3)

# Write to PDB
with mda.Writer("frame_10.pdb", u.atoms.n_atoms) as W:
    u.trajectory[10]  # Move to frame 10
    W.write(u.atoms)

# Write a topology with exactly the subset atom order before its trajectory.
protein = u.select_atoms("protein")
protein.write("protein_topology.pdb")
# Write a uniformly sampled trajectory subset, preserving the frame interval.
# DCD cannot represent arbitrary timestamps; separately record the time origin.
with mda.Writer("protein_traj.dcd", protein.n_atoms, dt=u.trajectory.dt) as W:
    for ts in u.trajectory:
        W.write(u.select_atoms("protein"))

# Convert to MDTraj (for compatibility)
# import mdtraj as md
# traj = md.load("trajectory.dcd", top="topology.pdb")
Raw lines 211 to 235
236## Performance Tips
238- **Use `in_memory=True`** for AlignTraj when RAM allows (much faster iteration)
239- **Select minimal atoms** before analysis to reduce memory/compute
240- **Parallel execution:** inspect `get_supported_backends()` for that class; do not
241 assume every analysis or coordinate transformation supports multiprocessing
242- **Choose a frame slice** with `start`/`stop`/`step`; slicing is not a general streaming
243 guarantee (PCA still builds a coordinate covariance matrix):
245```python
246# Analyze every 10th frame from frame 100 to 1000
247R.run(start=100, stop=1000, step=10)
248```

Performance Tips

  • **Use in_memory=True** for AlignTraj when RAM allows (much faster iteration)
  • Select minimal atoms before analysis to reduce memory/compute
  • Parallel execution: inspect get_supported_backends() for that class; do not assume every analysis or coordinate transformation supports multiprocessing
  • Choose a frame slice with start/stop/step; slicing is not a general streaming guarantee (PCA still builds a coordinate covariance matrix):
# Analyze every 10th frame from frame 100 to 1000
R.run(start=100, stop=1000, step=10)
Raw lines 236 to 250
251## Official API references
253- [RMSD/RMSF](https://docs.mdanalysis.org/2.10.0/documentation_pages/analysis/rms.html)
254- [Alignment](https://docs.mdanalysis.org/2.10.0/documentation_pages/analysis/align.html)
255- [PCA](https://docs.mdanalysis.org/2.10.0/documentation_pages/analysis/pca.html)
256- [DSSP](https://docs.mdanalysis.org/2.10.0/documentation_pages/analysis/dssp.html)
257- [Hydrogen bonds](https://docs.mdanalysis.org/2.10.0/documentation_pages/analysis/hydrogenbonds.html)
258- [Groups and selections](https://docs.mdanalysis.org/2.10.0/documentation_pages/core/groups.html)
259- [Distances and periodic boxes](https://docs.mdanalysis.org/2.10.0/documentation_pages/lib/distances.html)
260- [Units](https://docs.mdanalysis.org/2.10.0/documentation_pages/units.html)