1. SMILES基础:表示法、原子符号、键类型、分支、环、芳香性

SMILES 这个东西,我最早接触是在做药物分子筛选的时候。当时看到一堆字符代表一个分子,觉得挺神奇。后来自己写解析器踩了不少坑,才真正理解它到底怎么设计的。说白了,SMILES 就是一种用 ASCII 字符描述分子结构的线性表示法。你给它一串字符串,它就能还原成原子和键的连接关系。核心就几个规则,我来一个一个拆开讲。

1.1 SMILES 表示法:基本思想

SMILES 全称 Simplified Molecular Input Line Entry System。它的思路很简单:按深度优先顺序遍历分子图,把原子符号、键符号和分支括号串起来。遇到环就打个数字标签标记,遇到芳香环就用小写字母。整个字符串就是一棵展开的树。

记住一点:SMILES 本身不保证唯一性。同一个分子可以写出多个合法的 SMILES。但规范化的正则化算法(比如 Weininger 算法)能帮你生成唯一的标准形式。我在项目中遇到过两次重复注册的案例,就是因为没有做规范化。

1.2 原子符号

原子符号直接用元素周期表的缩写。大写字母开头,如果是有机元素(C、N、O、S、P、F、Cl、Br、I),连方括号都不用。其他元素必须用方括号括起来。

类型 示例 说明
常见有机元素 C, N, O, S 直接写,不带方括号
卤素 F, Cl, Br, I 直接写,但不含 At
其他元素 [Fe], [Na], [Au] 必须用方括号
同位素或电荷 [13C], [O-] 方括号内可加同位素质量和电荷

嗯,这里要注意:方括号不是只给过渡金属用的。有机物若带了非默认价态或同位素,也得加。比如 C 默认四价,但如果你要表示一个带正电的碳正离子,就得写 [C+]。

我习惯在写自定义脚本时,先用 from rdkit import Chem 把 SMILES 解析一遍,看看原子符号是否合法。你想想看,万一写了个 [Si] 但忘了大写 S,解析器直接报错,排查半天才发现。

1.3 键类型

SMILES 里键用符号表示,放在两个原子之间。默认不写键时,表示单键(包括分支连接)。

  • 单键:不写,或者用 -。例如 CC 就是乙烷,C-C 也是乙烷。
  • 双键:=。例如 C=C 是乙烯。
  • 三键:#。例如 C#N 是氢氰酸。
  • 四键:$。极少见,主要用于过渡金属配合物。

我曾经在写合成路线自动生成工具时,遇到过一个坑:SMILES 里默认连接是单键,但很多人写芳香环时漏了上下键,结果解析出来变成了环己烷而不是苯。后来我在代码里加了一个 SanitizeMol 步骤才解决。

1.4 分支

分支用圆括号表示。括号里的内容挂在它前面的原子上。可以嵌套。举个例子:

CC(C)C       异丁烷
C(C)(C)C     也是异丁烷,写法不同
O=C(O)C      乙酸(C=O 连接 OH 和 CH3)

理解分支的关键是:括号里的内容相当于是一个侧链,它从当前原子出发,写完再回到主干继续往下。我记得第一次手动解析分支时,用栈实现深度优先遍历,嵌套层数一多就容易搞混。递归才是正道。

注意括号不能少写半边!我曾经在批量清洗数据时,因为一行 SMILES 缺少右括号导致整个解析中断。建议用 Chem.MolFromSmiles 时包裹 try-except,做好异常处理。

1.5 环

环的表示是 SMILES 的一大亮点。用数字做标签,表示两个相同数字的原子上有一条额外的连接。数字从 1 开始,依次递增。例如:

C1CCCCC1    环己烷(6个碳,1号标签连接首尾)
C1=CC=CC=C1  1,3-环己二烯
c1ccccc1     苯(小写 c 表示芳香碳)

你想想看,为什么不用 C1CCCCC1 而要用 c1ccccc1?因为芳香性影响了电子结构和反应活性。后者是芳香环,前者只是饱和环。

我建议在一个分子里,环的数字标签不要重复使用。如果有多个环,可以用 1, 2, 3…… 直到 9。超过 9 个则需要用 % 加两位数,比如 %10。其实很少碰到超过 9 个环的分子,但写解析器时还是要兼容。

1.6 芳香性

这是最容易混淆的地方。SMILES 里芳香原子用小写字母表示:c、n、o、s 等。注意不是 C 大写才是芳香,而是小写字母本身表示该原子参与芳香体系。例如:

c1ccccc1      苯(6个芳香碳)
c1ncncn1      三嗪(3个碳和3个氮交替)
c1ccoc1       呋喃(氧不芳香,但碳是芳香)

关键在于:小写字母自动隐含了单键/双键交替的凯库勒结构,但实际连接是离域的。在规范化 SMILES 时,RDKit 会把芳香环转成小写,非芳香环转为大写。我在做虚拟筛选时,输入数据里混着大小写混合的 SMILES,比对指纹时发现完全对不上,就是因为芳香性表示不一致。

一个实用技巧:用 Chem.MolFromSmiles 解析后,再用 Chem.MolToSmiles 输出,就能得到标准形式。比如输入 C1=CC=CC=C1 输出的是 c1ccccc1。这就是规范化的效果。

1.7 知识体系结构图

SMILES 表示法核心要素 SMILES 原子符号 键类型 分支 环 芳香性 说明 每个分支都有子规则:方括号原子、同位素/电荷、默认价态; 单键省略/显式、双三键、环数字标签最多9个;芳香小写字母自动离域。 规范化推荐:Chem.MolToSmiles(Chem.MolFromSmiles(s))。

1.8 一个完整的示例

我们拿阿斯匹灵(阿司匹林)来实战。它的结构式是邻乙酰水杨酸。我经常用它作测试用例,因为同时包含芳香环、单键、双键、分支、羧基和酯基。两种合法的 SMILES:

CC(=O)Oc1ccccc1C(=O)O         非规范形式
O=C(C)Oc1ccccc1C(=O)O         另一种顺序

用 RDKit 规范化一下:

from rdkit import Chem
s = "CC(=O)Oc1ccccc1C(=O)O"
mol = Chem.MolFromSmiles(s)
std = Chem.MolToSmiles(mol)
print(std)  # 输出类似 O=C(C)Oc1ccccc1C(=O)O

注意:Chem.MolFromSmiles 可能因为括号不匹配或环标签重复而返回 None。写自动化脚本时,一定要做 if mol is None 的检查,否则后续调用会崩掉。我吃过这个亏,跑了三天的数据分析,结果因为一条脏数据导致整个 pipeline 中断。

1.9 小结

SMILES 的基础规则其实不多:原子符号、键类型、分支、环、芳香性。但组合起来能表达几乎任何有机分子。我个人认为,熟练掌握这些规则是后续做数据清洗和增强的前提。你如果能手动写出常见分子的 SMILES,写解析器和生成器就更容易理解。

嗯,最后留个小练习:试试写出咖啡因的 SMILES。它有三个环、双键和多个氮原子。写出来后再用 RDKit 验证一下,看看和你预想的是否一致。