mcpField
A
alterlab-molecular-dynamics

alterlab-molecular-dynamics

github.com/alterlab-ieu其他7868 次安装5 周前更新
代码仓库

关于 alterlab-molecular-dynamics

面向分子模拟研究者的分子动力学技能,结合 OpenMM 进行模拟、MDAnalysis 分析轨迹,并用 PDBFixer 处理结构。适用于蛋白质稳定性、构象变化和蛋白质—配体相互作用研究。

主要功能

  1. 01使用 OpenMM 运行分子动力学模拟
  2. 02通过能量最小化与平衡准备体系
  3. 03用 MDAnalysis 分析轨迹
  4. 04计算 RMSD 和 RMSF
  5. 05追踪蛋白质—配体接触

适用场景

  1. 01分析蛋白质突变对稳定性的影响
  2. 02研究配体结合模式及驻留时间
  3. 03量化轨迹中的结构波动
  4. 04探索蛋白质构象变化

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/alterlab-ieu/alterlab-academic-skills --skill alterlab-molecular-dynamics

安装后在 Claude Code 中输入/alterlab-molecular-dynamics确认已加载。

附带文件evals/evals.jsonreferences/mdanalysis_analysis.md

详细介绍

分子动力学

概述

分子动力学(MD)模拟通过积分牛顿运动方程,以计算方式模拟分子系统随时间的演化。本技能涵盖两种互补工具:

安装(截至 2026-09 已验证:OpenMM 8.6.1、MDAnalysis 2.10、pdbfixer 1.12):

# uv (preferred) — all three publish binary wheels on PyPI, no conda needed
uv add openmm "mdanalysis>=2.10" pdbfixer
# If a pdbfixer wheel is unavailable for your platform, install from source:
uv pip install "pdbfixer @ git+https://github.com/openmm/pdbfixer.git"

# conda-forge alternative (pulls CUDA builds on Linux):
# conda install -c conda-forge openmm mdanalysis pdbfixer nglview

注意:Apple Silicon 不支持 CUDA;OpenMM 会回退到 CPU(在 macOS 上还会使用 OpenCL/Metal)平台——请参阅下方的平台选择代码块。

何时使用此技能

以下情况可使用分子动力学:

  • 蛋白质稳定性分析:突变会如何影响蛋白质动力学?
  • 药物结合模拟:表征配体的结合模式和驻留时间
  • 构象采样:探索蛋白质的柔性和构象变化
  • 蛋白质-蛋白质相互作用:对界面动力学和结合能进行建模
  • RMSD/RMSF 分析:量化相对于参考结构的结构波动
  • 自由能估算:计算结合自由能或构象自由能
  • 膜模拟:对脂质双层中的蛋白质进行建模
  • 内在无序蛋白质:研究 IDR 构象集合
不适用的情况
场景改用
预测初始蛋白质-配体结合姿势(无动力学)alterlab-diffdock
量子化学、pKa 或云端 AutoDock Vina 对接评分alterlab-rowan
预测待模拟的蛋白质结构(无实验 PDB)alterlab-alphafold 或 alterlab-boltz
仅为小分子参数化力场(OpenFF/GAFF),不进行模拟使用 alterlab-rdkit 进行结构准备,然后使用 OpenFF(见下文)

核心工作流:OpenMM 模拟

1. 系统准备
from openmm.app import *
from openmm import *
from openmm.unit import *
import sys

def prepare_system_from_pdb(pdb_file, forcefield_name="amber14-all.xml",
                              water_model="amber14/tip3pfb.xml"):
    """
    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:
        pdb, forcefield, system, topology
    """
    # 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)

    # Add solvent box (10 Å padding, 150 mM NaCl)
    modeller.addSolvent(
        forcefield,
        model='tip3p',
        padding=10*angstroms,
        ionicStrength=0.15*molar
    )

    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 hydrogen bonds (allows 2 fs timestep)
        rigidWater=True,
        ewaldErrorTolerance=0.0005
    )

    return modeller, system
