031

SMILES 入门:AI 如何把分子变成字符串

SMILES 是把分子写成字符串的标准方式。这篇讲清语法规则、规范化的重要性与实际使用中的陷阱。

SMILES(Simplified Molecular Input Line Entry System)把分子的二维结构编码成一行字符串。它是化学信息学中最基础的表示——理解它的规则与局限,是做任何分子数据工作的前提。

基本语法

# 【原子】
#   C, N, O, S, P, F, Cl, Br, I    有机子集,可省略方括号
#   [Na+], [Fe+2], [nH], [13C]     需要方括号(电荷、同位素、显式氢)
#   小写 = 芳香原子:c, n, o, s
#
#   CC        乙烷(氢是隐含的)
#   C=C       乙烯
#   C#C       乙炔
#
# 【键】
#   -   单键(通常省略)
#   =   双键
#   #   三键
#   :   芳香键(通常省略,用小写原子表示)
#
# 【分支:用括号】
#   CC(C)C        异丁烷
#   CC(=O)O       乙酸
#   CC(=O)Nc1ccc(O)cc1    对乙酰氨基酚
#
# 【环:用数字标记闭合】
#   C1CCCCC1      环己烷
#   c1ccccc1      苯(芳香写法)
#   C1=CC=CC=C1   苯(凯库勒写法)
#   c1ccc2ccccc2c1  萘(两个环,用不同数字)
#
# 【立体化学】
#   顺反异构:/  和  \
#     F/C=C/F     反式
#     F/C=C\F     顺式
#   手性:@ 和 @@
#     N[C@@H](C)C(=O)O   L-丙氨酸
#     N[C@H](C)C(=O)O    D-丙氨酸
#
# 【断开的组分:用点】
#   CC(=O)[O-].[Na+]     乙酸钠
#   → 【盐、溶剂化物用这种方式表示】
#
# 【常见的记忆点】:
#   环闭合数字可以复用(一个环闭合后数字释放)
#   芳香性由 RDKit 感知,不同写法可以等价

规范化:最重要的概念

# 【同一个分子有无数种 SMILES 写法】
#   苯:c1ccccc1、C1=CC=CC=C1、c1ccc(cc1)、...
#
# 【后果】:
#   直接比较字符串会把同一分子当成不同的
#   → 去重、匹配、连接数据都会出错
#
# 【解决:规范 SMILES(canonical SMILES)】
#   用确定的算法选择唯一的写法

from rdkit import Chem

def canonical(smiles):
    mol = Chem.MolFromSmiles(smiles)
    return Chem.MolToSmiles(mol) if mol else None

print(canonical("c1ccccc1"))       # c1ccccc1
print(canonical("C1=CC=CC=C1"))    # c1ccccc1
# 【相同】

# 【重要提醒】:
#   1) 【规范算法是工具特定的】
#      RDKit 的规范 SMILES 与 OpenBabel 的可能不同
#      → 【跨工具比较必须用同一个工具规范化】
#
#   2) 规范化【不】处理:
#      - 盐型(CC(=O)[O-].[Na+] vs CC(=O)O)
#      - 互变异构体
#      - 不同的质子化态
#      → 【这些需要专门的标准化流程】(见 046)
#
#   3) 立体化学的处理
#      Chem.MolToSmiles(mol, isomericSmiles=False)
#      会去掉立体信息 —— 【有时需要,但要明确】

# 【实践建议】:
#   数据入库时统一规范化并存储规范 SMILES
#   → 之后所有的匹配、去重都基于它

SMILES 的信息含量与局限

包含 不包含
原子类型与连接 三维坐标
键级 构象
形式电荷 实际的电荷分布
立体化学(如果标注) 互变异构体的实际比例
同位素 晶型/固态形式
芳香性感知 溶液中的实际状态

最需要注意的两个局限SMILES 不含三维信息(做对接或三维建模时必须另外生成构象,见 049《构象生成入门》);SMILES 表示的是「一个」互变异构体或质子化态,而分子在溶液中可能是多种形式的平衡。

SMARTS:用于子结构匹配的扩展

# SMILES 描述【具体的分子】
# SMARTS 描述【一类结构模式】

from rdkit import Chem

# 基本用法
patt = Chem.MolFromSmarts("c1ccccc1")          # 苯环
mol = Chem.MolFromSmiles("CC(=O)Nc1ccc(O)cc1")
print(mol.HasSubstructMatch(patt))              # True
print(mol.GetSubstructMatches(patt))            # 匹配的原子索引

# SMARTS 特有的语法:
#   *          任意原子
#   [C,N]      碳或氮
#   [!C]       非碳
#   [#6]       原子序数 6(碳),包括芳香与脂肪
#   [CX4]      四价碳(sp3)
#   [OH]       带一个氢的氧(羟基)
#   [NX3;H2,H1;!$(NC=O)]   非酰胺的伯胺或仲胺
#   ~          任意键
#   @          环键
#   [r5]       五元环中的原子
#   $(...)     递归 SMARTS
#
# 【常用的官能团模式】:
PATTERNS = {
    "羧酸": "[CX3](=O)[OX2H1]",
    "伯胺": "[NX3;H2;!$(NC=O)]",
    "酰胺": "[NX3][CX3](=[OX1])",
    "酯": "[CX3](=O)[OX2H0][#6]",
    "硝基": "[NX3+](=O)[O-]",
    "卤代烷": "[CX4][F,Cl,Br,I]",
    "芳香羟基": "[OX2H][c]",
    "迈克尔受体": "[CX3]=[CX3][CX3]=[OX1]",   # 共价弹头(见 379)
}

def count_groups(smiles):
    mol = Chem.MolFromSmiles(smiles)
    if mol is None:
        return None
    return {name: len(mol.GetSubstructMatches(Chem.MolFromSmarts(p)))
            for name, p in PATTERNS.items()}

# 【SMARTS 是结构警示过滤的基础】(见 069)
# 【写 SMARTS 的建议】:
#   1) 写完后一定要在正例与反例上测试
#   2) 注意芳香性:c 与 C 不等价
#   3) 用 [#6] 而非 C 可以同时匹配芳香与脂肪碳

常见陷阱

  • 解析失败静默返回 None必须检查,否则后续操作会抛出难以理解的错误;
  • 芳香性的感知差异:不同工具对某些环系(如某些杂环)的芳香性判断可能不同——跨工具传递数据时要注意
  • 立体化学未指定:SMILES 中不写 @ 表示「未指定」而非「没有手性」——生成三维构象时会随机分配
  • 盐与混合物:用 . 分隔的多组分,建模时通常要去掉抗衡离子(见 047《去盐与去重复》);
  • 过长的 SMILES:大分子(如多肽、大环)的 SMILES 可能很长,某些工具有长度限制;
  • 特殊字符的转义:SMILES 中的 \ 在字符串处理中要注意转义(Python 中用原始字符串)。

关键要点

  • 同一分子有无数种写法——必须用规范 SMILES 才能正确比较与去重;
  • 规范算法是工具特定的——跨工具比较必须用同一个工具规范化;
  • 规范化不处理盐型、互变异构体与质子化态,这需要专门的标准化流程;
  • SMILES 不含三维信息;未指定立体化学 ≠ 没有手性

延伸资源