Skip to content

apex: apex_sim bench + unify finder + cull weighting, re-tune base profile#70

Merged
jspaezp merged 19 commits into
mainfrom
apex-unify
Jul 19, 2026
Merged

apex: apex_sim bench + unify finder + cull weighting, re-tune base profile#70
jspaezp merged 19 commits into
mainfrom
apex-unify

Conversation

@jspaezp

@jspaezp jspaezp commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

Net change vs main. History is noisy (it builds an apex-profile weighting
experiment and then removes it) -- squash on merge.

What this does

  1. apex_sim test-harness (new). Synthetic DIA extractions fed to the real
    TraceScorer. Broad (1695-cyc, production Phase-1) and narrow (150-cyc,
    Phase-3) recovery suites, interferent density that scales with the
    window (0.41 peaks/cyc anchor from a real run), sub-cycle per-seed apex
    jitter, and a ROC-AUC bench separating a real peptide's score from a
    pure-noise twin. Window/width/density anchors derived from a real
    250225 HeLa run.

  2. Dead code removed. score_for_viewer path, ViewerResult, full_results.rs,
    build_broad_extraction, find_joint_apex + its tests. Stale docs fixed
    (apex_profile C(t) was documented cos^3*I).

  3. Apex finder unified. Pass-3 score_at now scores at Pass-1's apex cycle
    (effective_apex = cycle) instead of relocating via find_joint_apex on a
    separate profile. One apex source.

  4. Apex-profile weighting: added, then culled. An intermediate experiment
    added ratio-free coelution + cross-row vote weighting to apex_profile.
    It tripled apex scoring cost (canonical 18->51 us/run, broad
    112->320 us/run) for recovery gains the sim barely discriminates, and
    the sim cannot reproduce the imperfect co-elution the passes were partly
    meant to handle. Net vs main: the weighting is GONE. apex_profile is the
    base composite profile only; runtime is back at the pre-experiment
    baseline.

  5. Base profile re-tuned standalone. With the weighting removed, the base
    knobs were re-tuned (Optuna over canonical+broad+narrow recovery,
    rounded to robust non-degenerate values): cos_pow 0.5->0.25,
    i_exp 0.75->1.0, s_ratio 1.75->0.25, blur_passes 1->2. +1.5% summed
    recovery across all three window sizes at no runtime cost.

Runtime

              before feature   with weighting   this PR

canonical us 18 51 18
broad us 112 320 110
narrow us 12 33 12

Validation

  • cargo build -p timsseek: clean, no new warnings.
  • cargo test -p timsseek --lib: 88 passed.
  • cargo test -p apex_sim: 11 passed.
  • apex_sim bench (n=4000/800): summed pass2 recovery 907 -> 921 with the
    tuned base knobs; per-suite canonical/broad/narrow all up, none regressed.

Review notes (fable), open for decision before merge

  • MEDIUM. Locator/scorer profile split. apex_profile (locator, cos^0.25)
    picks the apex cycle; score_at then reads the 11 features off
    cosine_profile (scorer, cos^3) AT that cycle. On non-co-eluting real data
    the two peaks can sit 1-3 cycles apart, so features get sampled off the
    true peak and deflate a genuine peptide's score. base_score is safe (it
    re-localizes independently); only the feature multipliers ride the
    locator apex. The apex_sim harness co-elutes everything at one cycle, so
    it is structurally blind to this -- "unify is bench-neutral" is asserted,
    not tested. Fix options: re-localize features in a small +/-window on
    cosine_profile around the locator apex, or add a non-co-eluting sim
    scenario (fragment apex offset from precursor/interference apex).

  • LOW. Post-unify, pass1% and pass2% recovery columns are mathematically
    identical (Pass 2 no longer relocates). Collapse or relabel.

  • LOW. run_discrimination's noise twin zeroes the expected (library)
    precursor intensity too, not just the observed data, making the AUC
    slightly easier than the real signal-vs-noise decision. Fragments are
    handled correctly (ratios preserved).

  • The tuned constants are sim-derived and provisional; cos_pow=0.25 sits
    near the search-range floor. A real-data check (bench/wandb_bench.py) is
    the right gate before trusting the +1.5%.

Supersedes

Folds in the content of #68 (apex weighting -- now culled) and #69
(apex_sim realism). Both can be closed.

jspaezp added 19 commits July 18, 2026 17:26
…blur pass, joint-snap 1)

Bench-validated: SUM 267.9 -> 326.2 (+58.3) on apex_sim canonical suite.
Rounded exponents (0.5/0.75/1.75) match or beat the Optuna values.
Per-cycle expected-intensity-weighted mean pairwise correlation of
center-normalized fragment XIC windows. Runtime cost (18->44 us/run)
addressed in a later perf pass.
…, +63.6)

Robust per-row matched-filter soft votes summed across fragments;
saturating votes make a genuine multi-fragment apex win over tall
single-row interferents. Runtime cost (44->91 us/run) addressed in perf pass.
MS1 precursor undetected, fragments intact. Guards against
over-reliance on precursor signal in the apex profile.
Replaces 3 full sorts/row with partial selection. Result-identical
(same order statistic); SUM 491.3 unchanged on canonical suite.
… 59->51 us/run)