2. 能量最小化
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):
    """
    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
    """
    # This integrator is reused by the NVT/NPT stages below, so use 2 fs: with only
    # HBonds constraints (no hydrogen mass repartitioning) a 4 fs step is unstable.
    integrator = LangevinMiddleIntegrator(300*kelvin, 1/picosecond, 0.002*picoseconds)

    # Create simulation
    # Use GPU if available (CUDA or OpenCL), fall back to CPU
    try:
        platform = Platform.getPlatformByName('CUDA')
        properties = {'DeviceIndex': '0', 'Precision': 'mixed'}
    except Exception:
        try:
            platform = Platform.getPlatformByName('OpenCL')
            properties = {}
        except Exception:
            platform = Platform.getPlatformByName('CPU')
            properties = {}

    simulation = Simulation(
        modeller.topology, system, integrator,
        platform, properties
    )
    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 simulation
3. NVT 平衡
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
    """
    # Add position restraints for backbone during NVT
    # (Optional: restraint heavy atoms)

    # Set temperature
    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)
    )

    print(f"Running NVT equilibration: {n_steps} steps ({n_steps*2/1000:.1f} ps)")
    simulation.step(n_steps)
    print("NVT equilibration complete")

    return simulation
4. NPT 平衡与生产模拟
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
    """
    # Add Monte Carlo barostat for pressure control
    system = simulation.context.getSystem()
    system.addForce(MonteCarloBarostat(pressure*bar, temperature*kelvin, 25))
    simulation.context.reinitialize(preserveState=True)

    # 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)
    )

    print(f"Running NPT production: {n_steps} steps ({n_steps*2/1000000:.2f} ns)")
    simulation.step(n_steps)
    print("Production MD complete")
    return simulation

使用 MDAnalysis 分析轨迹

1. 加载轨迹
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")
    print(f"Time range: 0 to {u.trajectory.totaltime:.0f} ps")
    return u
