直接拿原始 PDB 去做对接或模拟,是最常见也最容易被忽视的错误来源。晶体结构几乎总是不完整的:缺失侧链原子、缺失整段 loop、没有氢原子、含有结晶助剂。这一步不做好,后面所有计算都建立在错误的结构上。
安装
mamba install -c conda-forge pdbfixer openmm
pdbfixer --help # 也有命令行与图形界面
标准修复流程(带诊断输出)
from pdbfixer import PDBFixer
from openmm.app import PDBFile
def fix_structure(pdb_input, pdb_output, ph=7.4,
keep_water=False, keep_hetero=None, verbose=True):
fixer = PDBFixer(filename=pdb_input)
# 1) 缺失残基(整段 loop)
fixer.findMissingResidues()
if verbose and fixer.missingResidues:
print("缺失残基片段:")
for (chain_idx, res_idx), residues in fixer.missingResidues.items():
print(f" 链 {chain_idx} 位置 {res_idx}: {len(residues)} 个残基")
# 关键判断:末端缺失通常可不补,位于结合位点附近的必须补
# 若要跳过末端缺失:
chains = list(fixer.topology.chains())
keys_to_remove = []
for key in fixer.missingResidues:
chain = chains[key[0]]
if key[1] == 0 or key[1] == len(list(chain.residues())):
keys_to_remove.append(key) # 末端
for k in keys_to_remove:
del fixer.missingResidues[k]
# 2) 非标准残基(如硒代甲硫氨酸 MSE → MET)
fixer.findNonstandardResidues()
if verbose and fixer.nonstandardResidues:
print("非标准残基:", fixer.nonstandardResidues)
fixer.replaceNonstandardResidues()
# 3) 杂原子处理 —— 这一步需要人工判断!
fixer.removeHeterogens(keepWater=keep_water)
# 4) 补缺失重原子(侧链)
fixer.findMissingAtoms()
if verbose and fixer.missingAtoms:
print(f"缺失重原子的残基数: {len(fixer.missingAtoms)}")
fixer.addMissingAtoms()
# 5) 按 pH 加氢
fixer.addMissingHydrogens(ph)
PDBFile.writeFile(fixer.topology, fixer.positions,
open(pdb_output, "w"), keepIds=True)
return fixer
fixer = fix_structure("1m17.pdb", "receptor_fixed.pdb", ph=7.4)
每一步的判断依据
| 步骤 | 判断 |
|---|---|
| 缺失残基 | 末端缺失可不补;结合位点附近的缺失 loop 必须处理,否则口袋形状是错的 |
| 杂原子 | 结晶助剂(甘油、DMSO、PEG、硫酸根)该删;催化金属、辅因子必须留 |
| 水分子 | 结合位点的保守水常参与桥接,是否保留需专门决定 |
| 加氢 pH | 默认 7.4;组氨酸质子化态影响最大,关键位点建议人工核对 |
| 非标准残基 | MSE→MET 通常安全;翻译后修饰要评估是否与功能相关 |
保留必要的辅因子与金属
# removeHeterogens 会无差别删除所有杂原子,包括必需的辅因子。
# 若需保留,应先手工分离,修复后再合并。
from Bio.PDB import PDBParser, PDBIO, Select
class KeepCofactors(Select):
KEEP = {"ZN", "MG", "MN", "FE", "CA", "HEM", "NAD", "FAD", "ATP", "SAM"}
def accept_residue(self, res):
return res.get_resname().strip() in self.KEEP
parser = PDBParser(QUIET=True)
st = parser.get_structure("x", "1m17.pdb")
io = PDBIO(); io.set_structure(st)
io.save("cofactors.pdb", KeepCofactors())
# 修复蛋白后,再把 cofactors.pdb 的内容合并回去
# (注意原子编号与链标识的一致性)
补 loop 的重要提醒
PDBFixer 补的缺失 loop 是用简单几何方法生成的,不是预测出来的真实构象。它能让拓扑完整、模拟不炸,但那段 loop 的具体构象不可信:
- 缺失 loop 远离结合位点 → 补上即可,不影响结论;
- 缺失 loop 就在结合位点旁边 → 不要直接信任补出来的构象。应改用同源建模、AlphaFold 预测(该区域通常 pLDDT 较低,本身就是提示),或做 loop 精修,并对补出的构象做充分采样。
修复后的检查清单
from openmm.app import PDBFile, ForceField, Modeller
from openmm import LangevinMiddleIntegrator, unit
import openmm.app as app
# 1) 结构完整性:能否被力场识别(能建体系说明拓扑正确)
pdb = PDBFile("receptor_fixed.pdb")
ff = ForceField("amber14-all.xml", "amber14/tip3pfb.xml")
try:
system = ff.createSystem(pdb.topology)
print("✓ 拓扑正确,力场可识别")
except Exception as e:
print("✗ 拓扑有问题:", e)
# 2) 能量最小化,检查是否有严重原子重叠
integrator = LangevinMiddleIntegrator(300*unit.kelvin, 1/unit.picosecond,
2*unit.femtoseconds)
sim = app.Simulation(pdb.topology, system, integrator)
sim.context.setPositions(pdb.positions)
e0 = sim.context.getState(getEnergy=True).getPotentialEnergy()
sim.minimizeEnergy(maxIterations=500)
e1 = sim.context.getState(getEnergy=True).getPotentialEnergy()
print(f"能量: {e0} → {e1}")
# 初始能量极高(如 1e6 kJ/mol 以上)说明有原子重叠
# 3) 目视检查(务必做)
# 用 PyMOL 或 NGLView 看一遍,重点看补出来的部分
- 确认该保留的金属、辅因子还在;
- 确认结合位点残基完整、质子化态合理;
- 确认没有链断裂或原子位置异常;
- 若用于 MD,先跑短时间约束模拟确认体系不炸。
常见坑与提示
removeHeterogens会删掉共晶配体与辅因子,需要的要先单独分离出来;- 补出的 loop 构象不可信,位于结合位点附近时必须另行建模;
- 组氨酸质子化态影响最大,关键位点人工核对;
- 修复后必须做能量最小化 + 目视检查,不能直接进入对接。
延伸资源
- 工具详解:215《PDBFixer》;下一步:341《用 Meeko 准备 Docking 文件》、336《用 AutoDock Vina 跑 Docking》;
- 结构质量判断:237《RCSB PDB》;模拟:200《OpenMM》。