Skip to content

#37: leave out residues outside the 20 standard amino acids with a warning, and name them - #38

Merged
teese merged 1 commit into
mainfrom
37-drop-unknown-residues
Oct 24, 2026
Merged

teese merged 1 commit into
mainfrom
37-drop-unknown-residues

Conversation

@teese

@teese teese commented Oct 24, 2026 •

Copy link
Copy Markdown

Problem

A structure predicted from a sequence that contains X holds a residue named UNK, which Boltz-2 and other predictors build with backbone atoms and a CB. Prodes, which calculates surface descriptors (charge, electrostatic potential, hydrophobicity, surface patches) from a PDB file, reads radii, pKa and hydrophobicity from a residue table that holds only the 20 standard amino acids. Such a structure therefore failed with KeyError: UNK is not found in the list with residues once the surface calculation had started, the error named neither the chain nor the residue number, and no features were produced. One affected example is the Boltz-2 model of the antibody with FDA UNII 105J35OE21, where residue 1 of both heavy chains is UNK, most likely an N-terminal pyroglutamate.

Closes #37

What changed

  • By default, parse_pdb_text now leaves out every ATOM residue outside the 20 standard amino acids, after the function elect_conformers has kept one alternate conformation per residue, and logs a warning naming each, for example UNK A1, UNK B1. The run finishes.
  • drop_unknown_residues=False (calculate, run_prodes, prodes.read, PDBparser.parse; --no-drop-unknown-residues on the command line) raises a ValueError naming them instead.
  • When a removed residue was at a chain end, the residue now at that end gets no terminal titratable group (N+ for the amine, C- for the carboxylate) and no terminal charge, because in the molecule it is bonded to the removed residue. Implemented as Chain.n_terminus_removed / c_terminus_removed, read by Residue.pkas, and a cleared Residue.terminus.
  • A residue set (--residue-set NAME=FILE, a list of residue labels the surface patch features are also reported for) loses a removed residue with a warning; a set naming only removed residues is refused.
  • prodes_run.json gains removed_residues and settings.drop_unknown_residues.
  • README section "Input files" documents the default, the opt-out and what leaving residues out does to the features. Version 10.0.0 to 11.0.0, because prodes.read() and PDBparser.parse() now return a structure without residues they used to include. No feature value of any other structure changes.

Results

Boltz-2 model of UNII 105J35OE21 (4 chains, UNK at residue 1 of chains A and B) Result
Before KeyError once the surface calculation had started, no features
After, default Warning naming UNK A1, UNK B1; calculation completes; prodes_run.json has removed_residues: ["UNK A1", "UNK B1"]
After, --no-drop-unknown-residues ValueError naming UNK A1, UNK B1, raised when the file is read

Local tests green, 509/509, including 21 new tests of this behaviour in tests/test_unknown_residues.py. The 105J35OE21 results are from a manual run on that structure. CI green.

Acceptance criteria still open

All acceptance criteria met.

Not verified

  • The terminal charge treatment is correct for an N-terminal pyroglutamate and wrong for an ordinary residue whose name is merely unknown. The README says so; Prodes cannot tell the two apart.
  • A file whose residue names come from a force-field tool (HIE, HSD, CYX) loses those residues by default, with a warning, rather than stopping. The README says to read the warning.
  • No PROPKA run was made on a structure containing UNK, so what PROPKA itself does with UNK atoms when predicting the neighbouring pKa values is not known.
  • The feature values for the 105J35OE21 model were not compared with those of a model built from a Q at residue 1.
  • A file whose chain records are interleaved (chain A, then B, then A again) already gets wrong termini on main: the parser function build_structure marks the C terminus on the last residue of whichever chain the previous atom record belonged to, not on the last residue of each chain. Removing a residue from such a file can move which residue gets that wrong terminus. Not fixed here; interleaved chains are not tested.
Detail
  • The check runs after elect_conformers, because a file can model one position as two different amino acids and only the elected one is the residue.
  • A residue counts as unknown if any one of its records carries an unknown name, since every atom reads its own residue name for its radius. One stray UNK record therefore removes a residue that is otherwise ALA, and it is reported as UNK.
  • The chain ends are taken in order of first appearance, the order build_structure gives chain.residues.
  • clear_removed_termini runs after parse_pdb_text writes the structure's last C terminus. For a chain that lost an end it sets terminus by the same if/elif that Residue.pkas uses, so a chain left with one residue ([UNK, VAL] or [VAL, UNK]) has the pKa group and the charged atom on the same end. Chains that lost no end are not touched.
  • A pKa from the --pka file for the residue next to a removed end is dropped by redo_pkas with its existing warning, because that residue has no terminal group.
  • A residue that elect_conformers renamed to an unknown name and that was then removed is taken out of alternate_conformers.residues_renamed, so prodes_run.json does not list it as both renamed and removed.
  • HETATM parsing is not checked: ligands and waters are never in the residue table.
  • A structure with no standard residue at all is refused whatever the setting.
  • New tests are in tests/test_unknown_residues.py; tests/test_cli.py, tests/test_run_prodes.py and tests/test_output_bundle.py were updated for the new argument and run record field.

…ning, and name them

A structure predicted from a sequence holding an X contains a residue named UNK, which the residue table has no radius, pKa or hydrophobicity for. Such a structure parsed without error and then failed with a KeyError naming only the residue type once the surface calculation had started.

parse_pdb_text now checks the protein residues after the conformer election. By default it removes every residue that is not one of the 20 standard amino acids and logs a warning naming each by chain and number, so an unattended run finishes. drop_unknown_residues=False (calculate, run_prodes, prodes.read, PDBparser.parse, parse_pdb_text; --no-drop-unknown-residues on the command line, appended last to the arguments main passes to calculate) raises a ValueError naming them instead. HETATM parsing is not checked. A structure with no standard residue is refused either way.

A residue removed from a chain end takes the terminal charge with it: the chain records which end was removed, Residue.pkas gives the residue now at that end no terminal group, and its terminus attribute is cleared, so the pKa groups and the atom charges agree. This is correct for an N-terminal pyroglutamate and is documented as wrong for an ordinary residue with an unknown name. A residue set loses the removed residues with a warning, and a set naming only removed residues is refused. The run record lists the removed residues as removed_residues and the setting under settings, and a residue the conformer election renamed to an unknown name and that was then removed is not also listed as renamed.

Version 11.0.0, because prodes.read and PDBparser.parse are documented public entry points and now return a structure without residues they used to include. No feature value of any other structure changes.
@teese
teese merged commit 9476015 into main Oct 24, 2026
3 checks passed
@teese
teese deleted the 37-drop-unknown-residues branch October 24, 2026 02:25
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.

Drop residues missing from the residue table on request, and name them when raising

1 participant