Weighted mean pairwise correlation of unit window vectors has a closed
form (sum_{a<b} w_a w_b <u_a,u_b> = (||W||^2 - sum w_a^2)/2), collapsing
the per-cycle O(rows^2) inner loop to O(rows). Zero-profile cycles skip
the window work entirely. Result-identical: SUM 491.3 unchanged.
…date alloc)

Moves cn/active/wacc and support/r/m/tmp into an ApexScratch owned by
TraceScorer, cleared+resized per call. Matches the TraceScorerBuffers
idiom; removes allocator churn across rayon workers at scale.
Result-identical: SUM 491.3 unchanged.
Moves coelution + vote into scoring/apex/{coelution,vote}.rs as pure
kernels over (profile, rows, cfg, scratch), decoupled from the Extraction
data model. ApexConfig/ApexScratch live in apex/mod.rs; apex_finding calls
a single weight_profile orchestrator. Adds 4 unit tests (co-elution vs
scattered, concurrent votes vs tall interferent, disabled no-ops).
Bench parity: SUM 491.3, 51 us/run unchanged.
…, s 0.4)

Rounded from raw Optuna values; SUM 491.3 -> 491.6, no regression.
…_loop)

Result-identical; SUM 491.6 unchanged.
Fixed count in a wide window understates interferent density and makes
apex-finding artificially easy. density_per_cycle > 0 overrides count as
round(density * n_cycles * hardness); defaults keep count-based behavior.
apex_jitter=Some(range) draws realized apex = apex_cycle + U(-range,range)
+ U(-0.5,0.5) per seed (off-grid, moving target). None keeps the historical
on-grid fixed apex byte-identical. run_sensitivity scores each run against
its realized apex.
roc_auc (average-rank Mann-Whitney) + run_discrimination: matched pairs
share a seed; the absent twin zeros BOTH fragments and precursor (true
no-signal), so identical noise+interferents isolate the real peak's effect
on the Pass-2 score. Measures the score as a signal/noise separator.
…bles

Density-scaled interferents + jittered sub-cycle apex at production window
sizes. Example prints canonical/broad/narrow recovery tables plus a narrow
score-discrimination AUC table. Canonical header kept as '=== summary' so
score.sh still parses it.
…0/245)

2.5/cyc (mis-derived from the 40-cyc viewer example) drowned every noisy
scenario at realistic window sizes. 0.41/cyc matches the canonical suite's
interferent density, isolating window size as the changed variable: broad
high-noise recovery 86%(245cyc) -> 54%(1695cyc); narrow AUC 1.0 clean ->
0.68 heavy. hardness=3 remains as an explicit stressor.
Delete score_for_viewer (no callers; viewer uses find_apex directly),
its ViewerResult type, and now-orphaned build_broad_extraction. Fix stale
docs: apex_profile C(t) formula (cos^0.5*I^0.75 not cos^3*I), drop
'Migration aid'/'replaces LocalizationBuffer' notes. No behavior change.
Pass 2 scored at effective_apex = find_joint_apex(cosine_profile) (the
UN-weighted cos^3*I, global argmax, snapped to Pass 1). It relocated onto
interferents in narrow windows. Now Pass 2 trusts Pass 1's weighted
apex_profile location -- one apex finder, one C(t). Removes find_joint_apex
+ its tests, and the joint_snap/relocate_apex_via_joint knobs.

Bench-neutral: canonical SUM 492.0->492.1; narrow recovery + AUC unchanged
on apex_sim (perfectly co-eluting sim can't show the real-window drift the
change prevents).
The coelution and cross-row vote weighting passes tripled apex scoring
cost (canonical 18->51 us/run, broad 112->320 us/run) while, on the
apex_sim suites, buying recovery that the sim itself barely discriminates
-- and the sim does not reproduce the imperfect co-elution these passes
were partly meant to handle. Remove both passes (coelution.rs, vote.rs,
weight_profile, ApexScratch, the coel_/vote_ knobs); apex_profile is now
the base composite profile only. Runtime returns to the pre-feature
baseline.

With the weighting gone, the base-profile knobs were re-tuned standalone
(Optuna over the canonical+broad+narrow recovery suites, rounded to
robust non-degenerate values): cos_pow 0.5->0.25, i_exp 0.75->1.0,
s_ratio 1.75->0.25, blur_passes 1->2. +1.5% summed recovery across all
three window sizes at no runtime cost.

Tuning is sim-derived and provisional pending a real-data check.
@jspaezp
jspaezp changed the base branch from apex-sim-realism to main July 19, 2026 03:53
@jspaezp jspaezp changed the title scoring: unify apex finder + remove dead code (tiers 1-2) apex: apex_sim bench + unify finder + cull weighting, re-tune base profile Jul 19, 2026
@jspaezp
jspaezp merged commit 0478db4 into main Jul 19, 2026
1 check passed
@jspaezp
jspaezp deleted the apex-unify branch July 19, 2026 03:59
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