第2章 分子基础:化学结构表示、SMILES、InChI、分子格式转换
做虚拟筛选,第一件事是什么?
不是跑模型,不是搭流程。而是先把分子结构搞清楚。
你想想看,一个化合物连结构都表达不对,后面的对接、打分、聚类全是白搭。我自己一直觉得,结构表示是虚拟筛选的"入场券"。这一章我就带你把这几个基础概念理清楚:SMILES、InChI、还有各种分子格式怎么转。
为什么需要分子结构表示?
说白了,计算机不认识结构式。它只认字符串和二进制。
你得把化学结构翻译成机器能读的东西。这个翻译过程,就是分子表示。好的表示法要满足三点:
- 唯一性:同一个分子,不管谁写,结果必须一样
- 可解析性:字符串能还原回分子结构,不能有歧义
- 紧凑性:占空间小,方便存储和网络传输
市面上常见的表示法有十几种,但真正在大规模筛选中用得上的,也就那么几个。我挑最核心的两个讲——SMILES和InChI,再加上格式转换这块硬骨头。
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%的场景。
说到这,分子基础和表示的这套东西,其实没多复杂。核心就是一句话——先标准化,再转换,永远保留原始来源的副本。拿不准的时候,拿一个已知分子来回转换几次,检查原子数和连接对不对。嗯,这招我用了好多年,从来没出过大问题。