Repository navigation
Replace the hardcoded OXT hydrophobicity with a value derived from the selected scale #34
Description
User Story
As someone building a dataset of Prodes features from structures that came from more than one source
I want the C-terminal OXT atom scored with a hydrophobicity appropriate to a carboxylate oxygen
So that the surface hydrophobicity features describe the protein rather than which program wrote the file
Description
Why this change matters:
- Problem: structure sources disagree about whether to write the C-terminal
OXTatom, and Prodes gives that atom an anomalous hydrophobicity. A dataset assembled from more than one source therefore carries a systematic difference between its sources that has nothing to do with the proteins. - Current behaviour:
Property_point.set_lipoinsrc/prodes/core/point.pygives every atom namedOXTa hydrophobicity of exactly1.0. Every other heavy atom takes the value of the residue it belongs to. On the defaultmj_scaledscale,1.0is the maximum, the same value as phenylalanine. - Desired behaviour:
OXTis scored with a value appropriate to a carboxylate oxygen, which is strongly hydrophilic, and that value is derived from the selected scale rather than hardcoded.
Measured over 855 structures assembled for an ion-exchange retention study, which source writes an OXT:
| source | structures with OXT |
|---|---|
| AlphaFold database | 200 of 200 sampled |
| Boltz2 predicted monomers | 0 of 200 sampled |
| crystal structures | 21 of 29 |
So the anomalous value is currently applied to every AlphaFold structure, no Boltz2 structure, and most but not all crystal structures.
This is a hydrophobicity problem, not a charge problem, so ion exchange work is largely unaffected. OXT carries no charge in Prodes: Atom.charge assigns charge only to the side-chain atoms listed for a residue type and to the backbone atom named N or C at a terminus, and OXT is neither. Checked directly: across five test structures, all four OXT atoms return a charge of zero at pH 7.4. Measured on 40 Boltz2 structures, adding an OXT left Formal charge and Isoelectric point unchanged on all 40. The electrostatic surface features move very slightly, by a median of 0.03 per cent on NSurfPosEpFormal, but that is the ordinary consequence of one more heavy atom in the solvent-accessible surface and would happen whatever hydrophobicity the atom carried.
The hydrophobicity features do move, and in one direction: NSurfPosMhp rises on 33 of the 39 structures where it moves at all, and SurfMhpMean rises on 12 of the 13.
Technical Context
The line is src/prodes/core/point.py:
contributions = np.array([1.0 if a.name == "OXT" else scale_dict[a.residue_name] for a in atoms_masked])Three things are worth knowing before changing it:
- The lookup the special case bypasses would work. An
OXTatom carries the residue name of its own residue, which is always present in the scale, so the special case is not preventing aKeyError. - The constant is not scale-aware. It is
1.0whatever--hydrois set to, so it is the maximum onmj_scaled, roughly mid-range onkdwhere the maximum is 4.5, and something else again oncw. - The behaviour is inherited from the original
tneijenhuis/prodesrepository and this fork preserves it. Changing it breaks output compatibility with the original for any structure that has anOXT, which is most of them.
Implementation Suggestion
Two candidates, and the choice should be made deliberately rather than by whichever is easier:
- Remove the special case, so
OXTtakes its residue's value like every other heavy atom of that residue. Minimal, internally consistent with how the rest of the function works, and scale-aware for free. - Give
OXTan explicit low value derived from the selected scale, for example the scale's minimum or the value of the most hydrophilic residue on it. More chemically defensible for a carboxylate oxygen, but it introduces an atom-level concept into a residue-level function.
Whichever is chosen, the value must come from the scale rather than be a literal, so that --hydro continues to mean something.
Acceptance Criteria
OXTno longer receives a hardcoded value, and the value it does receive is derived from the selected hydrophobicity scale.- Running Prodes on the same structure with
--hydro mj_scaled,--hydro kdand--hydro cwgivesOXTa value on each scale that sits at the hydrophilic end of that scale. - The change is released under a major version bump, and the README's
Output compatibilitysection records that hydrophobicity feature values move for any structure containing anOXT. docs/structure_preparation.md, which currently documents the present behaviour, is updated to describe the new behaviour.- The size of the change to each affected feature is measured on a set of structures and recorded, in the way the existing analyses under
docs/are.
Out of Scope
- Changing how hydrophobicity is assigned to any atom other than
OXT. - Moving from residue-level to atom-level hydrophobicity generally. That is a larger question and would supersede this ticket rather than be part of it.
- Any change to charge assignment or to the electrostatic features.
- Adding
OXTto structures that lack one. That is the job of the structure preparation step already documented in the README.
Testing Considerations
- A unit test that a C-terminal
OXTreceives a hydrophilic value, on each of the shipped scales, so that a future hardcoded constant cannot reappear unnoticed. - The existing regression test will fail and that is expected.
tests/test_sasa.pycompares this fork's output againsttests/data/ARH96693_prodes_orig_output.csv, which was generated by the original code. That structure contains oneOXT, onSER 60of chain A, so its hydrophobicity columns will move. Decide whether to regenerate the reference file or to exclude the hydrophobicity columns from that comparison, and say which in the commit. - A test on a structure with more than one chain, which has one
OXTper chain rather than one per structure.
Dependencies
- None blocking. The change is self-contained within
Property_point.set_lipo. - It should land before any dataset is regenerated with the structure preparation step now documented in the README, because that step adds an
OXTto every structure that lacks one and so applies the current behaviour uniformly.
Resources
- Pull request that documents the current behaviour, and the survey the measurements come from: Recommend repairing the structure with PDB2PQR before PROPKA and Prodes #33
docs/structure_preparation.md, section "What happens to a predicted structure: the terminal oxygen"- Original repository the behaviour is inherited from: https://github.com/tneijenhuis/prodes
Estimation
Story Points: 3
Priority
Medium
Nothing is broken for ion exchange elution prediction, which is charge-driven. It matters for hydrophobicity-driven work and for any dataset that mixes structure sources.
Activity