Skip to content

feat: Trp-weighted aromatic bonus + safety abs() fix + scoring accuracy - #65

Merged
cschanhniem merged 2 commits into
mainfrom
feat/pre-wetlab-polish-7
Jun 28, 2026
Merged

cschanhniem merged 2 commits into
mainfrom
feat/pre-wetlab-polish-7

Conversation

@cschanhniem

Copy link
Copy Markdown
Collaborator

Summary

Deep scientific review identified three scoring issues and two documentation accuracy problems.

Scoring Fixes

Trp-weighted aromatic bonus (activity.py): Trp is now weighted 1.5× vs Phe/Tyr in the aromatic_bonus calculation. Justification: Trp's indole ring preferentially anchors at the lipid-water interface (Wimley-White W=−1.85 kcal/mol vs Eisenberg W=+0.81), a mechanism distinct from and more membrane-favorable than Phe's hydrophobic stacking. Previously, indolicidin-class (5 Trp) scored the same as an equivalent Phe-rich sequence, which is scientifically incorrect. SEED-008 (puroindoline-a) benefits from this fix.

Safety abs() bug fix (safety.py): Removed abs() from the charge_density hemolysis check. Negatively charged peptides are electrostatically repelled from both bacterial and mammalian membranes — they cannot cause hemolysis. The comment claimed "charge magnitude drives hemolysis risk" which is wrong: only positive charge density > 0.55 drives hemolysis. The old code would incorrectly penalize a negatively charged peptide's safety score.

import math style fix (boman.py): Moved to module top level from inside boman_activity_score().

Eisenberg scale comment fix (physchem.py): Removed false claim that values are "shifted to 0..1 range." The values are raw signed (R=−2.53 to I=+1.38). The signed values are required for the hydrophobic moment calculation. A future developer reading "shifted to 0..1" might "fix" this by renormalizing and break μH.

N/C terminus docstring (physchem.py): Documented that terminal charges (−0.20 net/peptide) are excluded from net_charge_at_ph74, a known convention.

Benchmark Impact

  • pipeline.yaml AUROC: 0.8164 → 0.8086 (within bootstrap CI [0.71-0.90])
  • phase3.yaml AUROC: 0.7936 → 0.7890 (within CI; still STRONG > 0.70)
  • The minor decrease is expected: random-background decoys containing Trp also benefit from the bonus — unavoidable with a composition-based scorer that cannot distinguish "AMP-strategic Trp" from "background Trp"

Synthesis Pool Update

Regenerated make phase3 with updated scoring: SEED-006: 11→10, SEED-009: 18→19. Same 6 scaffold families, 100 total candidates.

Test plan

  • 1185 tests passing
  • test_negative_charge_density_no_hemolysis_penalty — verifies corrected safety behavior
  • test_phase3_benchmark_passes_gate — AUROC=0.7890 passes ≥ 0.75 regression sentinel
  • make validate-scoring-phase3 → AUROC=0.7890 STRONG
  • make validate-scoring → AUROC=0.8086 STRONG
  • make phase3 regenerated pool with 100 candidates across 6 families

🤖 Generated with Claude Code

…racy

Scoring changes (PR #65):
- activity.py: Trp weighted 1.5× vs Phe/Tyr in aromatic_bonus; indole ring
  anchors at lipid-water interface (Wimley-White W=-1.85 kcal/mol) — uniquely
  membrane-favorable vs Phe's hydrophobic stacking. Differentiates puroindoline-
  a/indolicidin class from Phe-rich peptides.
- safety.py: remove abs() from charge_density — negatively charged peptides are
  electrostatically repelled from membranes and pose no hemolysis risk; only
  positive charge_density > 0.55 should trigger the penalty
- boman.py: move `import math` from inside function to module top level
- physchem.py: fix Eisenberg scale comment (values are signed [-2.53..+1.38],
  NOT shifted to [0,1]; the signed values are required for μH calculation)
- physchem.py: document N/C terminus charge omission in net_charge_at_ph74

Benchmark impact:
- pipeline.yaml AUROC: 0.8164 → 0.8086 (within bootstrap CI; caused by random-
  background decoys containing Trp benefiting from the bonus — scientific trade-off)
- phase3.yaml AUROC: 0.7936 → 0.7890; both still STRONG (> 0.70 gate)

Synthesis pool update:
- SEED-006: 11 → 10 selected; SEED-009: 18 → 19 selected (pool regenerated
  from make phase3 with updated scoring); same 6 scaffold families, 100 total

Tests: 1185 passing; safety test updated to assert correct behavior (negative
charge should not trigger hemolysis penalty)
- TestTrpWeightedAromaticBonus: 6 new tests covering the residue_counts code path
  - Trp-rich outscore Phe-rich at same aromatic fraction
  - 1.5x multiplier is quantitatively verified (delta ≈ 0.025 at 10% aromatic)
  - Ceiling still 0.10 regardless of how high Trp fraction goes
  - No residue_counts fallback produces same result as all-Phe (correct)
  - Mixed Trp/Phe scores between all-W and all-F
  - Documents saturation behaviour: bonus only differentiates below ~13% Trp
- Update test_scoring_activity.py docstring to show Trp-weighted formula
- Fix stale |cd| comment in test_scoring_safety.py (abs() removed)
@cschanhniem
cschanhniem merged commit a949310 into main Jun 28, 2026
1 check passed
@cschanhniem
cschanhniem deleted the feat/pre-wetlab-polish-7 branch June 28, 2026 11:15
cschanhniem added a commit that referenced this pull request Jun 28, 2026
* chore: update ROADMAP.md with current metrics, add coverage gate to make coverage

- AUROC 0.8164→0.8420 (bootstrap CI₉₅: 0.76–0.91) in v0.4 milestone entry
- Add v0.6.x section documenting PRs #61–#72 scoring improvements
- Mark AUPRC gap as Done (PR #58, pipeline AUPRC=0.8627)
- Update large-scale benchmark gap row with corrected n=87 and CI
- coverage target: add --cov-fail-under=80 so make coverage exits non-zero
  below 80%; currently passes at 87.40%

* docs: fix v0.6.x chain gap (add PR #65) and AUPRC attribution
cschanhniem added a commit that referenced this pull request Jun 28, 2026
…ocstring (#95)

- presynth_check.py: His pKa corrected 6.0→6.5 (Bjellqvist 1993) to match
  physchem.py — divergent pI/charge estimates for His-rich peptides are now
  consistent across both modules
- METHODS.md: remove stale claim that safety.py falls back to abs(charge_density);
  the abs() was removed in PR #65 — anionic peptides now correctly receive
  safety=1.0 (no hemolysis concern from electrostatically repelled sequences)
- ensemble.py: add docstring to ensemble_score() documenting default and
  phase3 config weights so they are discoverable without reading YAML files
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.

1 participant