2. RMSD 分析
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 of (time, rmsd) values
    """
    # Pre-align the trajectory (rewrites coordinates in memory) so that any
    # downstream per-atom analysis sees superimposed frames.
    aligner = align.AlignTraj(u, u, select=selection, in_memory=True)
    aligner.run()

    # rms.RMSD does its own optimal superposition against ref_frame, so the
    # RMSD numbers below do not depend on the AlignTraj step above.
    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 fig
3. RMSF 分析(逐残基柔性)
def compute_rmsf(u, selection="backbone", start_frame=0):
    """
    Compute per-residue RMSF (flexibility).

    Returns:
        resids, rmsf_values arrays
    """
    atoms = u.select_atoms(selection)

    # RMSF does no superposition of its own — align the trajectory to the average
    # structure first, otherwise the fluctuations include rigid-body motion.
    average = align.AverageStructure(u, u, select=selection, ref_frame=0).run()
    align.AlignTraj(u, average.results.universe, select=selection, in_memory=True).run()

    R = rms.RMSF(atoms).run(start=start_frame)

    # R.results.rmsf is indexed by the atom's position WITHIN `atoms` (0..n-1),
    # not by global atom index — use the group's per-atom residue indices to average.
    resindices = atoms.resindices
    unique_res = np.unique(resindices)
    rmsf_per_res = np.array([R.results.rmsf[resindices == ri].mean() for ri in unique_res])
    return u.residues[unique_res].resids, rmsf_per_res
4. 蛋白质-配体接触
from MDAnalysis.lib import distances as mda_distances

def analyze_contacts(u, protein_sel="protein", ligand_sel="resname LIG",
                      radius=4.5, start_frame=0):
    """
    Track which protein residues are in contact with the ligand per frame.

    Args:
        radius: Contact distance cutoff in Angstroms

    Returns:
        list of sets, one per analyzed frame, of contacting protein resids
    """
    protein = u.select_atoms(protein_sel)
    ligand = u.select_atoms(ligand_sel)

    contact_frames = []
    for ts in u.trajectory[start_frame:]:
        # Pairwise protein-ligand distance matrix (PBC-aware via box=u.dimensions),
        # then threshold into a boolean contact matrix.
        # NOTE: contacts.contact_matrix(d, radius) takes a *distance matrix* d,
        # not raw coordinates — passing positions directly is a common bug.
        dmat = mda_distances.distance_array(
            protein.positions, ligand.positions, box=u.dimensions
        )
        contact_mask = contacts.contact_matrix(dmat, radius)  # shape (n_prot, n_lig)

        # A protein atom is in contact if it is close to any ligand atom.
        contacting_atoms = protein.atoms[contact_mask.any(axis=1)]
        contact_frames.append(set(contacting_atoms.resids))

    return contact_frames

力场选择指南

系统推荐力场水模型
标准蛋白质AMBER14(amber14-all.xml)TIP3P-FB
蛋白质 + 小分子AMBER14 + GAFF2TIP3P-FB
膜蛋白CHARMM36mTIP3P
核酸AMBER99-bsc1 或 AMBER14TIP3P
无序蛋白质ff19SB 或 CHARMM36mTIP3P

系统准备工具

PDBFixer(用于原始 PDB 文件)
from pdbfixer import PDBFixer
from openmm.app import PDBFile

def fix_pdb(input_pdb, output_pdb, ph=7.0):
    """Fix common PDB issues: missing residues, atoms, add H, standardize."""
    fixer = PDBFixer(filename=input_pdb)
    fixer.findMissingResidues()
    fixer.findNonstandardResidues()
    fixer.replaceNonstandardResidues()
    fixer.removeHeterogens(True)    # Remove water/ligands
    fixer.findMissingAtoms()
    fixer.addMissingAtoms()
    fixer.addMissingHydrogens(ph)

    with open(output_pdb, 'w') as f:
        PDBFile.writeFile(fixer.topology, fixer.positions, f)

    return output_pdb
小分子参数化(通过 OpenFF Toolkit)
# conda/mamba install -c conda-forge openff-toolkit openff-interchange
# (the .offxml files live in openff-forcefields, which is conda-only — not on PyPI,
#  so a pip/uv-only install of openff-toolkit cannot load the Sage force field.)
# openff-2.2.1.offxml is OpenFF "Sage" — a SMIRNOFF force field, NOT GAFF2 (the newest
# Sage line is openff-2.3.0.offxml). For actual GAFF2, use AmberTools antechamber/ACPYPE.
from openff.toolkit import Molecule, ForceField as OFFForceField

def parameterize_ligand(smiles, ff_name="openff-2.2.1.offxml"):
    """Generate OpenFF (Sage) parameters for a small molecule as an Interchange."""
    mol = Molecule.from_smiles(smiles)
    mol.generate_conformers(n_conformers=1)
    mol.assign_partial_charges("am1bcc")  # AM1-BCC charges, OpenFF default

    off_ff = OFFForceField(ff_name)
    interchange = off_ff.create_interchange(mol.to_topology())
    return interchange  # -> interchange.to_openmm() for an OpenMM System

最佳实践

  • MD 前务必进行能量最小化:原始 PDB 结构存在空间位阻冲突
  • 生产模拟前先进行平衡:NVT(50–100 ps)→ NPT(100–500 ps)→ 生产模拟
  • 使用 GPU:GPU 上的模拟速度可提升 10–100 倍(CUDA/OpenCL)
  • 使用 2 fs 时间步长并约束 HBonds:这是标准做法;使用 HMR(氢质量重分配)时可采用 4 fs
  • 仅分析已平衡的轨迹:舍弃最初 20–50% 作为平衡阶段
  • 保存检查点:MD 运行可能失败;检查点可用于重启
  • 周期性边界条件:溶剂化系统必须使用
  • 使用 PME 处理静电作用:对于带电系统,比截断方法更准确

其他资源

评论

支持 Markdown