Skip to content

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

Closed

Description

@teese

User Story

As a user calculating surface features for many predicted structures
I want Prodes to leave out residues it has no data for, with a warning that names them, and to be able to ask for an error instead
So that one unknown residue in one model does not stop the calculation for that structure

Description

  • Problem: a structure predicted from a sequence that contains X holds a residue named UNK. Boltz-2, a structure prediction model that writes mmCIF files, builds it with the atoms N, CA, C, O and CB. Prodes reads every residue's properties (radii, aromatic carbons, pKa, hydrophobicity) from the residue table in prodes/data/general_data.json, which has no UNK entry, so the run stops with KeyError: UNK is not found in the list with residues.
  • Example: an antibody registered in the FDA Global Substance Registration System (GSRS) under the Unique Ingredient Identifier (UNII) 105J35OE21. GSRS gives its heavy chain sequence as starting with X, at the position where other records of the same antibody have a glutamine, so the residue is most likely an N-terminal pyroglutamate. In the Boltz-2 model built from the GSRS sequences, residue 1 of both heavy chains, A and B, is UNK. Of 7012 Boltz-2 models built from GSRS protein sequences in one downstream project, 103 had at least one X in the submitted sequences, counted from the sequences each model's input record lists.
  • Current behaviour: the KeyError is raised during the surface calculation, after the structure has been read, and it names the residue type but not the chain or residue number.
  • Desired behaviour: by default, Prodes removes those residues when the structure is read, logs a warning that lists each one by chain, name and number, records them in the run record (prodes_run.json in the output bundle, written by run_metadata in src/prodes/output.py), and calculates the features from the remaining residues. An unattended caller, such as an agent, then gets features rather than an error for the sake of one or two residues. With the option set to False, Prodes raises a ValueError listing them instead.

Technical Context

  • parse_pdb_text in src/prodes/io/parser.py reads all records, keeps the first model, keeps one conformation per residue where the file models several (elect_conformers, the conformer election, which keeps the best occupied one) and builds the Structure object. The residue name is final only after the conformer election, because a file can model one position as two different amino acids, and the conformer election chooses one.
  • build_structure gives the first residue of each chain the N terminus (parser.py:359), and the last residue of each chain the C terminus. If an unknown first residue were removed before build_structure, the second residue would become the N terminus and would be given an N-terminal charge that it does not have in the molecule. For a pyroglutamate there is no free N-terminal amine at all.
  • calculate() in src/prodes/run.py is called both from the CLI and in-process by other code. The CLI passes its arguments to calculate() by position, and tests/test_cli.py pins that order, so a new argument is appended at the end of the calculate() signature and at the end of the argument list the CLI builds.
  • The run record written by run_metadata already carries models, alternate_conformers and inferred_elements for the same reason: each says that the features describe less than, or something other than, the whole file.

Implementation Suggestion

  • In parse_pdb_text, after the conformer election, find the residues whose name is not in data.all_residues().
  • With the option False: raise a ValueError that lists each one as chain, residue name and residue number with insertion code, and names the option that removes them.
  • With the option True, the default (drop_unknown_residues in parse_pdb_text, PDBparser.parse, prodes.read and calculate(), --no-drop-unknown-residues on the CLI to turn it off): remove the records of those residues, print one line naming them, and store the chain, name and number of each on the Structure object, so that calculate() can write them to prodes_run.json.
  • A removed residue takes its terminus with it: when the first residue of a chain is removed, no residue of that chain is given the N terminus, and the same for the last residue and the C terminus. The atoms that carried that charge are the ones removed.
  • Raise if removing them leaves no residue.
  • Add the list of removed residues to the run record, as an empty list when none were removed.
  • Document the option in the README section "Input files".

Acceptance Criteria

  • By default, a PDB file holding a residue that is not in the residue table gives a feature table, and the warning and the run record both list the removed residues.
  • With the option False, the same file raises a ValueError when it is read, and the message names each such residue by chain, name and number.
  • When residues are removed, a chain whose first residue was removed has no residue with the N terminus, and a chain whose last residue was removed has no residue with the C terminus.
  • A file with no unknown residues gives the same features with and without the option.
  • A file in which every residue is unknown raises, whatever the option.
  • The option can be used from the CLI and from calculate().

Out of Scope

  • Adding a UNK entry, or entries for modified residues such as PCA, to the residue table. Any values there (radius, hydrophobicity, pKa) would be invented.
  • HETATM records, which are already read separately and are not affected.
  • Changing the sequence a structure is predicted from. That belongs to the code that submits the prediction.

Testing Considerations

  • A small test structure with an UNK residue at the start of a chain, in the middle of a chain and at the end of a chain.
  • A test that the conformer election runs before the check: a position modelled as one known amino acid at higher occupancy and UNK at lower occupancy is kept, and the reverse is removed.
  • The CLI argument order test in tests/test_cli.py.

Dependencies

  • None.

Estimation

Story Points: 3

Priority

Medium

Activity

  1. added a commit that references this issue on Oct 24, 2026
  2. teese commented on Oct 24, 2026

    @teese
    Author

    Merged in #38 (merge commit 9476015, branch 37-drop-unknown-residues), released as 11.0.0. Each acceptance criterion was checked against main after the merge and is covered by tests/test_unknown_residues.py and tests/test_cli.py: by default a residue outside the 20 standard amino acids is left out with a warning and listed in prodes_run.json as removed_residues; drop_unknown_residues=False (--no-drop-unknown-residues) raises a ValueError naming each by chain, name and number; a chain whose end residue was removed has no terminus at that end; a file with no unknown residue gives the same structure and atom charges either way; a file with no standard residue raises either way; the option works from the CLI and from calculate().

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions