第2章 分子基础:化学结构表示、SMILES、InChI、分子格式转换

做虚拟筛选,第一件事是什么?

不是跑模型,不是搭流程。而是先把分子结构搞清楚。

你想想看,一个化合物连结构都表达不对,后面的对接、打分、聚类全是白搭。我自己一直觉得,结构表示是虚拟筛选的"入场券"。这一章我就带你把这几个基础概念理清楚:SMILES、InChI、还有各种分子格式怎么转。

为什么需要分子结构表示?

说白了,计算机不认识结构式。它只认字符串和二进制。

你得把化学结构翻译成机器能读的东西。这个翻译过程,就是分子表示。好的表示法要满足三点:

  • 唯一性:同一个分子,不管谁写,结果必须一样
  • 可解析性:字符串能还原回分子结构,不能有歧义
  • 紧凑性:占空间小,方便存储和网络传输

市面上常见的表示法有十几种,但真正在大规模筛选中用得上的,也就那么几个。我挑最核心的两个讲——SMILES和InChI,再加上格式转换这块硬骨头。

分子表示与格式转换知识体系 分子结构(2D/3D) SMILES 表示法 InChI 表示法 分子格式转换 线性字符串 Canonical SMILES 层次化结构 InChIKey SDF / MOL2 / PDB Open Babel / RDKit

SMILES:最流行的分子语言

SMILES全称是Simplified Molecular Input Line Entry System。名字挺长,用起来很简单。

它用一串ASCII字符表示分子结构。比如乙醇,就是CCO。苯环是c1ccccc1。你看,是不是很直观?

SMILES 规则 说明 示例
原子符号 大写字母,有机元素可省略氢 C 表示甲烷,O 表示水
单键/双键/三键 - 单键,= 双键,# 三键 C=C 乙烯,C#N 氢氰酸
环闭合 数字标记环的起始和终止位置 c1ccccc1 苯环
分支 括号表示支链结构 CC(C)C 异丁烷
离子/电荷 [Na+] 表示钠离子 [NH4+] 铵根

我个人习惯:写SMILES的时候,尽量用Canonical SMILES。不同工具生成的SMILES可能写法不同,但Canonical SMILES能保证同一分子只有一个表示。我建议用RDKit的MolToSmiles带canonical=True参数来标准化。

来,看看实际怎么用。下面这段代码,用RDKit读入SMILES并生成分子对象:

from rdkit import Chem

# 读取SMILES
smiles = "CCO"
mol = Chem.MolFromSmiles(smiles)

# 查看分子中的原子数
print(mol.GetNumAtoms())  # 输出 3

# 生成Canonical SMILES
canonical = Chem.MolToSmiles(mol, canonical=True)
print(canonical)  # 输出 CCO

嗯,这里要注意。SMILES有个让人头疼的问题——手性的表示。同样是CHBrClF,两种对映异构体的SMILES看起来几乎一样,只有@符号的朝向不同。我在项目中遇到过,两个库生成的SMILES串一模一样,但三维构象却是镜像的。后来我强制指定了四面体手性标签才解决。

InChI:更严谨的身份证

如果说SMILES是口语,那InChI就是化学界的"身份证号"。它由IUPAC制定,天生就是为唯一性设计的。

InChI的格式长这样:

InChI=1S/C2H6O/c1-2-3/h3H,2H2,1H3

这是乙醇的InChI。看出来了吗?它分了几个层次:分子式、连接表、氢原子位置。每个层次都用斜杠隔开。

InChI 层次 含义 示例(乙醇)
1S 版本号 / 标准化标志 1S 表示标准InChI
C2H6O 分子式(Hill排序) 碳氢氧依次排列
c1-2-3 连接表(原子连接关系) 1号C连2号C连3号O
h3H,2H2,1H3 氢原子分配 3号原子带1个H,2号带2个,1号带3个

但InChI有个短板——字符串太长。一长串字符放到文件里、数据库里,存起来不方便。所以又衍生出了InChIKey,它是InChI的哈希值,固定27个字符。拿乙醇来说:

LFQSCWFLJHTTHZ-UHFFFAOYSA-N

我曾经踩过的坑:InChIKey虽然紧凑,但有哈希冲突的风险。大约每10亿个分子才有一个冲突概率,但在百万级别的虚拟筛选里仍可能碰到。我建议对碰撞的分子做二次验证,用完整InChI回查。

从SMILES生成InChI,RDKit一行就行:

from rdkit import Chem
from rdkit.Chem import rdMolDescriptors

smiles = "CCO"
mol = Chem.MolFromSmiles(smiles)

# 生成标准 InChI
inchi = Chem.MolToInchi(mol)
print(inchi)

# 生成 InChIKey
inchikey = Chem.MolToInchiKey(mol)
print(inchikey)

分子格式转换:打通数据孤岛

真实项目里,你拿到的数据源五花八门。有的给SDF,有的给MOL2,有的干脆给一个PDB文件。这时候,格式转换就是必备技能。

我整理了一下最常见的分子格式对比:

格式 全称 适用场景 是否支持3D 备注
SDF Structural Data File 小分子数据库、虚拟筛选 是 一个文件可存多个分子
MOL2 Sybyl Mol2 分子对接(如AutoDock) 是 含原子类型和电荷信息
PDB Protein Data Bank 蛋白质结构、配体存放 是 原子坐标+连接信息
MOL MDL Molfile 单个分子存储 是 SDF的前身
SMILES 线性字符串 数据库存储、网络传输 否 最紧凑,不含3D坐标

格式转换,我个人最常用的是Open Babel。它支持的格式超过110种,几乎覆盖所有常见化学格式。命令极其简单:

# SMILES 转 SDF
obabel -: "CCO" -O ethanol.sdf --gen3d

# MOL2 转 PDB
obabel ligand.mol2 -O ligand.pdb

# SDF 批量转 SMILES
obabel library.sdf -O library.smi

用OBabel做格式转换时,我建议加上--gen3d或--conformer参数。嗯,不然后续对接会报错——分子没有三维坐标。

除了Open Babel,RDKit也内置了强大的格式处理能力。比如读取一个SDF文件并转为SMILES列表:

from rdkit import Chem

supplier = Chem.SDMolSupplier("molecules.sdf")
smiles_list = []
for mol in supplier:
    if mol is not None:
        smi = Chem.MolToSmiles(mol)
        smiles_list.append(smi)

print(f"Converted {len(smiles_list)} molecules")
# 输出示例: Converted 1250 molecules

实用建议:

  • 转格式前先做标准化:加氢、去盐、中和电荷。
  • 大规模筛选时,优先用SMILES做去重和索引,最后再转3D格式。
  • 我曾经处理过一个100万分子的库,直接从SDF读结构,内存直接爆了。后来我改用SMILES做预筛选,只保留命中分子再读全结构,效率提升了十几倍。

避坑与最佳实践

讲到这里,我总结几个实际项目中容易翻车的地方:

  • SMILES 歧义性:不同工具可能生成不同SMILES。解决办法——一律用Canonical SMILES。
  • InChI 缓慢:生成InChI比SMILES慢得多。用于数据库索引可以,但不建议在实时流程中大量调用。
  • 格式丢失信息:比如SMILES转SDF时,如果不加--gen3d,生成的是平面坐标。对接时直接报错。
  • 原子类型不对:不同力场用的原子类型不同。MOL2转PDB时,Sybyl原子类型会丢失。你需要重新分配GAFF或MMFF类型。

我自己的经验法则:存结构用SMILES + InChIKey,做计算用SDF/MOL2,做展示用SVG图。三种表示互相配合,基本能应对90%的场景。

说到这,分子基础和表示的这套东西,其实没多复杂。核心就是一句话——先标准化,再转换,永远保留原始来源的副本。拿不准的时候,拿一个已知分子来回转换几次,检查原子数和连接对不对。嗯,这招我用了好多年,从来没出过大问题。