SMILES 进阶:分支、环、芳香性、异构体、规范 SMILES、SMILES 解析原理
上一章我们搞懂了最基础的 SMILES 写法——就是把原子一个个串起来。但真实分子哪有这么乖?
有分支的、有环的、有芳香环的、有手性中心的……今天我就带你把这些坑全填上。
我自己刚接触 SMILES 的时候,写过一条带三个环的青霉素衍生物,结果因为括号没配对,解析器直接报错。后来我养成了一个习惯:写长 SMILES 时,先在纸上画一遍骨架,再一行行拆成子片段。
分支:用小括号搞定侧链
分支就是从一个原子上分出多条链。SMILES 用 () 包裹侧链。
来看个例子:
CC(C)C # 异丁烷:主链 C-C-C,中间 C 上有个甲基侧链
O=C(O)c1ccccc1 # 苯甲酸:主链苯环-羧基,羧基里的羟基是分支
括号可以嵌套。我见过最夸张的深度达到 6 层——那是个环肽模拟物。解析这样的 SMILES,栈式结构很管用。
写嵌套分支时,建议每打开一个左括号缩进一个层级,像写 JSON 一样。等最后压缩成一行再提交给代码。
环:数字标记 + 配对
开环分子简单。一旦涉及环键,就要用数字标记环的连接点。
规则:
- 在环的起点和终点原子上各写一个相同的数字(0~9)
- 数字可以重复使用,只要在首次出现后再次出现时形成闭合
- 最多支持 10 个环(数字 0-9),更多环需要
%两位数字
比如环己烷:
C1CCCCC1
第一个 C 后写 1,最后一个 C 后也写 1,表示这两头连起来。
我处理过一个三环体系:
C12CC3CC(C1)CC(C3)C2 # 金刚烷骨架
顺便说个坑:数字不能连续写两个不匹配的环数字,比如 C12 会被理解为 C 同时是环 1 和环 2 的起点,除非你确实想表达螺环。
芳香性:小写字母表示
苯环如果写成 C1=CC=CC=C1 也没错,但太啰嗦。SMILES 允许用小写字母表示芳香原子,让环变成 c1ccccc1。
芳香性判断标准:
- 原子必须在芳香环中(满足休克尔规则或常见芳香体系)
- 键不写双键/单键,默认芳香键
- 常见芳香原子:c(碳)、n(吡啶氮)、o(呋喃氧)、s(噻吩硫)
看几个对比:
| 结构 | 写法 | 说明 |
|---|---|---|
| 苯 | c1ccccc1 | 6 个芳香碳,环形 |
| 吡啶 | c1ccncc1 | 5 个 c + 1 个 n |
| 呋喃 | c1ccoc1 | 4 个 c + 1 个 o |
注意:芳香原子不能加氢显式写,比如 c1ccccc1 自动带 6 个 H。如果你显式写 C1=CC=CC=C1,解析器认为它是环己三烯,不再是芳香体系,后续芳香性检测会失败。
我曾经用 RDKit 读一批化合物,发现一个分子明明有苯环却算不出芳香性,查了半天就是因为 SMILES 里用了大写 C 且写了双键。换成小写 c 立刻正常。
手性异构体:@ 与 @@
手性中心用 @ 或 @@ 标记,表示原子周围取代基的排列方向(顺时针或逆时针)。
规则:
- 紧接在手性原子后面写 @ 或 @@
- @ 表示逆时针(从第一个取代基看过去)
- @@ 表示顺时针
- 只对 tetrahedral 中心(sp3 碳)生效
L-丙氨酸 vs D-丙氨酸:
L-Ala: N[C@@H](C)C(=O)O
D-Ala: N[C@H](C)C(=O)O
这里 C@H 中的 H 是手性碳上直接连的氢。要注意手性标记必须紧贴中心原子,不能中间插别的符号。
我个人经验:手动写手性 SMILES 容易把 @/@@ 弄反。我的做法是先画个费歇尔投影,再按 RDKit 的 Chem.MolToSmiles 反向验证一次。实在不确定就交给 3D 构象生成再检查。
顺反双键异构:/ 与 \
双键两端的方向用 / 和 \ 表示。斜杠写在键的相邻原子上,表示从该原子出发“朝上”或“朝下”。
典型例子:
C/C=C/C # 反式-2-丁烯:第一个 / 表示左侧甲基朝上,第二个 / 表示右侧甲基也朝上
C/C=C\C # 顺式-2-丁烯:左侧朝上,右侧朝下
这玩意很容易被忽略。我有个同事写药物分子库时,遗漏了斜杠,RDKit 默认给了最稳定的反式,结果对接结果全错了。后来每写一个双键,我都会检查斜杠是否成对出现。
规范 SMILES:让唯一性落地
同一种分子可以写出无数种 SMILES(从不同原子开始、不同环数字分配)。比如乙醇:
CCO
OCC
C(C)O
都是有效写法,但对计算机来说不是“同一个字符串”。规范 SMILES 就是通过一套规则(例如 CANGEN 算法)生成唯一的、确定性的字符串。
使用 RDKit 获取规范 SMILES:
from rdkit import Chem
mol = Chem.MolFromSmiles("C(C)O")
canon = Chem.MolToSmiles(mol)
print(canon) # 输出 CCO
规范化的核心步骤:
- 分配规范原子序号(基于图不变量排序)
- 固定遍历方向(从最小序号原子开始)
- 统一环数字分配(最小数字优先)
需要配套使用规范 SMILES 的场景:
- 数据库去重(同一化合物只存一条记录)
- 分子哈希索引
- 注册系统(确保新分子不与已有分子重复)
规范 SMILES 并不保证 100% 唯一性。有些互变异构体(如酮-烯醇)可能产生多个规范字符串。建议在数据库里同时存储 InChI 或骨架哈希作为二次校验。
SMILES 解析原理:从字符串到图
解析器做的事就是把这串字符翻译成分子图。核心逻辑可以用一段伪代码概括:
def parse_smiles(s):
stack = [] # 记录分支开始的位置
atoms = [] # 原子序列
bonds = [] # 键列表
ring_numbers = {} # 环号 -> 待连接原子
cur_atom = None
for ch in tokenize(s):
if ch == '(':
stack.append(len(atoms)) # 记录分支起点
elif ch == ')':
stack.pop()
elif ch.isdigit() or ch == '%':
number = read_ring_number(ch)
if number in ring_numbers:
# 与之前标记的环端点连键
partner = ring_numbers[number]
bonds.append((cur_atom, partner, 1))
del ring_numbers[number]
else:
ring_numbers[number] = cur_atom
elif ch in 'cCnNoOsS':
atom = parse_atom(ch)
atoms.append(atom)
if cur_atom is not None:
bonds.append((cur_atom, len(atoms)-1, 1))
cur_atom = len(atoms)-1
# ... 处理分支、方向、手性、键类型等
return (atoms, bonds)
实际成熟的解析器(如 OpenBabel、RDKit)会用更精细的状态机来处理括号嵌套、环配对、芳香性判断。但核心就是一条:从左到右,维护一个栈,遇到括号压栈,遇到环数字配对。
如果你想自己实现轻量级 SMILES 解析器(用来做分子子结构索引),建议只支持无分支无环的线性 SMILES,避开栈管理。等测试通过再逐步加入括号和环逻辑。
好了,上面这六块内容几乎覆盖了 90% 的日用 SMILES 场景。下面这张图帮你把整个知识网络串起来(SVG 画布),方便复习时快速定位。
梳理下来会发现,规矩虽然多,但每条都是历史演化的结果——为了让 SMILES 既简洁又明确。作为工程师,我最推崇的习惯是:遇到不熟悉的片段,先用 RDKit 画出来,再 dump 成规范 SMILES 看看。你亲眼看到机器怎么理解,比背一百条规则管用。