3. 分子结构表示:原子坐标、PDB与XYZ格式

做RMSD计算,第一步就是把分子结构读进来。你想想看,如果没有坐标数据,RMSD就是个空架子。我刚开始接触这个领域时,光搞清楚不同格式的区别就花了一整天。今天咱们就把原子坐标、PDB格式和XYZ格式彻底理清楚。

3.1 原子的位置就是用三个数字

任何一个原子在三维空间里的位置,就是一个三元组 (x, y, z)。单位通常是埃(Å),1 Å = 10⁻¹⁰ m。RMSD的本质就是在比较两套原子坐标的差异,所以坐标的精度和格式直接决定计算结果是否靠谱。

我个人习惯把所有格式都转成同一个内部表示再计算,避免格式转换的坑。先看看最基本的坐标长什么样:

# 纯坐标清单(示例)
C    1.234   2.345   3.456
O    0.123   1.234   2.345
N    2.345   3.456   4.567

这种裸坐标文件在实际工作中几乎不用,因为没有原子名称、残基信息,根本没法做RMSD的原子匹配。得靠PDB或XYZ格式来带上下文。

3.2 PDB格式:生物信息学的通用语言

PDB(Protein Data Bank)格式是最常见的生物大分子结构文件。它不仅可以存原子坐标,还能存残基名、链ID、温度因子、占有率等等。我在做蛋白质构象分析时,90%以上的输入都是PDB。

PDB文件每行固定80个字符,按记录类型开头。最常用的是ATOM和HETATM记录:

ATOM      1  N   ALA A   1       1.234   2.345   3.456  1.00  0.00           N
ATOM      2  CA  ALA A   1       1.567   2.678   3.789  1.00  0.00           C
ATOM      3  C   ALA A   1       1.890   2.123   3.012  1.00  0.00           C
ATOM      4  O   ALA A   1       2.111   2.444   3.555  1.00  0.00           O
ATOM      5  CB  ALA A   1       0.999   2.888   3.666  1.00  0.00           C

各列含义如下:

列号字段说明
1-6记录名ATOM 或 HETATM
7-11原子序列号整数,从1开始
13-16原子名如 CA、N、C、O
17-20残基名ALA、GLY、HOH
22链ID大写字母,A/B/…
23-26残基序号整数
31-38x 坐标八位,小数点后三位
39-46y 坐标八位,小数点后三位
47-54z 坐标八位,小数点后三位
55-60占有率1.00 或 0.00
61-66温度因子常用 B 因子
77-78元素符号右对齐
我的一点经验:很多PDB文件的原子名对齐方式很坑,比如 " CA " 和 "CA " 看起来一样,但用字符串解析时会出错。用固定宽度解析永远比用split安全。

PDB里还有一个重要的记录叫MODEL/ENDMDL,专门用来存多构象。做RMSD时经常会读入多个MODEL:

MODEL        1
ATOM      1  N   ALA A   1       1.234   2.345   3.456  1.00  0.00           N
ATOM      2  CA  ALA A   1       1.567   2.678   3.789  1.00  0.00           C
ENDMDL
MODEL        2
ATOM      1  N   ALA A   1       1.111   2.222   3.333  1.00  0.00           N
ATOM      2  CA  ALA A   1       1.444   2.555   3.666  1.00  0.00           C
ENDMDL
我曾经踩过的坑:有的PDB文件在ENDMDL后面还跟着多余的空行或注释,用Biopython读没事,但自己写解析器时容易漏掉最后一个MODEL。读完再检查原子数是个好习惯。

3.3 XYZ格式:轻量但缺少语义

XYZ格式就简单多了。第一行是原子数,第二行是注释(经常被忽略),后面每行一个原子。元素名加三个坐标,用空格或制表符分隔:

3
water molecule from DFT calculation
O       0.000   0.000   0.117
H       0.757   0.000  -0.469
H      -0.757   0.000  -0.469

XYZ的优点:解析超级快,文件体积小,适合存大量轨迹帧。缺点:没有残基、链、键信息,没法做原子匹配。做RMSD时,如果你比较两个相同原子顺序的XYZ文件,直接算就行。但如果顺序乱了,就悲剧了。

