3. 结构质量检查:残基缺失、原子缺失、异常键长键角、Ramachandran图

拿到一个PDB文件,我最习惯做的事不是直接看原子坐标,而是先跑一遍质量检查。你想想看,如果模型里残基都缺了一串,或者键长快到断裂了,后面不管是做分子动力学还是对接,都是白费功夫。我在项目里遇到过好多次,就是因为开头没做这步检查,后面跑了几天的模拟结果全是废的。所以这一章,我们就把这几个核心检查项挨个捋一遍。

说白了,结构质量检查就四件事:残基有没有缺、原子有没有丢、键长键角是不是在合理范围内、主链二面角落在不落在允许区。下面我从工程实战的角度一个个讲,每个检查我都会告诉你常见的坑,以及我自己的处理方式。

3.1 残基缺失检查

残基缺失是最容易被忽略的问题。很多PDB文件里的loop区、或是末端残基直接被删掉了,但PDB header里根本没注明。我最早做结构分析的时候,拿过一个X-ray结构,链A从1到250,链B却只有10到200——中间缺了十几号残基,而我用了好几天才发现。

检查残基缺失,核心方法是用PDB文件的SEQRES记录和实际原子记录的ATOM/HETATM序列做比对。我给一个常用的Python片段:

from Bio.PDB import PDBParser

