4、分子结构的数据结构:原子坐标、残基、链的表示
拿到一个 PDB 文件,你第一反应是什么?
我刚开始做结构解析的时候,觉得这就是一堆数字和字母。后来才发现,PDB 里藏着三层嵌套的数据结构——原子、残基、链。不理解这个结构,写校验脚本就是瞎蒙。
4.1 原子坐标:最底层的数据单元
PDB 里每一个原子对应一条 ATOM(或 HETATM)记录。说白了,就是一个编号、一个元素符号、三个浮点数坐标(x, y, z),再加上 occupancy(占有因子)、temperature factor(温度因子,也叫 B 因子)。
举个例子,一条典型的 ATOM 记录长这样:
ATOM 1 N ALA A 1 5.372 4.509 1.277 1.00 10.44 N
字段顺序:
- ATOM:记录类型,HETATM 表示异种原子(非标准氨基酸或小分子)
- 1:原子序号(整个文件内唯一)
- N:原子名称(比如 N、CA、CB)
- ALA:残基名称(氨基酸三字母缩写)
- A:链标识符(单个字符)
- 1:残基序号(整数,可重复配合插入码区分)
- 5.372 4.509 1.277:直角坐标系下的原子坐标,单位 Å
- 1.00:occupancy,通常为 1.0(完全占据),部分占据可能低于 1
- 10.44:温度因子(B 因子),表示原子位置的不确定性
- N:元素符号
这里面有一点坑——坐标精度。我见过有人直接从 PDB 里读 float 不保留小数位,导致校验时精度不对。PDB 标准格式坐标宽度固定为 8 列,3 位小数。你解析时必须按格式读,否则批次比对会出错。
4.2 残基:氨基酸或核酸的基本单元
一个残基由一组原子组成。比如丙氨酸(ALA)包含 N、CA、CB、C、O 这 5 个主链原子,加上侧链原子。残基在 PDB 里通过残基名称(三字母)+ 残基序号 + 链标识 来唯一标识。
残基序号不一定连续。我记得有一次校验项目,发现某结构中间缺失了一个残基,导致序列比对断裂。原因是在结构解析时那个 loop(环区)没建出来。批次校验时,必须检查残基序号是否严格递增,有没有跳跃或重复。
| 字段 | 含义 | 常见问题 |
|---|---|---|
| residue name | 三字母代码,如 ALA、ARG | 大小写混合?标准统一大写 |
| chain ID | 单字符链标识 | 空链或特殊字符(如空格)需警惕 |
| residue number | 整数序号,可含插入码(如 101A) | 不同批次序号体系不一致? |
| atom count | 每个残基在 PDB 中原子数量 | 缺失原子导致几何校验失败 |
4.3 链:蛋白质或核酸的高级组织
一条链就是一组连续残基的集合。PDB 用一个大写字母(A、B、C…)或数字标识链。多链结构常见于复合体:比如抗体-抗原、酶-底物、DNA-蛋白质。
链的连续性有两种含义:
- 序列连续性:残基按从 N 端到 C 端的顺序排列。
- 坐标连续性:残基在空间上是否紧邻(Cα 距离 < 4 Å)。
我的习惯是在校验脚本里先按链分组,再按残基序号排序,然后计算相邻残基间 Cα 距离。如果距离大于 4.5 Å,基本就是断链。批次一致性校验要比较两个 PDB 的链 ID 集合是否一致,残基顺序是否相同。
4.4 三层嵌套结构:从原子到链
把这三层关系画出来,你就明白 PDB 的层次结构了:
4.5 数据结构对批次校验的影响
为什么要理解这个层次?因为批次一致性校验的核心就是比较两套数据的结构树是否同构。
- 原子级别的校验:检查每个原子的坐标差、B 因子差,看是否在容忍阈值内。
- 残基级别的校验:比对残基类型、序号连续性、原子组成(缺失原子算不合格)。
- 链级别的校验:链数量、链 ID、链内残基顺序是否相同。不同批次的链 ID 分配可能不同(比如 A 链变成了 B 链),这时候需要做拓扑比对。
我曾经遇到一个案例:两个批次的 PDB 文件,残基序号完全一样,但链 ID 一个是 'A',另一个是 ' '(空格)。直接按字符串比较会判错。正确的做法是先标准化——把空格链映射成默认链,或者根据链内残基的序列相似性做匹配。
4.6 在实践中解析和校验
我个人偏好用 BioPython 的 PDB 模块来解析。它把结构自动映射成 SMCRA 层次:Structure → Model → Chain → Residue → Atom。用起来很顺手。
from Bio.PDB import PDBParser
parser = PDBParser(QUIET=True)
structure = parser.get_structure("1ubq", "1ubq.pdb")
for model in structure:
for chain in model:
print(f"Chain {chain.id} has {len(chain)} residues")
for residue in chain:
for atom in residue:
coords = atom.get_vector()
# 这里可以加校验逻辑
但注意,PDB 里可能包含多个模型(比如 NMR 结构,一个文件有 20 个模型)。批次校验时通常只比对第一个模型(或每个模型单独比对)。
4.7 数据结构带来的常见坑
- 插入码(iCode):残基序号后面可能跟一个字母,比如 101A 和 101B。纯数字比较会漏掉。你要把序号+插入码作为一个组合键。
- 交替构象(Alternate Conformations):同一个原子可能有多个坐标,用 altLoc 标识(比如 A 和 B)。校验时选主要构象(occupancy 最高)或者全部比对。
- 水分子与配体:HETATM 记录构成的残基不要和标准氨基酸混为一谈。批次校验前最好先分离出目标链。
我的一个实用技巧:写一个函数,把 PDB 结构序列化成一个嵌套字典 —— {chain_id: {residue_key: [atom_names, coords]}}。这样比对两批次只需递归遍历字典,性能高且逻辑清晰。代码放下面供参考:
def pdb_to_nested_dict(pdb_file):
from Bio.PDB import PDBParser
parser = PDBParser(QUIET=True)
struct = parser.get_structure('x', pdb_file)
# 只取第一个模型
model = struct[0]
data = {}
for chain in model:
ch = {}
for res in chain:
# 用 (resid, icode) 作为键
key = f"{res.get_id()[1]}{res.get_id()[2] or ''}"
atoms = {}
for atom in res:
coord = list(atom.get_vector())
atoms[atom.get_name()] = coord
ch[key] = atoms
data[chain.get_id()] = ch
return data
有了这个字典,批次之间对比就简单了——直接比较键集合、值精度。遇到不一致的地方,用差异报告定位。
说白了,PDB 的数据结构不复杂,但细节决定成败。你做校验脚本时,一定要把原子、残基、链三层都覆盖到。只要有一个层级对不上,批次一致性就算不通过。