分子表示基础:SMILES、InChI、分子图、3D坐标的表示方法与互转
做分子生成,第一个门槛就是“怎么描述一个分子”。
你想想看,计算机不认识结构式,它只认字符串和数字。所以我们需要把化学结构翻译成它听得懂的语言。SMILES、InChI、分子图、3D 坐标——这四种表示法,基本覆盖了所有场景。我个人习惯是:初期建模用 SMILES,数据库存 InChI,图神经网络用分子图,对接或构象分析用 3D 坐标。但互转的时候经常踩坑,今天我们就逐个拆开讲清楚。
SMILES——最常用的线性表示
SMILES 全称 Simplified Molecular Input Line Entry System。说白了就是用一行 ASCII 字符描述分子连接关系。比如水的 SMILES:O,乙醇是 CCO,苯是 c1ccccc1。
规不规范,差别很大。我在项目中遇到过一版训练数据,同一个分子有的用 CCO,有的用 OCC,模型根本学不对。后来我强制用 RDKit 的 MolToSmiles 做 canonical 化,才把问题解决了。
SMILES 的语法很简单:原子用元素符号,单键省略,双键写 =,三键写 #,分支用括号,环用数字标记。比如环己烷:C1CCCCC1。
但注意两点:
- SMILES 不包含立体化学信息(除非用 @ 符号表示手性)
- 同一个分子可以有无数种 SMILES 字符串,必须规范化
实用经验:用 rdkit.Chem.MolToSmiles(mol, canonical=True) 确保输出统一。数据清洗时一定要做这步。
InChI——国际化标识符
InChI(International Chemical Identifier)是 IUPAC 推的标准。跟 SMILES 比,它更严谨,层次化更强。怎么看 InChI 的格式?比如水的 InChI:
InChI=1S/H2O/h1H2
前面是版本和层数,后面是分子式和氢原子信息。InChI 还带一个哈希版本叫 InChIKey,固定 27 个字符,适合做数据库索引。
我个人习惯:对外发布数据时用 InChI,避免乱序歧义。但注意,InChI 转回结构时,RDKit 的速度比 SMILES 慢几倍,做大规模生成时尽量少用。
避坑:我曾经在爬取 PubChem 数据时直接用 InChI 做 Key,结果发现同分异构体(如对映体)InChI 几乎一样,只有最后一段有区别。如果你需要区分立体构型,请用 InChI 完整版本,不要只截取前几层。
分子图——GNN的“母语”
图神经网络(GNN)处理分子,必须把分子转成图结构。节点是原子,边是化学键。每个节点可以包含原子类型、电荷、手性等特征;边可以包含键级、环信息等。
RDKit 里用 Chem.rdmolops.GetAdjacencyMatrix 拿到邻接矩阵,然后自己拼特征。但更推荐用 Chem.MolFromSmiles 之后直接遍历原子和键:
from rdkit import Chem
mol = Chem.MolFromSmiles('CCO')
for atom in mol.GetAtoms():
print(atom.GetAtomicNum(), atom.GetSymbol())
for bond in mol.GetBonds():
print(bond.GetBeginAtomIdx(), bond.GetEndAtomIdx(), bond.GetBondType())
分子图的好处是能直接喂给 GNN,不需要额外编码。但我做项目时发现,如果直接用 SMILES 的原子顺序建图,图的拓扑映射对不上标准的 InChI 编号,导致验证集准确率偏低。后来我改用 Chem.CanonicalRankAtoms 排序,问题才解决。
3D 坐标——真实空间位置
SMILES 和分子图没有三维信息。做分子对接、构象搜索、力场优化必须用 3D 坐标。常见格式:XYZ 文件和 SDF 文件。
XYZ 文件长这样:
3
water
O 0.0000 0.0000 0.1170
H 0.7570 0.0000 -0.4690
H -0.7570 0.0000 -0.4690
SDF 更丰富,可以存性质、键类型、多分子。RDKit 从 SMILES 生成 3D 坐标的典型做法:
mol = Chem.MolFromSmiles('CCO')
mol = Chem.AddHs(mol) # 加氢
AllChem.EmbedMolecule(mol, randomSeed=42)
AllChem.MMFFOptimizeMolecule(mol) # 力场优化
print(mol.GetConformer().GetPositions())
这里有个坑:不加氢直接嵌入,坐标很离谱。我第一版 pipeline 就忘了这步,生成的分子大部分键长都异常。后来我加了一行 Chem.AddHs,效果立刻正常了。
互转:核心工具链与实战对比
实际工作中,这四种表示经常需要来回倒。核心工具还是 RDKit 和 Open Babel。下表总结常见互转场景和推荐方法:
| 转换方向 | 推荐方法 | 注意 |
|---|---|---|
| SMILES → InChI | Chem.MolToInchi(mol) |
确保 mol 是 Sanitize 后的 |
| InChI → SMILES | Chem.MolFromInchi(inchi) → Chem.MolToSmiles(mol) |
可能丢失立体信息 |
| SMILES → 分子图 | RDKit 原子/键遍历 | 建议用 canonical 原子顺序 |
| SMILES → 3D 坐标 | AllChem.EmbedMolecule + 力场优化 |
必须加氢 |
| 3D 坐标 → SMILES | 先用 MolFromXYZ 或 SDF 读入,再 MolToSmiles |
有损转换,注意环的感知 |
| 分子图 → SMILES | 从邻接矩阵重建 mol 对象 | 需要原子类型、键级等完整特征 |
另外,我常用 Open Babel 做批量转换,尤其处理 SDF 和 XYZ 互转。命令行一行搞定:
obabel input.sdf -O output.xyz
obabel input.smi -O output.inchi --geninchi
但 Open Babel 的 SMILES canonical 跟 RDKit 不总是严格一致。我的建议是:pipeline 中从头到尾只用同一个工具,避免不一致。
我的建议:如果你刚开始搭建分子生成 pipeline,先拿 SMILES 作为主干,所有输入都转成 RDKit 的 Mol 对象。需要什么表示,就从 Mol 对象生成。这样能避免多工具不一致的问题。我自己的项目一直这么干,后面加图解码、3D 构象都挺顺畅的。
代码整合示例:完整互转流程
下面这个函数展示从 SMILES 出发,生成 InChI、分子图特征、3D 坐标,并检查一致性:
def molecular_representation_pipeline(smiles):
from rdkit import Chem
from rdkit.Chem import AllChem, Descriptors
mol = Chem.MolFromSmiles(smiles)
if mol is None:
raise ValueError("Invalid SMILES")
Chem.SanitizeMol(mol)
# 1. SMILES canonical
canonical_smiles = Chem.MolToSmiles(mol)
# 2. InChI
inchi = Chem.MolToInchi(mol)
# 3. 分子图(节点特征:原子序数,边特征:键级)
atoms = [atom.GetAtomicNum() for atom in mol.GetAtoms()]
bonds = [(b.GetBeginAtomIdx(), b.GetEndAtomIdx(), int(b.GetBondTypeAsDouble()))
for b in mol.GetBonds()]
# 4. 3D 坐标(需要加氢)
mol_h = Chem.AddHs(mol)
AllChem.EmbedMolecule(mol_h, randomSeed=42)
AllChem.MMFFOptimizeMolecule(mol_h)
conf = mol_h.GetConformer()
coords = conf.GetPositions()
return {
'canonical_smiles': canonical_smiles,
'inchi': inchi,
'num_atoms': len(atoms),
'num_bonds': len(bonds),
'coords_3d': coords
}
# 测试
result = molecular_representation_pipeline('CCO')
print(result['canonical_smiles']) # CCO
print(result['inchi']) # InChI=1S/C2H6O/c1-2-3/h3H,2H2,1H3
一句话总结:所有表示都可以从 Mol 对象派生,千万别自己在字符串级别解析。RDKit 的 Mol 就是你分子生成的“中转枢纽”。