Skip to content

Fix ColabFold MSA script dropping selenomethionine (MSE) residues - #61

Open
SID-6921 wants to merge 2 commits into
NVIDIA-BioNeMo:mainfrom
SID-6921:fix/colabfold-mse-selenomethionine
Open

SID-6921 wants to merge 2 commits into
NVIDIA-BioNeMo:mainfrom
SID-6921:fix/colabfold-mse-selenomethionine

Conversation

@SID-6921

@SID-6921 SID-6921 commented Nov 3, 2026

Copy link
Copy Markdown

What

fetch_target_msa_colabfold.py's seq_from_pdb() only matches lines startswith("ATOM"). Selenomethionine (MSE) — a standard Se-SAD/MAD phasing substitution for methionine — is always recorded as HETATM in PDB files, never ATOM, even though it is genuinely part of the polymer chain. The effect isn't just a wrong residue letter: MSE residues are silently dropped from the derived sequence entirely, which is a frameshift/deletion, not a substitution.

Verified empirically with a synthetic ALA-MSE-GLY chain: the current code returns "AG" (2 residues) instead of "AMG" (3 residues).

The sibling script pdb_to_boltz_template_cif.py in the same package doesn't have this bug, because it parses via biotite.structure.io.pdb.PDBFile.read(...).get_structure(), which includes HETATM-recorded residues as part of the chain, combined with its own _AA3to1 dict that already has "MSE": "M".

Fix

  • Added "MSE": "M" to the AA3 dict.
  • Changed the line filter to accept HETATM lines too, but only when the residue name is one we recognize (i.e., res_name in AA3) — not a blanket startswith(("ATOM", "HETATM")), which would also pick up non-polymer HETATM records such as a Ca2+ ion (resName and atom name both "CA", matching the alpha-carbon atom-name check).

Verification

Two synthetic PDB files (no existing test file covers this script):

  1. ALA-MSE-GLY chain → now correctly returns "AMG".
  2. ALA-GLY chain plus a Ca2+ ion HETATM and a water HETATM → still correctly returns "AG" (confirms the fix doesn't introduce a false positive from the ion).

fetch_target_msa_colabfold.py's seq_from_pdb() derives the target protein's
sequence for MSA/ColabFold submission by scanning only lines starting with
"ATOM". Selenomethionine (MSE), a very common Se-SAD/MAD crystallographic
phasing substitution for methionine, is always recorded as HETATM in PDB
format even though it is a genuine part of the polymer chain. Any MSE
residue in a fetched RCSB target structure was therefore not just mapped to
the wrong letter, it was skipped entirely, silently shortening and
frameshifting the derived sequence relative to the true target.

The sibling script in the same package, pdb_to_boltz_template_cif.py, reads
structures via biotite (which includes HETATM-recorded residues as part of
the chain) and already maps MSE -> M in its own dict, so the same pipeline
produces two different target sequences for the same target.pdb depending
only on which conditioning mode (msa vs template) is selected -- an
operational choice, not a biological one.

Fix: also accept HETATM lines, but only when the residue name is one we
recognize (now including MSE), not a blanket "any HETATM". A naive
blanket-HETATM fix would have introduced a new bug: a Ca2+ ion's PDB record
has residue name "CA" and atom name "CA" -- the identical four characters as
a protein alpha-carbon atom -- and would be picked up as a spurious residue.

Verified with two synthetic PDB snippets (not part of the existing test
suite, which has no file for this script):
1. ALA-MSE-GLY chain: previous code returned "AG" (MSE silently dropped);
   fixed code returns "AMG".
2. ALA-GLY chain plus a Ca2+ ion and a water HETATM: both before and after
   the fix return "AG" -- confirming the fix does not introduce the Ca2+
   false-positive a blanket HETATM match would have.

Scope note: this repo vendors most skill directories nightly from upstream
sources (sync-skills.yml, overwritten via rsync --delete); only directories
listed in catalog-exceptions.yml are native and safe to edit here.
`workflows/` is explicitly listed as native, so this file is fixable
in-repo. The duplicate copy of this same script under
`skills/bionemo-agent-toolkit/skills/complexa-binder-design/scripts/` is
NOT in that native list and was left untouched, since any edit there would
be overwritten on the next sync.

Signed-off-by: Siddhardha Nanda <99672439+SID-6921@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings November 3, 2026 15:12

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

@greptile-apps

greptile-apps Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The change affects PDB-derived target sequences in the Complexa binder-design workflow.

The MSE fix is ready, but the shipped skill must be regenerated before merging so the sync check passes.

Summary

Both copies of fetch_target_msa_colabfold.py now retain MSE residues as methionine.

  • PDB-derived target sequences keep selenomethionine as methionine.

Reviews (2) · Last reviewed commit: "Sync generated aggregate and add regress..." · Reviewed by Greptile

Running scripts/plugin_sync.py --write to propagate the MSE/selenomethionine
fix from the source script
(workflows/.../complexa-binder-design/scripts/fetch_target_msa_colabfold.py)
into the generated aggregate copy at
skills/bionemo-agent-toolkit/skills/complexa-binder-design/scripts/fetch_target_msa_colabfold.py,
which plugin_sync.py --check confirmed had drifted (the plugin-sync CI gate
would have failed on this PR).

Also added tests/test_fetch_target_msa_colabfold.py, covering the two
synthetic cases verified manually in the original fix: an ALA-MSE-GLY
chain must return "AMG", and an ALA-GLY chain with a Ca2+ ion and a water
HETATM record must still return "AG" (guards against the fix treating any
HETATM as a residue). Both pass against the real script.

Signed-off-by: Siddhardha Nanda <99672439+SID-6921@users.noreply.github.com>
@SID-6921

Copy link
Copy Markdown
Author

Checking in — this has been open about a week with no review yet. Happy to address any feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants