Skip to content

feat: windowed mu_h (Eisenberg window=11) + anionic guard — AUROC 0.8047→0.8348 - #70

Merged
cschanhniem merged 2 commits into
mainfrom
feat/scoring-improvements
Jun 28, 2026
Merged

cschanhniem merged 2 commits into
mainfrom
feat/scoring-improvements

Conversation

@cschanhniem

Copy link
Copy Markdown
Collaborator

Summary

Two scoring improvements that raise the retrospective AUROC from 0.8047 → 0.8348 (pipeline) and 0.7846 → 0.8126 (phase3 synthesis gate) before wet-lab synthesis.

1. Windowed Hydrophobic Moment (PR #70a)

For sequences > 11 residues, the full-sequence μH is diluted by non-helical termini and linker regions. The Eisenberg (1984) standard uses a window of 11 residues to capture the most amphipathic helical segment.

  • Added max_windowed_hydrophobic_moment(sequence, window=11) to features/physchem.py
  • Added max_hydrophobic_moment key to compute_features() output
  • activity_likeness_score() now uses max(hydrophobic_moment, max_hydrophobic_moment)
  • Short sequences (≤ 11 AA) are unaffected: windowed == full-seq by construction
  • Improvements for known AMPs: magainin-2 1.51×, cecropin-A 2.90×

2. Anionic Charge Guard (PR #70b)

Anionic peptides (charge_density_ph74 < 0.0) are electrostatically repelled by bacterial membranes (phosphatidylglycerol, cardiolipin) and cannot operate via the canonical cationic AMP mechanism. Previously, EEIEIEIEIEIEIEE received 0.42 — now correctly 0.0.

  • Returns 0.0 immediately when charge_density_ph74 < 0.0
  • Boundary: exactly 0.0 is NOT blocked (neutral, not anionic)
  • Fixed a Python eager-evaluation bug: dict.get(key, features["fallback"]) evaluates the fallback argument before calling .get(), raising KeyError when charge_density is absent. Replaced with explicit if "charge_density_ph74" in features conditional.

Results

Benchmark Before PR #70 After PR #70
pipeline.yaml AUROC 0.8047 0.8348
phase3.yaml AUROC (synthesis gate) 0.7846 0.8126
Tests passing 1200 1223 (+23 new)

Sentinel updates

  • Regression sentinel: >= 0.79 (was >= 0.75)
  • Config-identity sentinel: < 0.82 — sits between phase3=0.8126 and pipeline=0.8348, so a silent config fallback is detectable

Test plan

  • 1223 tests passing (uv run pytest tests/ -q)
  • TestMaxWindowedHydrophobicMoment — 8 tests: short seq == full, long seq >= full, magainin-2 >1.2× improvement, single residue, custom window, nonneg
  • TestComputeFeaturesMaxMuH — 5 tests: key present, type, nonneg, max >= full, short seq == full
  • TestAnionicGuard — 6 tests: anionic → 0, ph74 field priority, polyglutamate → 0, zero not blocked, small negative blocked, positive not blocked
  • TestMaxWindowedMuHInActivity — 3 tests: max used when higher, backward compat, missing both keys
  • Retrospective sentinel: phase3.yaml AUROC=0.8126 >= 0.79 ✓, < 0.82 ✓
  • METHODS.md formula updated with μH_eff = max(hydrophobic_moment, max_hydrophobic_moment) and anionic guard note
  • DISCOVERY_PREDICTION.md AUROC values updated to 0.8348 / 0.8126
  • CHANGELOG.md updated for PR feat: windowed mu_h (Eisenberg window=11) + anionic guard — AUROC 0.8047→0.8348 #70

🤖 Generated with Claude Code

Two scoring improvements that raise the retrospective AUROC meaningfully
before synthesis:

1. Windowed hydrophobic moment (window=11, Eisenberg standard):
   - `max_windowed_hydrophobic_moment()` added to physchem.py
   - `max_hydrophobic_moment` feature added to compute_features() output
   - activity_likeness_score() uses max(mu_h, max_mu_h), so long-sequence
     AMPs get credit for their best helical segment (magainin-2: 1.51×,
     cecropin-A: 2.90× improvement vs full-seq)
   - Short sequences (≤11 AA) are unaffected: windowed == full-seq

2. Anionic charge guard:
   - charge_density_ph74 < 0.0 returns 0.0 immediately
   - Prevents anionic peptides (electrostatically repelled by bacterial
     membranes) receiving spurious partial credit
   - Corrects EEIEIEIEIEIEIEE getting 0.42 (should be 0.0)
   - Fixed Python eager-eval bug: dict.get(key, features["fallback"])
     evaluates the fallback even when key is present; replaced with
     explicit `if key in features` conditional

AUROC: pipeline 0.8047→0.8348, phase3 0.7846→0.8126
Regression sentinel: ≥0.79 (was ≥0.75)
Config-identity sentinel: <0.82 (sits between phase3=0.8126, pipeline=0.8348)
Tests: 1223 passing (+23 new for windowed mu_h and anionic guard)
Docs: METHODS.md formula and AUROC table updated; DISCOVERY_PREDICTION.md
      and CHANGELOG.md updated
…_h ordering

Code review (PR #70) found two MEDIUM issues:

1. max_windowed_hydrophobic_moment is NOT guaranteed >= hydrophobic_moment.
   For non-amphipathic/uniform sequences longer than the window, the fixed-
   window value (normalised by window=11) can be LESS than the full-sequence
   average (normalised by len). The scorer handles this correctly via
   max(hydrophobic_moment, max_hydrophobic_moment), but the docstring and
   test comment implied the wrong invariant.

   - Corrected docstring to document the [0, ∞) range and the non-invariant
   - Renamed test to _for_known_amps and added note
   - Added test_windowed_can_be_less_than_full_for_uniform_sequences as an
     explicit counterexample (12-A uniform sequence)

2. Test comment erroneously stated the windowed value is <= 1.0. The Eisenberg
   scale can produce values > 1.0 for tightly packed hydrophobic windows;
   clamping happens in the scorer (clamp01). Removed the incorrect upper-bound
   claim from the comment.

Also fixed LOW: stale weight coefficient in amphipathicity bonus comment
(0.15 → 0.14, matching activity.py line 79).
@cschanhniem
cschanhniem merged commit 9e74b9e into main Jun 28, 2026
@cschanhniem
cschanhniem deleted the feat/scoring-improvements branch June 28, 2026 12:51
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