Repository navigation
Recommend repairing the structure with PDB2PQR before PROPKA and Prodes - #33
Merged
Merged
Conversation
…and Prodes Prodes describes the structure it is given and does not correct one. A residue whose side chain was not modelled carries no charge, and nothing in the output records that this happened, so every charge-derived feature is wrong without any signal to the user. PDB2PQR repairs the structure: it rebuilds missing heavy atoms, adds hydrogens, optimises the hydrogen bond network, resolves the resulting steric clashes, adds a missing OXT, and reports backbone chain breaks and gaps it cannot repair. It is BSD-3-Clause, pure Python, and available from conda-forge. Measured on PDB entry 4HKZ, a Fab with two lysines modelled only as far as CB: rebuilding those two side chains alone moves 31 of the 54 default features, takes the formal charge at pH 7.4 from -2 to 0, moves the isoelectric point from 6.46 to 7.61, and flips the sign of the mean surface electrostatic potential. For choosing between an anion and a cation exchanger at a given buffer pH, those are different recommendations. Changes: - A "Preparing your structure" section in the README covering why the step is needed, what it fixes, what Prodes keeps doing itself, and the traps. - The quick start and the worked QSPR loop now repair the structure first and run every later step, PROPKA included, on the repaired file. Running PROPKA before the repair loses the titratable groups that the repair restores: on 4HKZ, 145 groups against 149. - docs/structure_preparation.md holds the detail, including what Prodes discards from the repaired file and why. - pdb2pqr added to environment.yml, pinned, alongside propka and for the same reason. The repaired file is written into a subdirectory under its original name rather than being renamed, because the ID column of the output is taken from the file name. PROPKA writes its .pka file into the working directory rather than beside its input, which the documented commands account for. PDB2PQR can run PROPKA itself, but that does not replace the propka3 step: it writes no .pka file for the converter to read, and it fixes a single pH where Prodes deliberately separates prediction from calculation.
…outside cross-validation Two notes in the README for users building a dataset, both about failure modes that are quiet rather than loud. The pKa step prints hundreds of warnings on any structure with more than one chain, because pKa values are keyed by residue number with no chain, so every chain's predictions are offered to every chain's residue with that number and most are rejected. A user meeting this for the first time on an antibody cannot tell whether their file is wrong. The note says the warnings are expected, says what the underlying defect can actually do to a value, and points at the issue that tracks it. The second note covers feature selection. Screening the 54 columns against a measurement and then cross-validating a model built from the survivors reports a score that is not out of sample, because every held-out protein helped choose the features. At the sample sizes typical of this field, tens of proteins rather than thousands, that produces impressive numbers from data with no signal in it. The note gives the correction, which is to refit the selection on each training fold. Neither note changes any behaviour.
Prodes never imports PDB2PQR. It only reads the repaired PDB file that PDB2PQR
writes, so the two programs never have to be importable at the same time and
installing them together buys nothing. What it costs is that each one's pins
constrain the other's for as long as both are installed. They are independently
maintained projects on separate release cycles, and the first time one moves a
shared dependency the other has not caught up with, a single environment stops
solving and takes a working Prodes install down with it.
pdb2pqr is therefore removed from environment.yml, and the documented workflow
creates a separate environment for it:
conda create -n pdb2pqr -c conda-forge pdb2pqr
The quick start now activates that environment for the repair step and the Prodes
environment for everything after it. The worked loop under "Using the features in
a model" calls conda run -n pdb2pqr instead, so a script runs start to finish
without switching environments by hand.
Installing into one environment with pip is documented as the alternative for
anyone who prefers it, with the note that the PyPI package pins docutils below
0.18 while the conda-forge build does not.
Also adds the second reference that PDB2PQR asks for and prints on every run,
the 2007 Nucleic Acids Research paper describing PDB2PQR itself, alongside the
2018 Protein Science paper on the wider software suite. Both were checked against
CrossRef and PubMed: the Protein Science article is volume 27, issue 1, pages
112-128, in the January 2018 issue, published online in October 2017.
.envrc holds the per-repo GitHub token routing and is machine-local, so it was already untracked in practice but not ignored. *_notrack.py matches the existing *_notrack.md convention: a script written to answer one question, superseded as soon as the answer is recorded, and not something a reader in five years should have to work out the purpose of.
…l oxygen is scored Running PDB2PQR over 855 structures, 826 Boltz2 predicted monomers and 29 crystal structures, repaired 854 and refused 1, at a median of about one second per predicted structure. The refusal was correct: 5CHA models a residue with its backbone nitrogen and no other atom, which cannot be rebuilt from its neighbours. Prodes reads that same file without a message. Predicted structures turn out to need exactly one atom each, the C terminal OXT, which Boltz2 does not write. That is worth documenting for two reasons. Structure sources disagree about whether to write OXT at all. Over the structures measured, every AlphaFold database model has one, no Boltz2 monomer has one, and 21 of 29 crystal structures have one. So the same protein arrives with a different atom count depending on where its structure came from. Prodes assigns hydrophobicity per residue and gives every heavy atom the value of its residue, with one exception: OXT is given a fixed 1.0, which on the default mj_scaled scale is the maximum, the same as phenylalanine. A carboxylate oxygen is strongly hydrophilic, so the value is inverted. The constant has been present since the first commit and this fork preserves it along with the rest of the original algorithm. Measured on 40 Boltz2 models, adding the OXT moves a median of 9 of the 54 default features. Most of that is the ordinary effect of one more atom on the surface. Two hydrophobicity features move in one direction: NSurfPosMhp rises on 33 of the 39 structures where it moves, and SurfMhpMean rises on 12 of 13. The size of the shift is small. The problem is that it currently applies to every AlphaFold structure, no Boltz2 structure and most crystal structures, so a model trained across sources partly learns which predictor produced each file. Repairing every structure removes that, because every structure then carries exactly one OXT per chain and the artefact becomes a constant rather than a per-source difference. No code changes. The constant itself is left alone here.
The paragraph explaining where the fixed hydrophobicity value came from was longer than the fact it conveyed. One sentence naming the upstream repository says the same thing.
… it affects Prodes needs chain identifiers. It groups residues by chain, decides which cysteines are bonded into a disulfide from the chain and residue number together, and applies per-residue pKa values by residue number. PDB2PQR writes two files and they behave differently. The file written by --pdb-output, which is the one Prodes reads, always carries the chain identifier. The .pqr file, which is PDB2PQR's normal output, leaves the chain column blank unless --keep-chain is given. The cause is in io.print_biomolecule_atoms: the PDB branch calls atom.get_pdb_string(), which takes no chain flag, while the PQR branch calls atom.get_pqr_string(chainflag=chainflag). So the flag is not required for this pipeline, and that was verified rather than assumed. On structures of two, four, six and eight chains the --pdb-output file carries every chain identifier, and running with and without --keep-chain produces byte-for-byte identical PDB output. On the six chain structure 1F6R, Prodes reads the same six chains, the same 727 residues and the same 24 disulfide bonds from the repaired file as from the original. The flag is added to the documented commands anyway. The .pqr file is what APBS and the tools built on it consume, so a reader who uses it needs the chain column, and nobody should have to remember which of two output files preserves what. A new section in the README and in docs/structure_preparation.md records the difference between the two outputs, so this does not have to be rediscovered.
This branch was written against 7.1. Version 9.0 keys a predicted pKa by chain as well as residue number, which makes one note added here wrong and another understated. Removes the note warning that the pKa step prints hundreds of warnings on a multi-chain structure because predictions are keyed by residue number alone. That is no longer true, and the note sat a few lines above the section that now describes the fixed behaviour. The section on chain identifiers said Prodes applies pKa values by residue number. It looks them up by chain and residue number from 9.0, which is a stronger reason to keep chain identifiers through a structure repair, not a weaker one: a file whose chains were flattened would silently fall back to the behaviour 9.0 exists to remove. No change to the advice itself. The survey behind it still holds, because the line it measured in src/prodes/core/point.py is unchanged in 9.0.
teese
force-pushed
the
structure-preparation-with-pdb2pqr
branch
from
September 12, 2026 19:57
b6ac264 to
4fe8fed
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Prodes describes the structure it is given and does not correct one. When a residue's side chain was not modelled in the deposited file, which is ordinary for exposed charged residues at moderate resolution, that residue carries no charge, and nothing in the output records that it happened. Every charge-derived feature is then wrong with no signal to the user, and the README told users nothing about it.
No ticket. The work follows from a comparison of Prodes against PDB2PQR and APBS.
What changed
Preparing your structuresection inREADME.rst, plusdocs/structure_preparation.md.Quick startand the worked loop inUsing the features in a modelrepair first, then run PROPKA on the repaired file.environment.yml, so the two projects' pins cannot constrain each other.Results
Why the advice is needed. PDB entry 4HKZ is a Fab with two lysines modelled only as far as CB. The columns below differ only in whether those two side chains are present:
NSurfPosEpFormal)31 of the 54 default features move, and a pI of 6.46 and a pI of 7.61 point at different resins.
Whether the advice works. PDB2PQR was installed with the exact command this README gives, then run over 855 structures: 826 monomers predicted by Boltz2 and 29 crystal structures.
173 seconds of wall clock on six worker processes; a median of 1.02 seconds per predicted structure. The one failure is PDB2PQR refusing a structure it should refuse, described below.
No test was added or changed; the full existing suite of 403 tests passes in an environment built fresh from the updated
environment.yml. All pre-commit hooks pass, and every command added to the README was run end to end.Not verified
--ff=PARSEis documented without comparing the six force fields PDB2PQR ships. Only geometry reaches Prodes, so it should not matter, but that was reasoned, not measured.Property_point.set_liposcores the C terminalOXTat the maximum of the hydrophobicity scale, which is inverted for a carboxylate oxygen. The constant is documented here but deliberately not changed, because changing it moves feature values and belongs in its own release.Detail
Running PROPKA after the repair, not before
A residue whose side chain is missing has no pKa to predict, so PROPKA run on the deposited file silently omits those groups. On 4HKZ, PROPKA finds 145 titratable groups in the deposited file and 149 in the repaired one. The four extra are the two rebuilt lysine side chains and the two C-termini that only exist once
OXThas been added. Among the 145 groups both runs share, six shift by more than 0.5 pKa units.PDB2PQR does not replace PROPKA
PDB2PQR can run PROPKA internally with
--titration-state-method=propka, but it writes no.pkafile forprodes.io.pka_converterto read, and it fixes a single pH where Prodes deliberately separates prediction from calculation so that one prediction serves many pH values. Thepropka3step stays. What changes is the file it is pointed at.Why the repaired file keeps its original name
Prodes takes the
IDcolumn of the output from the file name it was given. A repaired file called1GDW_prep.pdblabels that row1GDW_prep, which is tedious to undo across a dataset, so the documented commands write the repaired file into aprepared/subdirectory under its original name. PROPKA writes its.pkafile into the working directory rather than beside its input, which the documented commands also account for.Traps documented
--ffoutrenames residues into a force field's own scheme, puttingASH,GLH,LYNandHIDinto the file, and Prodes raisesKeyErroron any of those. PDB2PQR 3.6.1 accepts a.cifbut produced an empty.pqrfrom a valid one in testing. PDB2PQR applies terminal patches at chain breaks, which Prodes discards because it re-titrates from scratch.Why a separate environment
Prodes never imports PDB2PQR; it only reads the repaired PDB file PDB2PQR writes, so the two never have to be importable at the same time. Installing them together buys nothing and costs a shared solve: two independently maintained projects on separate release cycles, and the first time one moves a shared dependency the other has not caught up with, the environment stops solving and takes a working Prodes install with it.
pip install pdb2pqrinto one environment is documented as the alternative, with the note that the PyPI package pinsdocutils<0.18while the conda-forge build does not.References
PDB2PQR asks for two citations and prints both on every run. Both were checked against CrossRef and PubMed. The Protein Science article is volume 27, issue 1, pages 112-128, January 2018 issue, published online October 2017; the DOI is
10.1002/pro.3280. The PDB2PQR paper itself is Nucleic Acids Research 35(Web Server), W522-W525 (2007), DOI10.1093/nar/gkm276.The one structure PDB2PQR refused
PDB entry 5CHA, alpha-chymotrypsin. Residue
VAL A 9is modelled with its backbone nitrogen and no other atom: noCA,C,O,CB,CG1,CG2orOXT, and the same forVAL E 9. Alpha-chymotrypsin is produced by cleaving chymotrypsinogen into three chains, and these are the ragged ends at a cleavage site. A residue with one atom cannot be rebuilt from its neighbours, so PDB2PQR stops withToo few atoms present to reconstruct or cap residue. Prodes reads the same file without a word and describes those residues as a lone nitrogen each.What the repair does to a predicted structure
Every one of the 826 Boltz2 models needed exactly one atom, and in every case it was
OXT, the second oxygen of the C terminal carboxylate, which Boltz2 does not write. No gaps, no alternate conformations, no non-standard residues. Crystal structures are the opposite: 10 of the 29 needed atoms rebuilt, up to 30 atoms across 8 residues, and 29 distinct non-standard residues appear across the two crystal sets.Structure sources disagree about whether to write
OXTat all: every AlphaFold database model sampled has one, no Boltz2 monomer has one, and 21 of 29 crystal structures have one. Prodes gives everyOXTa fixed hydrophobicity of 1.0, the maximum on the defaultmj_scaledscale. Adding the atom therefore moves a median of 9 of the 54 features on a Boltz2 model, and two hydrophobicity features move in one direction.That artefact is not created by this pull request; it already applies to every AlphaFold structure and most crystal structures. What repairing every structure does is make it uniform, so it becomes a constant rather than a difference between structure sources.
docs/structure_preparation.mdrecords this in full.Checks
README.rstparses under docutils with no warnings other than a missing Pygments package. The fourQuick startcommands were run on 1GDW, and the Python loop underUsing the features in a modelwas run over two structures, in an environment created from the updatedenvironment.yml.