Molecular Dynamics
K-DenseCommunityCreated by Kuan-lin HuangDistributed by K-Dense
- Skill
- Tool integration
- Science and research
- MIT
- Low risk
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured workflow for conducting molecular dynamics (MD) simulations and analyzing the resulting trajectories. It leverages OpenMM for high-performance simulation, including system preparation, energy minimization, and equilibration (NVT/NPT), and MDAnalysis for post-simulation structural analysis such as RMSD, RMSF, and contact mapping. The skill emphasizes scientific rigor, requiring users to validate system construction, force field selection, and simulation parameters before execution.
What it does
- Prepares protein and ligand systems for simulation including solvation and ionization.
- Performs energy minimization to resolve steric clashes.
- Executes NVT and NPT equilibration stages to stabilize the system.
- Runs production MD simulations with checkpointing.
- Calculates structural metrics like RMSD and RMSF to assess stability and flexibility.
- Analyzes protein-ligand interactions through contact mapping.
When to use it
- When investigating the stability of protein structures or the effect of mutations.
- When characterizing the binding mode or residence time of small molecule ligands.
- When exploring conformational sampling and protein flexibility.
- When calculating binding free energies or analyzing protein-protein interfaces.
Advantages
- Integrates high-performance GPU-accelerated simulation with flexible analysis tools.
- Provides a modular, reproducible workflow for complex biophysical modeling.
- Supports standard force fields and common trajectory formats.
- Includes built-in validation steps to ensure simulation quality.
Usage examples
- “Prepare an OpenMM system from a cleaned PDB file using the AMBER14 force field.”
- The agent will initialize the system, add hydrogens, solvate with TIP3P water, and return the topology and system objects ready for minimization.
- “Calculate the RMSD of the protein backbone over a simulation trajectory.”
- The agent will load the trajectory, perform an alignment to the reference structure, compute the RMSD for the backbone atoms, and generate a plot.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/k-dense-ai/molecular-dynamics/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip molecular-dynamics.zip -d .agents/skills
Documentation by Kuan-lin Huang
Molecular Dynamics
Overview
Molecular dynamics (MD) simulation computationally models the time evolution of molecular systems by integrating Newton's equations of motion. This skill covers two complementary tools:
OpenMM (https://openmm.org/): High-performance MD simulation engine with GPU support, Python API, and flexible force field support
MDAnalysis (https://mdanalysis.org/): Python library for reading, writing, and analyzing MD trajectories from all major simulation packages
Reviewed versions: OpenMM 8.6.1 and MDAnalysis 2.10.0. Reference-platform smoke checks use small synthetic systems; GPU performance and scientific convergence are not established by them. OpenMM latest API pages identify a development build, so the installed 8.6.1 API is the executable reference.
Installation into a dedicated environment:
uv venv --python 3.12 .venv-md
uv pip install --python .venv-md/bin/python "openmm==8.6.1" "MDAnalysis==2.10.0" matplotlib pandas
# Windows: use .venv-md/Scripts/python.exe insteadWhen to Use This Skill
Use molecular dynamics when:
Protein stability analysis: How does a mutation affect protein dynamics?
Drug binding simulations: Characterize binding mode and residence time of a ligand
Conformational sampling: Explore protein flexibility and conformational changes
Protein-protein interaction: Model interface dynamics and binding energetics
RMSD/RMSF analysis: Quantify structural fluctuations from a reference structure
Free energy estimation: Compute binding free energy or conformational free energy
Membrane simulations: Model proteins in lipid bilayers
Intrinsically disordered proteins: Study IDR conformational ensembles
Core Workflow: OpenMM Simulation
Examples require system-specific preparation and validation. Review biological assembly, alternate locations, missing loops, termini, protonation, disulfides, ligands and cofactors before parameterization. An MD trajectory alone does not establish binding free energy or residence time.
The functions below form one Python module: execute their import blocks together. Use a new output prefix for every stage to avoid overwriting earlier results.
1. System Preparation
from openmm.app import *
from openmm import *
from openmm.unit import *
def prepare_system_from_pdb(pdb_file, forcefield_name="amber14-all.xml",
water_model="amber14/tip3pfb.xml", ph=7.0):
"""
Prepare an OpenMM system from a PDB file.
Args:
pdb_file: Path to cleaned PDB file (use PDBFixer for raw PDB files)
forcefield_name: Force field XML file
water_model: Water model XML file
Returns:
modeller, system
"""
# Load PDB
pdb = PDBFile(pdb_file)
# Load force field
forcefield = ForceField(forcefield_name, water_model)
# Add hydrogens and solvate
modeller = Modeller(pdb.topology, pdb.positions)
modeller.addHydrogens(forcefield, pH=ph)
# TIP3P geometry also serves TIP3P-FB; XML supplies the parameters.
# 10 Å padding; added salt excludes neutralizing counterions.
modeller.addSolvent(
forcefield,
model='tip3p',
padding=10*angstroms,
ionicStrength=0.15*molar, neutralize=True
)
print(f"System: {modeller.topology.getNumAtoms()} atoms, "
f"{modeller.topology.getNumResidues()} residues")
# Create system
system = forcefield.createSystem(
modeller.topology,
nonbondedMethod=PME, # Particle Mesh Ewald for long-range electrostatics
nonbondedCutoff=1.0*nanometer,
constraints=HBonds, # Constrain bonds involving H (not intermolecular H-bonds)
rigidWater=True,
ewaldErrorTolerance=0.0005
)
return modeller, system2. Energy Minimization
from openmm.app import *
from openmm import *
from openmm.unit import *
def minimize_energy(modeller, system, output_pdb="minimized.pdb",
max_iterations=1000, tolerance=10.0, platform_name=None):
"""
Energy minimize the system to remove steric clashes.
Args:
modeller: Modeller object with topology and positions
system: OpenMM System
output_pdb: Path to save minimized structure
max_iterations: Maximum minimization steps
tolerance: Convergence criterion in kJ/mol/nm
Returns:
simulation object with minimized positions
"""
# Keep the integrator at 2 fs for the subsequent NVT/NPT examples.
integrator = LangevinMiddleIntegrator(300*kelvin, 1/picosecond, 0.002*picoseconds)
# Automatic platform selection, or an explicit tested platform (e.g. CPU).
# A registered GPU plugin does not prove a working device or driver.
platform = Platform.getPlatformByName(platform_name) if platform_name else None
simulation = Simulation(modeller.topology, system, integrator, platform)
simulation.context.setPositions(modeller.positions)
# Check initial energy
state = simulation.context.getState(getEnergy=True)
print(f"Initial energy: {state.getPotentialEnergy()}")
# Minimize
simulation.minimizeEnergy(
tolerance=tolerance*kilojoules_per_mole/nanometer,
maxIterations=max_iterations
)
state = simulation.context.getState(getEnergy=True, getPositions=True)
print(f"Minimized energy: {state.getPotentialEnergy()}")
# Save minimized structure
with open(output_pdb, 'w') as f:
PDBFile.writeFile(simulation.topology, state.getPositions(), f)
return simulation3. NVT Equilibration
from openmm.app import *
from openmm import *
from openmm.unit import *
def run_nvt_equilibration(simulation, n_steps=50000, temperature=300,
report_interval=1000, output_prefix="nvt"):
"""
NVT equilibration: constant N, V, T.
Equilibrate velocities to target temperature.
Args:
simulation: OpenMM Simulation (after minimization)
n_steps: Number of MD steps (50000 × 2fs = 100 ps)
temperature: Temperature in Kelvin
report_interval: Steps between data reports
output_prefix: File prefix for trajectory and log
"""
# This example is unrestrained; add validated restraints before Context creation.
if any("Barostat" in type(f).__name__ and f.getFrequency() > 0
for f in simulation.system.getForces()):
raise ValueError("NVT requires all barostats disabled or absent")
# Set both the thermostat target and initial velocities.
simulation.integrator.setTemperature(temperature*kelvin)
simulation.context.setVelocitiesToTemperature(temperature*kelvin)
# Add reporters
simulation.reporters = []
# Log file
simulation.reporters.append(
StateDataReporter(
f"{output_prefix}_log.txt",
report_interval,
step=True,
potentialEnergy=True,
kineticEnergy=True,
temperature=True,
volume=True,
speed=True
)
)
# DCD trajectory (compact binary format)
simulation.reporters.append(
DCDReporter(f"{output_prefix}_traj.dcd", report_interval)
)
duration = (n_steps * simulation.integrator.getStepSize()).value_in_unit(picoseconds)
print(f"Running NVT equilibration: {n_steps} steps ({duration:.1f} ps)")
simulation.step(n_steps)
print("NVT equilibration complete")
return simulation4. NPT Equilibration and Production
Call this function first with a dedicated NPT equilibration prefix. Inspect density, energy, structure and replicate stability before a separate production call; the default duration is an example, not an equilibration or convergence criterion.
def run_npt_production(simulation, n_steps=500000, temperature=300, pressure=1.0,
report_interval=5000, output_prefix="npt"):
"""
NPT production run: constant N, P, T.
Args:
n_steps: Production steps (500000 × 2fs = 1 ns)
temperature: Temperature in Kelvin
pressure: Pressure in bar
report_interval: Steps between reports
"""
# Keep the Langevin thermostat and barostat at the same temperature.
simulation.integrator.setTemperature(temperature*kelvin)
system = simulation.system
barostats = [f for f in system.getForces() if "Barostat" in type(f).__name__]
if not system.usesPeriodicBoundaryConditions():
raise ValueError("NPT requires a periodic system")
if not barostats:
system.addForce(MonteCarloBarostat(pressure*bar, temperature*kelvin, 25))
simulation.context.reinitialize(preserveState=True)
elif len(barostats) != 1 or not isinstance(barostats[0], MonteCarloBarostat):
raise ValueError("This example supports one isotropic MonteCarloBarostat")
else:
barostats[0].setDefaultPressure(pressure*bar)
barostats[0].setDefaultTemperature(temperature*kelvin)
barostats[0].setFrequency(25)
# Existing Context parameters must also be updated on repeated calls.
simulation.context.setParameter(MonteCarloBarostat.Pressure(), pressure)
simulation.context.setParameter(MonteCarloBarostat.Temperature(), temperature)
# Update reporters
simulation.reporters = []
simulation.reporters.append(
StateDataReporter(
f"{output_prefix}_log.txt",
report_interval,
step=True,
potentialEnergy=True,
temperature=True,
density=True,
speed=True
)
)
simulation.reporters.append(
DCDReporter(f"{output_prefix}_traj.dcd", report_interval)
)
# Save checkpoints
simulation.reporters.append(
CheckpointReporter(f"{output_prefix}_checkpoint.chk", 50000)
)
duration = (n_steps * simulation.integrator.getStepSize()).value_in_unit(nanoseconds)
print(f"Running NPT stage: {n_steps} steps ({duration:.3f} ns)")
simulation.step(n_steps)
simulation.saveCheckpoint(f"{output_prefix}_checkpoint.chk")
simulation.saveState(f"{output_prefix}_state.xml")
print("NPT stage complete")
return simulationTrajectory Analysis with MDAnalysis
1. Load Trajectory
Use the solvated topology with exactly the DCD atom count and order (e.g. the
minimized.pdb above), not the original unsolvated input. MDAnalysis converts
lengths to Å and time to ps by default; OpenMM bare coordinates use nm. Preserve
frame box vectors and actual timestamps; do not infer time from frame number.
import MDAnalysis as mda
from MDAnalysis.analysis import rms, align, contacts
import numpy as np
import matplotlib.pyplot as plt
def load_trajectory(topology_file, trajectory_file):
"""
Load an MD trajectory with MDAnalysis.
Args:
topology_file: PDB, PSF, or other topology file
trajectory_file: DCD, XTC, TRR, or other trajectory
"""
u = mda.Universe(topology_file, trajectory_file)
print(f"Universe: {u.atoms.n_atoms} atoms, {u.trajectory.n_frames} frames")
first, last = u.trajectory[0].time, u.trajectory[-1].time
print(f"Time range: {first:g} to {last:g} ps")
u.trajectory[0]
return u2. RMSD Analysis
def compute_rmsd(u, selection="backbone", reference_frame=0):
"""
Compute RMSD of selected atoms relative to reference frame.
Args:
u: MDAnalysis Universe
selection: Atom selection string (MDAnalysis syntax)
reference_frame: Frame index for reference structure
Returns:
numpy array with columns [frame index, time in ps, RMSD in Angstroms]
"""
# RMSD performs its own fit; avoid rotating the stored trajectory here.
# Make the selected molecule whole before analysis of periodic trajectories.
R = rms.RMSD(u, select=selection, ref_frame=reference_frame)
R.run()
rmsd_data = R.results.rmsd # columns: frame, time, RMSD
return rmsd_data
def plot_rmsd(rmsd_data, title="RMSD over time", output_file="rmsd.png"):
"""Plot RMSD over simulation time."""
fig, ax = plt.subplots(figsize=(10, 4))
ax.plot(rmsd_data[:, 1] / 1000, rmsd_data[:, 2], 'b-', linewidth=0.5)
ax.set_xlabel("Time (ns)")
ax.set_ylabel("RMSD (Å)")
ax.set_title(title)
ax.axhline(rmsd_data[:, 2].mean(), color='r', linestyle='--',
label=f'Mean: {rmsd_data[:, 2].mean():.2f} Å')
ax.legend()
plt.tight_layout()
plt.savefig(output_file, dpi=150)
return fig3. RMSF Analysis (Per-Residue Flexibility)
Make molecules whole and align a separate structural-analysis copy before RMSF; RMSF does not align. Do not run periodic contacts on a rotated trajectory whose box was not rotated. See analysis reference for alignment, PCA, DSSP, hydrogen bonds and population-derived free energy.
def compute_rmsf(u, selection="protein and name CA", start_frame=0):
"""
Compute per-residue RMSF (flexibility).
Returns:
residue_keys, rmsf_values; keys are (resindex, segid, resid, resname)
"""
# Select atoms
atoms = u.select_atoms(selection)
if not len(atoms):
raise ValueError("RMSF selection is empty")
# A multi-atom selection returns mean atomic RMSF, not COM RMSF.
R = rms.RMSF(atoms)
R.run(start=start_frame)
# Average by residue
resids = []
rmsf_per_res = []
for res in u.select_atoms(selection).residues:
res_atoms = res.atoms.intersection(atoms)
if len(res_atoms) > 0:
resids.append((res.ix, res.segid, res.resid, res.resname))
# RMSF is indexed within the selected AtomGroup, not the Universe.
rmsf_per_res.append(R.results.rmsf[atoms.resindices == res.ix].mean())
return resids, np.array(rmsf_per_res)4. Protein-Ligand Contacts
def analyze_contacts(u, protein_sel="protein", ligand_sel="resname LIG",
radius=4.5, start_frame=0, periodic=True):
"""
Track protein-ligand contacts over trajectory.
Args:
radius: Atom-pair cutoff in Angstroms; any pair defines a residue contact.
periodic: Require box data and use minimum-image distances.
Returns sets of (resindex, segid, resid, resname), one per analyzed frame.
"""
from MDAnalysis.lib.distances import distance_array
protein = u.select_atoms(protein_sel)
ligand = u.select_atoms(ligand_sel)
if not len(protein) or not len(ligand):
raise ValueError("Protein and ligand selections must both be nonempty")
if not np.isfinite(radius) or radius <= 0:
raise ValueError("radius must be finite and positive")
contact_frames = []
for ts in u.trajectory[start_frame:]:
if periodic and (ts.dimensions is None or not np.all(np.isfinite(ts.dimensions))
or np.any(ts.dimensions[:3] <= 0)):
raise ValueError("Periodic contacts require valid frame box dimensions")
# This includes hydrogen atoms unless the caller selects heavy atoms.
distances = contacts.contact_matrix(
distance_array(protein.positions, ligand.positions, box=ts.dimensions if periodic else None),
radius,
)
contact_residues = set()
for i in range(distances.shape[0]):
if distances[i].any():
res = protein[i].residue
contact_residues.add((res.ix, res.segid, res.resid, res.resname))
contact_frames.append(contact_residues)
return contact_framesForce Fields and Preparation
Choose a validated protein/ligand/water/ion combination for the scientific system.
The working example retains AMBER14/TIP3P-FB; it is not a universal recommendation.
Current OpenMM also bundles amber19-all.xml (ff19SB, DNA OL21, RNA OL3, lipid21),
and charmm36_2024.xml with its own charmm36_2024/water.xml. Generic TIP3P and
CHARMM-modified TIP3P are not interchangeable. IDP ensembles are especially
sensitive to protein-water balance. Four-site waters need extra particles and a
matching Modeller geometry, unlike the three-site example above.
AMBER protein XML does not parameterize arbitrary ligands. Use a reviewed GAFF2 or OpenFF route with explicit stereochemistry, protonation, bond orders, atom mapping and charge method. A ligand-only Interchange is not a protein-ligand system. See preparation reference for conservative PDBFixer and OpenFF examples, installation requirements and current release details.
Validation and Provenance
Start with minimization; finite energy does not establish a valid structure.
Use staged NVT → NPT equilibration → production. This example has no restraints.
A 2 fs step with constrained H-containing bonds is a starting choice. Larger steps/HMR require integrator, stability and observable validation for that system.
Use per-frame boxes for periodic distances. Make molecules whole for structural observables using trusted bond topology; unwrapping cannot infer missing bonds.
Assess burn-in, autocorrelation, effective samples, replicas and uncertainties. Short stable temperature/density traces do not establish conformational sampling.
Save topology/atom order, System/Integrator XML, force-field files/versions, seeds, platform/precision, parameters and analysis selections. Checkpoints are platform/hardware/version specific; XML states are more portable but do not retain all internal random-generator state. Test restarting the actual setup.
Additional Resources
OpenMM documentation: https://openmm.org/documentation.html
MDAnalysis user guide: https://docs.mdanalysis.org/
GROMACS (alternative MD engine): https://manual.gromacs.org/
NAMD (alternative): https://www.ks.uiuc.edu/Research/namd/
CHARMM-GUI (web-based system builder): https://charmm-gui.org/
AmberTools (free Amber tools): https://ambermd.org/AmberTools.php
OpenMM paper: Eastman P et al. (2017) PLOS Computational Biology. PMID: 28278240
MDAnalysis paper: Michaud-Agrawal N et al. (2011) J Computational Chemistry. PMID: 21500218
Files
- ATTRIBUTION.mdAdded by AutoGineer942 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md18.5 KiB
- references/mdanalysis_analysis.md9.2 KiB
- references/system_preparation.md4.8 KiB
references/mdanalysis_analysis.md
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.
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 psAtom 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.
Common Analysis Modules
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 AngstromsRadius 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} Å")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.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'])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)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 figTrajectory Formats Supported
| Format | Extension | Notes |
|---|---|---|
| DCD | .dcd | CHARMM/NAMD binary; widely used |
| XTC | .xtc | GROMACS compressed |
| TRR | .trr | GROMACS full precision |
| NetCDF | .nc | AMBER format |
| LAMMPS | .lammpstrj | LAMMPS dump |
| HDF5 | .h5md | H5MD standard |
| PDB | .pdb | Multi-model PDB |
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")Performance Tips
Use
in_memory=Truefor 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 multiprocessingChoose 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)Official API references
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs Python
The author says: Requires Python 3.11+ with OpenMM and MDAnalysis; matplotlib for plots. Optional PDBFixer and OpenFF need separate installation. Network access for installation; local simulation and analysis run offline.
Security
What our scanner noticed· Low risk
- Instructions that use the network
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Kuan-lin Huang
- Publisher
- K-Dense (Community)
- Distributed by
- K-Dense
- Source
- K-Dense-AI/scientific-agent-skills · skills/molecular-dynamics · commit a25cf9e
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE.md)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Kuan-lin Huang or K-Dense.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download molecular-dynamics.zip