化学工具生态里格式混乱是常态:一个工具只吃 MOL2,另一个输出 PDBQT,第三个要 XYZ。Open Babel(见 216《OpenBabel》)是处理这类问题的常备工具,支持一百多种格式互转。
安装
mamba install -c conda-forge openbabel
obabel -L formats | head -30 # 查看支持的格式
obabel -V
最常用的命令
# 基本转换
obabel input.sdf -O output.mol2
obabel input.pdb -O output.sdf
# SMILES → 3D 结构
obabel -:"CC(=O)Oc1ccccc1C(=O)O" -O aspirin.sdf --gen3d
obabel -:"CC(=O)O" -O out.sdf --gen3d --best # 更高质量但慢
# 按 pH 加氢(对接前常用)
obabel input.sdf -O output.sdf -p 7.4
# 加氢 / 去氢
obabel input.sdf -O with_h.sdf -h
obabel input.sdf -O no_h.sdf -d
# 去盐(只保留最大片段)
obabel input.smi -O clean.smi -r
# 按 InChI 去重
obabel library.sdf -O unique.sdf --unique
# 拆分多分子文件
obabel library.sdf -O mol_.sdf -m
# 能量最小化
obabel input.sdf -O min.sdf --minimize --ff MMFF94 --steps 500
# 构象搜索
obabel input.sdf -O confs.sdf --conformer --nconf 20 --score rmsd
# 转换时顺手过滤
obabel library.sdf -O filtered.sdf --filter "MW<500 && logP<5"
# 加上计算的属性
obabel input.sdf -O out.sdf --append "MW logP TPSA"
批量处理脚本
#!/bin/bash
# 批量把 SDF 转成对接用的 PDBQT,带失败统计
mkdir -p pdbqt logs
success=0; failed=0
for f in sdf/*.sdf; do
name=$(basename "$f" .sdf)
if obabel "$f" -O "pdbqt/${name}.pdbqt" -p 7.4 \
2> "logs/${name}.log"; then
success=$((success+1))
else
failed=$((failed+1))
echo "FAILED: $name" >> logs/failures.txt
fi
done
echo "成功 $success,失败 $failed"
# 大批量时用 GNU parallel 加速
# ls sdf/*.sdf | parallel -j 16 \
# 'obabel {} -O pdbqt/{/.}.pdbqt -p 7.4 2>/dev/null'
Python 接口
from openbabel import pybel
# 读取并处理
for mol in pybel.readfile("sdf", "library.sdf"):
mol.addh()
mol.make3D(forcefield="mmff94", steps=100)
mol.localopt(forcefield="mmff94", steps=500)
desc = mol.calcdesc(["logP", "TPSA", "MW"])
print(mol.title, {k: round(v, 2) for k, v in desc.items()})
mol.write("mol2", f"out/{mol.title}.mol2", overwrite=True)
# 格式转换的简写
mol = pybel.readstring("smi", "CC(=O)Oc1ccccc1C(=O)O")
mol.make3D()
print(mol.write("pdb"))
什么时候该改用 RDKit
这是使用 Open Babel 时最该有的判断。两者功能重叠,但可靠性不同:
| 任务 | 推荐 | 原因 |
|---|---|---|
| 格式转换(尤其冷门格式) | Open Babel | 格式覆盖面无可替代 |
| 分子标准化、去盐 | RDKit | MolStandardize 行为更明确可控 |
| 芳香性 / 键级感知 | RDKit | Open Babel 从 PDB 推断易出错 |
| 构象生成 | RDKit | ETKDGv3 质量更好 |
| 指纹与描述符 | RDKit | 实现更主流,与文献可比 |
| 子结构匹配 | RDKit | SMARTS 支持更完整 |
| 批量 pH 处理 | 两者皆可 | 要求高时用 Dimorphite-DL |
务实结论:把 Open Babel 当格式转换器用,把化学逻辑交给 RDKit。
转换会丢失什么
# 不同格式携带的信息不同,转换是有损的
# SMILES → 无 3D 坐标
# XYZ → 无键级、无电荷
# PDB → 无键级(靠距离推断)
# PDBQT → 无键级,有 AutoDock 原子类型与部分电荷
# MOL2 → 有键级与电荷
# SDF → 有键级、可带属性字段
# 建议:链路上始终保留一份权威的 SDF 或 SMILES 作为真相来源,
# 需要什么格式就从它转,而不是链式转换
# 反例(错误做法):
# SDF → PDBQT → PDB → MOL2 ← 每次转换都可能丢信息,累积出错
# 正例:
# SDF → PDBQT
# SDF → MOL2
# SDF → PDB
验证转换结果
from rdkit import Chem
def verify_conversion(original_smiles, converted_file, fmt="sdf"):
"""用 InChIKey 比对,确认转换没改变分子"""
orig = Chem.MolFromSmiles(original_smiles)
if fmt == "sdf":
conv = Chem.SDMolSupplier(converted_file, removeHs=True)[0]
elif fmt == "mol2":
conv = Chem.MolFromMol2File(converted_file, removeHs=True)
if conv is None:
return False, "cannot_parse_converted"
k1 = Chem.MolToInchiKey(orig)
k2 = Chem.MolToInchiKey(conv)
# 比较骨架层(前 14 位),忽略质子化差异
return k1[:14] == k2[:14], f"{k1[:14]} vs {k2[:14]}"
ok, msg = verify_conversion("CC(=O)Oc1ccccc1C(=O)O", "aspirin.sdf")
print("转换正确" if ok else f"⚠ 转换后分子改变: {msg}")
常见坑与提示
- 把 Open Babel 当格式转换器,化学逻辑交给 RDKit 更可靠;
- 避免链式转换,始终从一份权威的 SDF/SMILES 派生各种格式;
- 从 PDB 推断键级容易出错,重要分子要用 InChIKey 验证;
- 管线中锁定版本——不同版本的 pH 模型与芳香性感知有差异。