我一般在处理小分子(< 100 原子)时用XYZ格式,又快又简单。但在蛋白质或核酸上,必须用PDB,因为要按残基和链名做匹配。

3.4 两种格式的核心差异

特性PDBXYZ
原子信息元素 + 原子名 + 残基 + 链仅元素符号
坐标精度小数点后3位(固定)任意精度
多帧支持MODEL/ENDMDL多个XY文件或扩展格式
键信息CONECT记录可存键无
文件大小较大(固定80列每行)较小
适用场景蛋白质、核酸、复合物小分子、轨迹帧
解析难度中等(注意列对齐)极简单

我的建议:如果做RMSD计算,优先用PDB格式,因为它保留了残基和原子名称,可以直接按名称做匹配。如果只有XYZ,加一个拓扑文件(比如mol2或pdb)来提供原子映射关系。

3.5 原子坐标在RMSD中的角色

RMSD公式里只用到了坐标向量。但坐标本身的数值单位、精度、是否平移或旋转都会影响结果。在实际项目中,我会先做以下预处理:

  1. 单位统一:全部转成埃,有的文件可能是微微米或纳米。
  2. 对齐参考系:用最小二乘法把待比较的分子跟参考分子叠合(这就是RMSD计算的前置步骤)。
  3. 原子选择:只选骨架原子(如CA、N、C、O)或所有重原子,视具体问题而定。

比如这里有一个预处理代码片段(伪代码风格):

def read_pdb(filename):
    atoms = []
    with open(filename) as f:
        for line in f:
            if line.startswith("ATOM") or line.startswith("HETATM"):
                atom_name = line[12:16].strip()
                res_name = line[17:20].strip()
                chain    = line[21].strip()
                res_num  = int(line[22:26].strip())
                x = float(line[30:38].strip())
                y = float(line[38:46].strip())
                z = float(line[46:54].strip())
                atoms.append( (atom_name, res_name, chain, res_num, x, y, z) )
    return atoms

实际项目中我会用Biopython的PDBParser,但刚上手时自己写一写,能帮你彻底搞懂PDB的结构。

3.6 知识体系结构

下面这张图梳理了分子结构格式在RMSD计算流程中的位置:

分子结构表示在RMSD计算中的位置 PDB文件 XYZ文件 其他格式(mol2等) 解析器:提取原子坐标 + 原子名称/残基信息 固定宽度解析 / split解析 / Biopython 原子坐标列表 [(x1,y1,z1), …, (xn,yn,zn)] 单位:Å,附带原子名称用于匹配 RMSD计算(Kabsch/Krylov) 叠合 + 距离平方和 先解析格式,再提取坐标,最后做RMSD。PDB保留语义,XYZ轻量但无上下文。
一个小技巧:如果你读入的PDB有多个MODEL,可以先做一个参考MODEL的坐标副本,然后逐一计算每个MODEL相对于参考的RMSD,方便观察构象变化。

3.7 格式转换的注意事项

你可能会遇到需要把PDB转成XYZ,或者反过来。我推荐用OpenBabel或者RDKit来做格式转换,但自己写个简单脚本也没问题。下面是一个用Python从PDB提取坐标并输出XYZ的例子:

def pdb_to_xyz(pdb_file, xyz_file):
    atoms = read_pdb(pdb_file)  # 用上面定义的函数
    with open(xyz_file, 'w') as f:
        f.write(f"{len(atoms)}\n")
        f.write("converted from PDB\n")
        for a in atoms:
            elem = a[0][0]  # 取原子名的首字母(简化版,不完善)
            x, y, z = a[4], a[5], a[6]
            f.write(f"{elem:2s}  {x:9.3f} {y:9.3f} {z:9.3f}\n")

这个代码很粗糙,但能跑通。真实项目中元素符号得按原子名准确映射,比如"CA"是C,"HA"是H。直接用首字母会出错。

我曾经犯过的错:有一次我把PDB的"CA"(主链碳原子)的首字母取成C,然后输出XYZ时元素符号是C,但残基和链信息全丢了。后续做RMSD匹配时,原子顺序对应不上,结果完全错误。所以如果要做有匹配的RMSD,千万别丢掉原子名称。