parser = PDBParser(QUIET=True)
structure = parser.get_structure("1abc", "1abc.pdb")
for model in structure:
    for chain in model:
        residues = list(chain.get_residues())
        res_ids = [res.get_id()[1] for res in residues]
        gaps = []
        for i in range(1, len(res_ids)):
            if res_ids[i] - res_ids[i-1] != 1:
                gaps.append((res_ids[i-1]+1, res_ids[i]-1))
        if gaps:
            print(f"Chain {chain.id} gaps: {gaps}

这里要注意:PDB文件里残基编号可能跳过某些数,比如从50直接跳到55。这不一定是缺失,可能是故意留的间隔用来放插入片段。我建议先读SEQRES里的完整序列,再跟实际残基编号比对,这样能排除假阳性。

缺失类型常见原因我的处理方式
末端缺失结晶时柔性末端看不清楚如果后续做MD,可以尝试用Modeller补齐;如果做对接,建议舍弃末端
内部缺失loop区电子密度不够优先用同源模建补齐,否则标记为缺失并记录审计信息
整段缺失结构域未被结晶必须生成缺失列表,写入audit日志,不能直接忽略
一个小技巧:跑检查前先看一眼PDB文件头里的REMARK 465(X-ray)或REMARK 500(NMR),里面通常会说明哪些残基没有模型化。把这两部分信息跟程序检查结果交叉验证,基本就不会漏了。

3.2 原子缺失检查

原子缺失比残基缺失更隐蔽。有时候残基编号还在,但少了几个侧链原子。比如一个赖氨酸只有Cα、Cβ,后面四个原子全没了。这就是典型的电子密度不够导致的“部分残基”。

我检查原子缺失的惯用思路是:遍历每个残基,跟标准氨基酸原子列表比对。Biopython里的standard_aa_names字典可以帮忙。下面是我常用的检查函数:

from Bio.PDB.Polypeptide import standard_aa_names

def check_missing_atoms(structure):
    for model in structure:
        for chain in model:
            for residue in chain:
                if residue.get_resname() in standard_aa_names:
                    expected_atoms = set(standard_aa_names[residue.get_resname()])
                    actual_atoms = set(a.name for a in residue.get_atoms())
                    missing = expected_atoms - actual_atoms
                    if missing:
                        print(f"Missing atoms in {residue.get_resname()}{residue.get_id()[1]}: {missing}

不过这里有个坑:有些标准库把氧原子命名为O,而有些PDB里主链羰基氧可能叫O或O1。我建议统一用atom.name.strip()清理空格再比较。另外,非标准残基(比如配体、修饰氨基酸)不要用这个办法,否则会报一大堆假阳性。

曾经有个让我很头疼的案例:检查某个酶结构时,发现所有谷氨酰胺的NE2原子都缺失。后来发现是文件里把NE2写成了NE (少了个2)。只靠set差集会漏掉这种情况。所以我额外加了一步——检查残基总原子数是否小于阈值(比如对于Lys,20个原子才算完整)。

3.3 异常键长键角检查

键长键角是结构质量的直接体现。正常的Cα-C键长约1.53Å,C-N肽键约1.33Å。如果偏差超过0.05Å,说明这个区域可能有几何冲突。我记得刚入行时用过Engh & Huber的键长键角参数表,那是做X-ray修结构的老标准,现在很多工具还用这套参数。

用Biopython算键长很简单:

from Bio.PDB.vectors import calc_bond_length

def bond_length_check(structure, max_dev=0.1):
    for model in structure:
        for chain in model:
            for residue in chain:
                atoms = list(residue.get_atoms())
                for i in range(len(atoms)-1):
                    for j in range(i+1, len(atoms)):
                        if atoms[i].is_disordered() or atoms[j].is_disordered():
                            continue
                        d = calc_bond_length(atoms[i].get_vector(), atoms[j].get_vector())
                        # 这里需要查标准键长表简单判断
                        if abs(d - 1.5) > max_dev and abs(d - 1.33) > max_dev and abs(d - 1.54) > max_dev:
                            print(f"Abnormal bond: {atoms[i].name}-{atoms[j].name} {d:.2f}Å")

这个代码很粗糙,实战中建议用ProCheck或MolProbity的几何参数。我自己的习惯是先将PDB转成mmCIF格式,然后直接用mmCIF自带的_struct_conn表里的键长数据做统计。如果无键长信息可用,就用CCDC的CSD平均键长做参照。

键角检查也是类似思路。但键角计算需要三个原子成组,而且涉及二面角。我更推荐用现成的tool——比如pdb-tools里的pdb_validate_angles。下面是我用的一个简化版:

# 用ProCheck的命令行检查
procheck -pdb my_structure.pdb -outdir procheck_output
# 然后从procheck_output/bands.ls提取z-score异常的残基
关键阈值参考:一般蛋白结构,键长RMSD应小于0.02Å,键角RMSD应小于2.0°。如果键长RMSD超过0.05Å,或键角RMSD超过4.0°,这个结构基本可以认为是低质量结构,不适合做下游分析。

3.4 Ramachandran图分析

Ramachandran图是结构质量的“照妖镜”。它展示的是主链二面角φ和ψ的分布。一个高质量的晶体结构,90%以上的残基应该落在“最允许区”,而完全不允许区的残基占比应小于2%。

实战中最简单的方式是用Rampage(验证工具,可网页运行)或者用Biopython画一个。我自己写过一个脚本,调用rampage命令行:

rampage -f my_structure.pdb -o rampage_output
# 读取输出文件中的favoured、allowed、outlier统计

如果你非要自己算二面角,Biopython有现成接口:

from Bio.PDB.vectors import calc_dihedral
from Bio.PDB.PDBParser import PDBParser

structure = PDBParser(QUIET=True).get_structure("test", "test.pdb")
for model in structure:
    for chain in model:
        residues = list(chain.get_residues())
        for i in range(1, len(residues)-1):
            try:
                n = residues[i-1]["N"].get_vector()
                ca = residues[i]["CA"].get_vector()
                c = residues[i]["C"].get_vector()
                n_next = residues[i+1]["N"].get_vector()
                phi = calc_dihedral(c, n, ca, c)
                psi = calc_dihedral(n, ca, c, n_next)
                print(f"{residues[i].get_resname()}{residues[i].get_id()[1]} phi={phi:.2f} psi={psi:.2f})
            except KeyError:
                continue

注意一点:Phi和Psi的合理范围会受氨基酸类型影响。比如甘氨酸的允许区更大,而脯氨酸的φ角几乎锁死在-60°左右。我见过有人把甘氨酸超出的点也标记为outlier,这就不对了。所以一定要按氨基酸类型做分类统计。

下面这张SVG图可以帮你快速理解质量检查的完整流程——从PDB文件读入,到输出检查报告:

PDB结构质量检查流程 读入PDB文件 残基缺失检查 原子缺失检查 异常键长键角 Ramachandran图 汇总检查报告 & 审计日志 无严重问题 存在严重问题 四个检查必须同时执行,不能跳过任何一项;审计日志需记录检查版本、阈值、异常残基列表。

3.5 实战避坑与自动化

一次完整的质量检查,我一般在预处理流水线里写成脚本,每次出新结构都自动跑一遍。这里我给出一个简易的Python脚本流程:

# quality_check.py  – 配合Biopython和外部工具
import sys, subprocess, tempfile, os
from Bio.PDB import PDBParser

pdb_file = sys.argv[1]
parser = PDBParser(QUIET=True)
structure = parser.get_structure("target", pdb_file)

# 1. 残基缺失检查
gaps = []
for chain in structure[0]:
    res_ids = [res.get_id()[1] for res in chain if res.get_id()[0] == ' ']
    for i in range(1, len(res_ids)):
        if res_ids[i] - res_ids[i-1] > 1:
            gaps.append((chain.id, res_ids[i-1]+1, res_ids[i]-1))

# 2. 原子缺失检查(略,见前文)

# 3. 键长检查:调用ProCheck
subprocess.run(["procheck", "-pdb", pdb_file, "-outdir", "procheck_out"])

# 4. Ramachandran检查:调用Rampage
subprocess.run(["rampage", "-f", pdb_file, "-o", "rampage_out"])

# 5. 输出汇总
with open("qa_report.txt", "w") as f:
    f.write(f"Gaps found: {len(gaps)}\n")
    for g in gaps:
        f.write(f"Chain {g[0]}: residues {g[1]}-{g[2]} missing\n")
    # 读取procheck/rampage的报告追加
print("Done. See qa_report.txt")
曾经掉进的一个坑:我用Rampage时,以为输出的outlier百分比就是最终结论。但实际上,有些outlier是因为残基处于结合位点或配体结合口袋,允许区外也是正常的。后来我学乖了:对每个outlier残基,再检查它的B因子和电子密度map,如果B因子很低且密度清晰,就把它标注为“允许的异常”,不纳入审计报警。

最后提醒一句:所有检查结果都要写入审计跟踪日志。我习惯用JSON格式记录,包含检查时间、工具版本、阈值、每个异常的详细信息。这样几周后反查时,能快速知道当初是怎么处理的。这点我们在后面第8章会详细展开。

质量检查不是一次性工作。只要PDB文件有任何修改(比如补齐缺失原子、调整键角),都要重新跑一遍全部检查,并生成新的审计日志。我见过团队因为跳过重检,导致下游分析用了过时的异常标记,白白浪费了一个月的计算资源。