欢迎关注我的CSDN:https://spike.blog.csdn.net/
本文地址:https://spike.blog.csdn.net/article/details/163749797
免责声明:本文来源于个人知识与公开资料,仅用于学术交流,欢迎讨论,不支持转载。

抗体感知聚类是一条分层聚类流水线,即首先进行长度过滤,以全链 entity-40 作为基线聚类;随后通过 ANARCI/IMGT 完成抗体标注,提取 CDRH3、CDRH123 和 CDRL123 特征。使用 MMseqs2 宽松召回候选序列对后,以 85% 阈值进行精确全局同一性复核,再采用确定性贪心中心聚类,最终在各 full40 baseline 簇内依据抗体特征进一步细分。
流程如下:
#mermaid-svg-MVZt7NQBKb7CrLiu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MVZt7NQBKb7CrLiu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MVZt7NQBKb7CrLiu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MVZt7NQBKb7CrLiu .error-icon{fill:#552222;}#mermaid-svg-MVZt7NQBKb7CrLiu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MVZt7NQBKb7CrLiu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MVZt7NQBKb7CrLiu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MVZt7NQBKb7CrLiu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MVZt7NQBKb7CrLiu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MVZt7NQBKb7CrLiu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MVZt7NQBKb7CrLiu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MVZt7NQBKb7CrLiu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MVZt7NQBKb7CrLiu .marker.cross{stroke:#333333;}#mermaid-svg-MVZt7NQBKb7CrLiu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MVZt7NQBKb7CrLiu p{margin:0;}#mermaid-svg-MVZt7NQBKb7CrLiu .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-MVZt7NQBKb7CrLiu .cluster-label text{fill:#333;}#mermaid-svg-MVZt7NQBKb7CrLiu .cluster-label span{color:#333;}#mermaid-svg-MVZt7NQBKb7CrLiu .cluster-label span p{background-color:transparent;}#mermaid-svg-MVZt7NQBKb7CrLiu .label text,#mermaid-svg-MVZt7NQBKb7CrLiu span{fill:#333;color:#333;}#mermaid-svg-MVZt7NQBKb7CrLiu .node rect,#mermaid-svg-MVZt7NQBKb7CrLiu .node circle,#mermaid-svg-MVZt7NQBKb7CrLiu .node ellipse,#mermaid-svg-MVZt7NQBKb7CrLiu .node polygon,#mermaid-svg-MVZt7NQBKb7CrLiu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-MVZt7NQBKb7CrLiu .rough-node .label text,#mermaid-svg-MVZt7NQBKb7CrLiu .node .label text,#mermaid-svg-MVZt7NQBKb7CrLiu .image-shape .label,#mermaid-svg-MVZt7NQBKb7CrLiu .icon-shape .label{text-anchor:middle;}#mermaid-svg-MVZt7NQBKb7CrLiu .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MVZt7NQBKb7CrLiu .rough-node .label,#mermaid-svg-MVZt7NQBKb7CrLiu .node .label,#mermaid-svg-MVZt7NQBKb7CrLiu .image-shape .label,#mermaid-svg-MVZt7NQBKb7CrLiu .icon-shape .label{text-align:center;}#mermaid-svg-MVZt7NQBKb7CrLiu .node.clickable{cursor:pointer;}#mermaid-svg-MVZt7NQBKb7CrLiu .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-MVZt7NQBKb7CrLiu .arrowheadPath{fill:#333333;}#mermaid-svg-MVZt7NQBKb7CrLiu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-MVZt7NQBKb7CrLiu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-MVZt7NQBKb7CrLiu .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MVZt7NQBKb7CrLiu .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-MVZt7NQBKb7CrLiu .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MVZt7NQBKb7CrLiu .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-MVZt7NQBKb7CrLiu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-MVZt7NQBKb7CrLiu .cluster text{fill:#333;}#mermaid-svg-MVZt7NQBKb7CrLiu .cluster span{color:#333;}#mermaid-svg-MVZt7NQBKb7CrLiu div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-MVZt7NQBKb7CrLiu .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-MVZt7NQBKb7CrLiu rect.text{fill:none;stroke-width:0;}#mermaid-svg-MVZt7NQBKb7CrLiu .icon-shape,#mermaid-svg-MVZt7NQBKb7CrLiu .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MVZt7NQBKb7CrLiu .icon-shape p,#mermaid-svg-MVZt7NQBKb7CrLiu .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-MVZt7NQBKb7CrLiu .icon-shape .label rect,#mermaid-svg-MVZt7NQBKb7CrLiu .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MVZt7NQBKb7CrLiu .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MVZt7NQBKb7CrLiu .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MVZt7NQBKb7CrLiu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 长度过滤
全链 entity-40 baseline
ANARCI/IMGT 抗体标注
CDRH3、CDRH123、CDRL123 特征提取
MMseqs2 宽松召回候选对
代码内 85% 精确全局同一性复核
确定性贪心中心聚类
在各 full40 baseline 簇内按抗体特征进一步细分
如图:

实现入口为 sabdab2/build_clusters_by_entity.py,聚类阶段主要位于:
sabdab2/clustering/mmseqs.py:全链 baseline、MMseqs2 候选召回、聚类产物和审计摘要。sabdab2/clustering/antibody.py:ANARCI 结果解析、CDR 特征构建、精确同一性、特征聚类和最终细分。
1. 输入与长度过滤
先完成 mmCIF 修复、protein entity 提取、质量过滤,以及可选的完全相同复合物去重。上述步骤决定哪些 entity 能进入聚类,不改变下述聚类公式。
聚类阶段只保留满足以下条件的 protein entity:
L ( x ) ≥ L min L(x) \ge L_{\min} L(x)≥Lmin
其中, L min L_{\min} Lmin 来自命令行参数 --min-length,默认值为 10。也就是说,默认配置下长度小于 10 aa 的 entity 不进入最终 cluster 文件;这不是不可配置的"长度不超过 9 aa"硬编码规则。如果过滤后没有任何合格 entity,聚类阶段直接失败,不生成空的 cluster 文件。
实现位置:sabdab2/clustering/mmseqs.py::_run_clustering_stage。
2. 全链 entity-40 baseline
所有长度合格的 protein entity 首先使用 MMseqs2 easy-cluster 建立全链 baseline:
bash
mmseqs easy-cluster \
protein_entities.fasta entity40 tmp \
--min-seq-id 0.4 \
-c 0.8 \
--cov-mode 0 \
--alignment-mode 3 \
--cluster-mode 0 \
-s 7.5
设两条序列 a , b a,b a,b 的长度分别为 L a , L b L_a,L_b La,Lb,局部比对中相同残基数为 N i d N_{\mathrm{id}} Nid,包含内部 gap 列的比对列数为 L a l n L_{\mathrm{aln}} Laln,覆盖计算所用的已对齐残基跨度为 L c o v L_{\mathrm{cov}} Lcov。在当前参数下:
I M M s e q s ( a , b ) = N i d L a l n ≥ 0.40 I_{\mathrm{MMseqs}}(a,b)=\frac{N_{\mathrm{id}}}{L_{\mathrm{aln}}} \ge 0.40 IMMseqs(a,b)=LalnNid≥0.40
--cov-mode 0 使用相对于两条序列中较长者的双向覆盖条件:
C b i ( a , b ) = L c o v max ( L a , L b ) ≥ 0.80 C_{\mathrm{bi}}(a,b)=\frac{L_{\mathrm{cov}}}{\max(L_a,L_b)} \ge 0.80 Cbi(a,b)=max(La,Lb)Lcov≥0.80
可以等价表达为 query 和 target 都至少覆盖 80%,实现语义不是两个可以独立选择的单向 coverage 模式。MMseqs2 仍进行局部比对;--alignment-mode 3 的作用是计算真实的相同残基数,而不是把比对改成全局比对。
--cluster-mode 0 是 greedy set-cover。输出保留 MMseqs2 选出的真实代表序列。一个成员加入某个 baseline 簇,表示由该代表的聚类归属覆盖;同一簇中任意两个非代表成员不保证彼此也达到 40% identity 和 80% coverage。
实现位置:sabdab2/clustering/mmseqs.py::MMseqsRunner.baseline。参数语义可参考 MMseqs2 官方指南。
3. ANARCI/IMGT 抗体标注
建立 baseline 后,所有待聚类 entity 都会送入 ANARCI,并使用 IMGT 编号体系:
text
scheme = imgt
batch size = 500
代码按 ANARCI 返回的 domain 类型分类:
H:重链 domain。K或L:轻链 domain。- 仅有一个重链 domain:
vh_vhh_vnar。 - 仅有一个轻链 domain:
vl。 - 同一条 entity 同时有一个重链和一个轻链 domain:
scfv。 - 没有 H/K/L 抗体 domain:
non_antibody,按普通 protein 处理。
同一 entity 如果出现多个重链 domain 或多个轻链 domain,代码无法无歧义地选择特征,将其标记为 ambiguous_antibody。ANARCI 返回的 domain 若缺少必需 CDR,则标记为 incomplete_antibody。这两类 entity 最终都采用 singleton 策略。
需要区分三种情况:
- ANARCI 没有命中抗体 domain:不是错误,entity 按普通 protein 保留 baseline 归属。
- 已识别抗体 domain,但 CDR 不完整或同类 domain 存在歧义:该 entity 强制成为 singleton。
- ANARCI 导入失败、批处理异常或返回结果结构不一致:整次聚类失败,不静默退回 baseline。
实现位置:sabdab2/clustering/antibody.py::annotate_entities 和 _classify_domains。
4. IMGT CDR 特征提取
代码按以下 IMGT 编号闭区间提取 CDR:
| CDR | IMGT 范围 |
|---|---|
| CDR1 | 27--38 |
| CDR2 | 56--65 |
| CDR3 | 105--117 |
ANARCI 编号中的空字符、. 和 - 不进入特征序列。代码同时构造完整的 variable_sequence,但当前聚类策略不使用 variable sequence 建簇。
实际进入抗体特征聚类的只有三类序列:
S C D R H 3 ( x ) = C D R H 3 ( x ) S_{\mathrm{CDRH3}}(x)=\mathrm{CDRH3}(x) SCDRH3(x)=CDRH3(x)
S C D R H 123 ( x ) = C D R H 1 ( x ) ∥ C D R H 2 ( x ) ∥ C D R H 3 ( x ) S_{\mathrm{CDRH123}}(x) =\mathrm{CDRH1}(x)\Vert\mathrm{CDRH2}(x)\Vert\mathrm{CDRH3}(x) SCDRH123(x)=CDRH1(x)∥CDRH2(x)∥CDRH3(x)
S C D R L 123 ( x ) = C D R L 1 ( x ) ∥ C D R L 2 ( x ) ∥ C D R L 3 ( x ) S_{\mathrm{CDRL123}}(x) =\mathrm{CDRL1}(x)\Vert\mathrm{CDRL2}(x)\Vert\mathrm{CDRL3}(x) SCDRL123(x)=CDRL1(x)∥CDRL2(x)∥CDRL3(x)
其中 ∥ \Vert ∥ 表示直接拼接,不插入分隔符:
- 重链参与
CDRH3和CDRH123两层。 - 轻链只参与
CDRL123一层。 - scFv 同时参与上述三层。
- 注释不完整的抗体不进入任何 CDR 特征搜索。
实现位置:sabdab2/clustering/antibody.py::parse_anarci_domain 和 _feature_sequences。
5. MMseqs2 负责宽松召回候选
三类 CDR 特征分别建簇。在调用 MMseqs2 前,代码先按特征序列去重:完全相同的特征只选择一个稳定 ID 进入候选搜索,聚类完成后再把归属展开回所有原始 entity。
MMseqs2 使用 easy-search 做 all-vs-all 候选召回:
bash
mmseqs easy-search \
input.fasta input.fasta candidates.tsv tmp \
--min-seq-id 0.75 \
-c 0.8 \
--cov-mode 0 \
--alignment-mode 3 \
-s 7.5 \
--max-seqs <唯一特征序列总数> \
--format-output query,target
这里的 75% identity 和双向 80% coverage 是候选召回阈值 ,不是最终同簇阈值。--max-seqs 被设置为唯一特征序列总数,以避免 MMseqs2 默认结果数量上限截断潜在邻居。每条序列的自配对还会由代码显式加入候选集合。
只有被 MMseqs2 召回的候选对才会进入下一步精确复核。
实现位置:sabdab2/clustering/mmseqs.py::MMseqsRunner.candidate_pairs 和 sabdab2/clustering/antibody.py::exact_feature_clusters。
6. 85% 精确全局同一性
每个候选对随后由代码内的 Needleman--Wunsch 仿射 gap 全局比对重新计算同一性。计分参数为:
text
match +1
mismatch 0
gap-open -2
gap-extend -1
动态规划首先最大化比对得分;如果多个路径同分,则优先保留相同残基数更多的路径。设最终路径中的相同残基数为 N m a t c h N_{\mathrm{match}} Nmatch,精确同一性定义为:
I e x a c t ( a , b ) = N m a t c h max ( ∣ a ∣ , ∣ b ∣ ) I_{\mathrm{exact}}(a,b) =\frac{N_{\mathrm{match}}}{\max(|a|,|b|)} Iexact(a,b)=max(∣a∣,∣b∣)Nmatch
只有满足以下条件时,两条特征序列之间才建立无向邻接关系 :
I e x a c t ( a , b ) ≥ 0.85 I_{\mathrm{exact}}(a,b) \ge 0.85 Iexact(a,b)≥0.85
该公式已经通过较长序列长度对未对齐端部和长度差进行惩罚。因此,代码没有单独的"CDR3 必须等长"规则,也没有要求 Coverage = 1.0。例如,17 个匹配残基与一条 20 aa 序列比较时,精确同一性可以恰好为 17 / 20 = 0.85 17/20=0.85 17/20=0.85;只要该序列对先被候选搜索召回,就可能建立邻接关系。
实现位置:sabdab2/clustering/antibody.py::global_sequence_identity。
7. CDR 特征的确定性贪心中心聚类
不会把 85% 邻接图直接取 connected components,而是反复选择中心:
- 为每个唯一特征序列建立包含自身的 85% 邻居集合。
- 在尚未分配的序列中,选择能覆盖最多剩余邻居的序列作为中心。
- 如果多个候选中心覆盖数相同,选择 ID 字典序最小者。
- 将该中心及其尚未分配的邻居归入同一特征簇。
- 删除这些成员,重复执行直到所有特征完成分配。
- 将完全相同特征的其他原始 ID 展开到其对应特征簇。
每次找一个"能拉到最多朋友"的序列当组长,把组长和这些朋友划成一组,然后从剩余序列中继续选下一位组长。
若 R R R 是当前未分配序列集合, N ( v ) N(v) N(v) 是 v v v 的 85% 邻居集合,则每轮中心可写为:
r ∗ = arg max v ∈ R ∣ N ( v ) ∩ R ∣ r^*=\underset{v\in R}{\arg\max}\ |N(v)\cap R| r∗=v∈Rargmax ∣N(v)∩R∣
覆盖数并列时,以 ID 字典序作为确定性决胜条件。
这是一种中心式星形分组,而不是完全链接聚类。因此,每个成员都与当轮中心满足 85% 精确门槛,但同一特征簇中两个非中心成员彼此不保证达到 85%。
实现位置:sabdab2/clustering/antibody.py::exact_feature_clusters。
8. 最终簇的组合键
#mermaid-svg-8sxfZCC6DBHcXnpN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8sxfZCC6DBHcXnpN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8sxfZCC6DBHcXnpN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8sxfZCC6DBHcXnpN .error-icon{fill:#552222;}#mermaid-svg-8sxfZCC6DBHcXnpN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8sxfZCC6DBHcXnpN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8sxfZCC6DBHcXnpN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8sxfZCC6DBHcXnpN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8sxfZCC6DBHcXnpN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8sxfZCC6DBHcXnpN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8sxfZCC6DBHcXnpN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8sxfZCC6DBHcXnpN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8sxfZCC6DBHcXnpN .marker.cross{stroke:#333333;}#mermaid-svg-8sxfZCC6DBHcXnpN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8sxfZCC6DBHcXnpN p{margin:0;}#mermaid-svg-8sxfZCC6DBHcXnpN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8sxfZCC6DBHcXnpN .cluster-label text{fill:#333;}#mermaid-svg-8sxfZCC6DBHcXnpN .cluster-label span{color:#333;}#mermaid-svg-8sxfZCC6DBHcXnpN .cluster-label span p{background-color:transparent;}#mermaid-svg-8sxfZCC6DBHcXnpN .label text,#mermaid-svg-8sxfZCC6DBHcXnpN span{fill:#333;color:#333;}#mermaid-svg-8sxfZCC6DBHcXnpN .node rect,#mermaid-svg-8sxfZCC6DBHcXnpN .node circle,#mermaid-svg-8sxfZCC6DBHcXnpN .node ellipse,#mermaid-svg-8sxfZCC6DBHcXnpN .node polygon,#mermaid-svg-8sxfZCC6DBHcXnpN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8sxfZCC6DBHcXnpN .rough-node .label text,#mermaid-svg-8sxfZCC6DBHcXnpN .node .label text,#mermaid-svg-8sxfZCC6DBHcXnpN .image-shape .label,#mermaid-svg-8sxfZCC6DBHcXnpN .icon-shape .label{text-anchor:middle;}#mermaid-svg-8sxfZCC6DBHcXnpN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8sxfZCC6DBHcXnpN .rough-node .label,#mermaid-svg-8sxfZCC6DBHcXnpN .node .label,#mermaid-svg-8sxfZCC6DBHcXnpN .image-shape .label,#mermaid-svg-8sxfZCC6DBHcXnpN .icon-shape .label{text-align:center;}#mermaid-svg-8sxfZCC6DBHcXnpN .node.clickable{cursor:pointer;}#mermaid-svg-8sxfZCC6DBHcXnpN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8sxfZCC6DBHcXnpN .arrowheadPath{fill:#333333;}#mermaid-svg-8sxfZCC6DBHcXnpN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8sxfZCC6DBHcXnpN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8sxfZCC6DBHcXnpN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8sxfZCC6DBHcXnpN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8sxfZCC6DBHcXnpN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8sxfZCC6DBHcXnpN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8sxfZCC6DBHcXnpN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8sxfZCC6DBHcXnpN .cluster text{fill:#333;}#mermaid-svg-8sxfZCC6DBHcXnpN .cluster span{color:#333;}#mermaid-svg-8sxfZCC6DBHcXnpN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8sxfZCC6DBHcXnpN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8sxfZCC6DBHcXnpN rect.text{fill:none;stroke-width:0;}#mermaid-svg-8sxfZCC6DBHcXnpN .icon-shape,#mermaid-svg-8sxfZCC6DBHcXnpN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8sxfZCC6DBHcXnpN .icon-shape p,#mermaid-svg-8sxfZCC6DBHcXnpN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8sxfZCC6DBHcXnpN .icon-shape .label rect,#mermaid-svg-8sxfZCC6DBHcXnpN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8sxfZCC6DBHcXnpN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8sxfZCC6DBHcXnpN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8sxfZCC6DBHcXnpN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
是
是
全链 baseline 相同?
CDRH3 特征簇相同?
CDRH123 特征簇相同?
进入同一个最终簇
记:
- B ( x ) B(x) B(x):entity x x x 的 full40 baseline 代表。
- H 3 ( x ) H_3(x) H3(x):其
CDRH3特征簇代表。 - H 123 ( x ) H_{123}(x) H123(x):其
CDRH123特征簇代表。 - L 123 ( x ) L_{123}(x) L123(x):其
CDRL123特征簇代表。
最终分区键严格按照 entity 类型构造。
8.1 普通非抗体蛋白
K ( x ) = ( f u l l 40 , B ( x ) ) K(x)=\left(\mathrm{full40},B(x)\right) K(x)=(full40,B(x))
普通蛋白完全沿用 baseline 归属。若一个 baseline 簇同时包含普通蛋白和抗体,两类 entity 因最终键类型不同而被分开。
8.2 VH、VHH 或 VNAR
当前代码不再进一步区分 VH、VHH 和 VNAR,三者共用 vh_vhh_vnar 类别:
K ( x ) = ( h e a v y _ a n t i b o d y , B ( x ) , H 3 ( x ) , H 123 ( x ) ) K(x)=\left( \mathrm{heavy\antibody}, B(x), H_3(x), H{123}(x) \right) K(x)=(heavy_antibody,B(x),H3(x),H123(x))
8.3 VL
K ( x ) = ( v l , B ( x ) , L 123 ( x ) ) K(x)=\left( \mathrm{vl}, B(x), L_{123}(x) \right) K(x)=(vl,B(x),L123(x))
VL 不单独使用轻链 CDR3 簇,也不使用 variable-domain 簇。
8.4 scFv
K ( x ) = ( s c f v , B ( x ) , H 3 ( x ) , H 123 ( x ) , L 123 ( x ) ) K(x)=\left( \mathrm{scfv}, B(x), H_3(x), H_{123}(x), L_{123}(x) \right) K(x)=(scfv,B(x),H3(x),H123(x),L123(x))
scFv 的三个 CDR 特征簇归属必须全部一致,才可能进入同一最终簇。
8.5 不完整或有歧义的抗体
K ( x ) = ( s i n g l e t o n , x ) K(x)=\left(\mathrm{singleton},x\right) K(x)=(singleton,x)
由于 member ID 被直接写入组合键,每个此类 entity 都形成独立 singleton。
两条 entity 只有最终分区键完全相同才进入同一最终簇。所有键都包含 baseline 代表,singleton 又天然只含单条序列,因此:
C f i n a l ( x ) ⊆ C f u l l 40 ( x ) C_{\mathrm{final}}(x)\subseteq C_{\mathrm{full40}}(x) Cfinal(x)⊆Cfull40(x)
也就是说,抗体规则只能拆细 baseline 簇,绝不会重新合并不同 baseline 簇。代码还会显式验证最终结果是 baseline 的无遗漏、无重复分割。
实现位置:sabdab2/clustering/antibody.py::refine_clusters 和 validate_refinement。
9. 代表序列的选择
最终 cluster 文件的每行第一项是真实选出的最终代表,不一定总是 full40 的原代表。
- 普通蛋白组:如果 full40 代表仍在该细分组中,则继续使用它;否则选择与 full40 代表全链精确同一性最高的成员,ID 字典序用于并列决胜。
- singleton:唯一成员就是代表。
- 抗体组:对每个成员,计算其所有相关 CDR 特征与相应特征簇中心的精确同一性之和;总和最高者成为最终代表,ID 字典序用于并列决胜。
例如,重链抗体成员 x x x 的中心得分为:
S ( x ) = I e x a c t ( S C D R H 3 ( x ) , S C D R H 3 ( H 3 ( x ) ) ) + I e x a c t ( S C D R H 123 ( x ) , S C D R H 123 ( H 123 ( x ) ) ) S(x)=I_{\mathrm{exact}}\!\left(S_{\mathrm{CDRH3}}(x),S_{\mathrm{CDRH3}}(H_3(x))\right) +I_{\mathrm{exact}}\!\left(S_{\mathrm{CDRH123}}(x),S_{\mathrm{CDRH123}}(H_{123}(x))\right) S(x)=Iexact(SCDRH3(x),SCDRH3(H3(x)))+Iexact(SCDRH123(x),SCDRH123(H123(x)))
最终输出中,代表位于行首,其余成员按 ID 排序;所有 cluster 行再按代表 ID 排序,从而保证相同输入和相同工具结果产生稳定文本。
实现位置:sabdab2/clustering/antibody.py::_choose_final_representative、representative_first 和 cluster_text。
10. 输出与审计产物
最终 cluster 文件覆盖所有满足 --min-length 的合格 protein entity,并保证每个成员恰好出现一次。聚类阶段还会生成:
baseline_entity40.txt:抗体细分前的全链 baseline。antibody_numbering.tsv:ANARCI domain、链类型、query 范围、variable sequence、CDR3、CDR123 和状态。entity_cluster_membership.tsv:每个 entity 的 full40、CDRH3、CDRH123、CDRL123、最终代表、角色、链类别和注释状态。summary.json:MMseqs2 版本与完整调用参数、策略版本cdr85-v1、ANARCI 统计、各层簇统计、异常大簇警告以及输出文件 SHA-256。
最终结果会校验:
- 所有预期成员均被唯一覆盖。
- 没有重复成员或空簇。
- 每个最终簇都完全位于某一个 full40 baseline 簇中。
- 每行首项是该簇实际代表。
实现位置:sabdab2/clustering/mmseqs.py::_run_clustering_stage、validate_cluster_text。