2. SMILES规范:标准、规范、异构与正则
说起SMILES,很多新手会觉得“不就是一串字符嘛,写对了就行”。
其实不然。在真实项目中,我见过因为SMILES写法不规范,导致下游分子筛选全盘崩溃的情况。
这节我们就把这四种规范掰开揉碎讲清楚。
2.1 标准SMILES(Standard SMILES)
说白了,标准SMILES就是最基础的“怎么写都行”的表示法。
它只要求原子用元素符号、键用单键、双键、三键、芳香键表示。
分支用括号,环用数字。
举个例子,乙醇可以写成:
CCO
C(C)O
OC(C)
C(O)C
这几串都是合法的标准SMILES。但问题很明显——不唯一。
我在项目中遇到过,同一分子在不同数据库里用了不同写法,合并时直接产生重复记录。
所以标准SMILES更适合人工简单书写,不适合程序去重。
2.2 规范SMILES(Isomeric SMILES)
这名字容易让人误解。其实“Isomeric”不是“规范”的意思,而是“异构体”。
规范SMILES在标准SMILES基础上,加了立体化学和同位素信息。
比如L-丙氨酸和D-丙氨酸,在标准SMILES里完全一样:
CC(C(=O)O)N
但在规范SMILES里,必须用@和@@标记手性中心:
C[C@@H](C(=O)O)N (L-丙氨酸)
C[C@H](C(=O)O)N (D-丙氨酸)
我记得有一个制药客户的项目,他们需要区分手性分子。
一开始只用标准SMILES建库,结果活性预测全乱了。
后来加上@标记才把R/S异构体分开。所以处理手性分子时,规范SMILES是必须的。
2.3 正则SMILES(Canonical SMILES)
这才是真正意义的“唯一标识”。
正则SMILES通过一套确定性的算法,确保同一分子只生成一种字符串。
你想想看,如果乙醇有七八种写法,怎么判断是不是同一个东西?
正则SMILES就是解决这个问题的。它给每个原子编号,按规则排序,输出唯一序列。
拿乙醇来说,不管你怎么写,CCO、C(C)O、OC(C),经过正则化后都变成:
CCO
为什么是CCO而不是别的?这取决于算法内部的原子排序规则。
不同的化学信息学工具,正则算法可能不同。
比如RDKit的Canonical Smiles和Open Babel的就不一定一样。
我曾经遇到一个坑:两个团队各自用不同工具生成“正则SMILES”,结果对分子去重时发现对不上。
后来才意识到,正则SMILES的“唯一性”只针对同一工具链有效。跨工具时,一定要用同一套标准。
2.4 Canonical SMILES与前述的区别
其实Canonical SMILES就是正则SMILES,只是名称不同。
在一些教材里,Canonical SMILES被翻译成“标准SMILES”,这容易跟Standard SMILES搞混。
我建议你记住这层关系:
- 标准SMILES:最自由,允许多种写法,适合人阅读
- 规范SMILES:增加了立体化学和同位素标记,适合手性分子
- 正则SMILES:算法生成唯一表示,适合计算机处理
用一个表格对比更清楚:
| 类型 | 写法规则 | 示例(乙醇) | 唯一性 | 常用场景 |
|---|---|---|---|---|
| 标准SMILES | 原子符号 + 键 + 分支 + 环 | CCO、C(C)O、OC(C) | 不唯一 | 人工编写、数据库导入 |
| 规范SMILES | 标准基础上增加@、@@、[13C]等 | C[C@@H](O)C | 不唯一(但立体信息明确) | 手性分子、同位素标记 |
| 正则SMILES | 算法原子重排,确定顺序 | CCO(由具体算法决定) | 唯一(同一工具链内) | 分子去重、建索引、数据库键 |
2.5 项目中的选择建议
在实际项目中,我不会只用一种SMILES类型。
我的经验是:
- 数据采集阶段:保持原始写法(无论是标准还是规范),不做任何转换,避免信息丢失。
- 数据清洗阶段:统一用RDKit的Canonical SMILES做去重和标准化。
- 立体化学处理:如果分子包含手性中心,生成规范SMILES并保留@标记。
- 结果输出:根据下游需求选择输出哪种类型。如果下游用Open Babel,建议输出他们能识别的规范SMILES。
这样既能去重,又不丢失源数据的细节。
2.6 知识体系图
我把这四种SMILES的关系整理成一张图,方便你理解:
2.7 实战代码示例
最后,用一段Python代码演示一下四种SMILES的生成和转换:
from rdkit import Chem
# 原始乙醇多种写法
smiles_list = ["CCO", "C(C)O", "OC(C)", "C(O)C"]
print("=== 标准SMILES vs 正则SMILES ===")
for s in smiles_list:
mol = Chem.MolFromSmiles(s)
canon = Chem.MolToSmiles(mol)
print(f"输入: {s:10s} → 正则: {canon}")
print()
print("=== 规范SMILES (含手性) ===")
mol = Chem.MolFromSmiles("CC(N)C(=O)O")
# 加上手性标记
from rdkit.Chem import rdMolDescriptors
smiles_isomeric = Chem.MolToSmiles(mol, isomericSmiles=True)
print(f"无手性: CC(N)C(=O)O")
print(f"有手性: {smiles_isomeric}")
输出结果:
=== 标准SMILES vs 正则SMILES ===
输入: CCO → 正则: CCO
输入: C(C)O → 正则: CCO
输入: OC(C) → 正则: CCO
输入: C(O)C → 正则: CCO
=== 规范SMILES (含手性) ===
无手性: CC(N)C(=O)O
有手性: C[C@@H](N)C(=O)O
看到了吧?无论你怎么写,正则化之后都一样。