Skip to content

apex: clean apex-profile weighting (bench-validated, perf-optimized)#68

Open
jspaezp wants to merge 11 commits into
mainfrom
apex-weighting-clean
Open

apex: clean apex-profile weighting (bench-validated, perf-optimized)#68
jspaezp wants to merge 11 commits into
mainfrom
apex-weighting-clean

Conversation

@jspaezp

@jspaezp jspaezp commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

Clean re-implementation of the apex-profile weighting experiments from the
apex-autoresearch branch, rebuilt off main as maintainable, tested, and
runtime-optimized code. Every term was bench-validated on the apex_sim
canonical suite; only terms that clear the agreed keep-bar ship.

What changed

Base apex profile retuned and two per-cycle weighting passes added, applied to
apex_profile before peak-picking:

  • Base: cos^0.5 * I^0.75 * (1.75 + s_norm), one gaussian-blur pass, joint-apex
    snap window 1 (was cos^3 * I, floor 0.5, no blur, snap 3).
  • Coelution weight: ratio-free expected-intensity-weighted mean pairwise
    correlation of center-normalized fragment XIC windows. Rewards genuine
    co-elution, independent of absolute fragment ratios.
  • Vote weight: robust per-row matched-filter soft votes summed across
    fragments; saturating votes let a real multi-fragment apex win over tall
    single-row interferents.

All knobs live in one ApexConfig (bench-tuned Default, no env-var globals). The
passes are pure kernels in scoring/apex/{coelution,vote}.rs over
(profile, rows, cfg, scratch), decoupled from the extraction data model and
unit-tested.

Ablation (keep-bar: SUM gain > 2.0 on the 6-scenario suite; noise = 0)

term decision SUM
base retune KEEP 267.9 -> 326.2 (+58.3)
gaussblur (1 pass) KEEP included above
joint-snap 1 KEEP +1.5 (free: existing constant, no new code)
coelution weight KEEP 326.2 -> 341.5 (+15.3)
MS1 isotope-coherence DROP +1.3 (< 2.0; regressed mismatched, and
dropping it keeps the stack
precursor-independent)
vote weight KEEP 341.5 -> 405.1 (+63.6)

Rounded exponents/strengths (0.5 / 0.75 / 1.75; coel_k 1.0, vote_k 14, tau 1.3,
s 0.4) match or beat the raw Optuna values, so no over-precise magic constants
ship. Also added an absent_precursor bench scenario as a permanent guard
against over-reliance on precursor signal.

Runtime

The passes run per-candidate on rayon workers. Cost recovered from 91 to 51
us/run, result-identical (SUM unchanged at each step):

  • vote median/MAD: 3 full sorts/row -> select_nth (O(n)). 91 -> 59
  • coelution: pairwise O(rows^2) -> ||W||^2 closed form (O(rows))
    • skip zero-profile cycles. 59 -> 51
  • per-worker scratch: no per-candidate allocation. parity

vs apex-autoresearch branch

branch (isotope on, unoptimized): SUM 494.7 @ 91.8 us/run
this PR (isotope dropped, opt): SUM 491.6 @ 51.2 us/run

-0.7% SUM for +44% throughput, a simpler precursor-independent stack, 3 fewer
knobs, and unit tests. The 3.4-point delta is entirely the dropped isotope term.

Validation

  • cargo test -p timsseek: 94 passed (4 new apex unit tests).
  • cargo build -p timsseek --release, cargo build -p apex_sim --release: clean.
  • No env/LazyLock knobs remain (grep clean).
  • Bench: bash bench_out/score.sh (apex_sim canonical suite, synthetic).

jspaezp added 10 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.
@github-actions

github-actions Bot commented Jul 19, 2026

Copy link
Copy Markdown

Apex-finder bench

cargo run -p apex_sim --release --example bench -- 1000 2

Sensitivity + timing across canonical scenarios
=== clean ===
  config: n_cycles=245 sigma=1 noise_floor=0.02 random_peaks=200 true_apex=30
  runs=1000 tol=±2 cycles
  build :    21.81 ms total  (21.81 us/run)
  score :    69.53 ms total  (69.53 us/run, 14382 runs/s)
  scorer errors:    0
  pass1 sensitivity: 100.00%  (1000/1000)
  pass2 sensitivity: 100.00%  (1000/1000)
  pass2 |apex-truth| cycles: mean=0.00 median=0 max=0
  pass2 err histogram: 0:1000 1:0 2:0 3:0 4:0 5:0 6:0 7+:0 

=== moderate_noise ===
  config: n_cycles=245 sigma=1 noise_floor=0.2 random_peaks=50 true_apex=30
  runs=1000 tol=±2 cycles
  build :    71.15 ms total  (71.15 us/run)
  score :    69.99 ms total  (69.99 us/run, 14287 runs/s)
  scorer errors:    0
  pass1 sensitivity:  99.90%  (999/1000)
  pass2 sensitivity:  99.80%  (998/1000)
  pass2 |apex-truth| cycles: mean=0.64 median=1 max=41
  pass2 err histogram: 0:451 1:503 2:44 3:1 4:0 5:0 6:0 7+:1 

=== high_noise+interference ===
  config: n_cycles=245 sigma=1 noise_floor=0.5 random_peaks=100 true_apex=30
  runs=1000 tol=±2 cycles
  build :   124.98 ms total  (124.98 us/run)
  score :    70.92 ms total  (70.92 us/run, 14100 runs/s)
  scorer errors:    0
  pass1 sensitivity:  86.50%  (865/1000)
  pass2 sensitivity:  86.40%  (864/1000)
  pass2 |apex-truth| cycles: mean=13.81 median=1 max=213
  pass2 err histogram: 0:316 1:495 2:53 3:4 4:1 5:1 6:3 7+:127 

=== heavy_interference ===
  config: n_cycles=245 sigma=1 noise_floor=0.5 random_peaks=300 true_apex=30
  runs=1000 tol=±2 cycles
  build :   346.15 ms total  (346.15 us/run)
  score :    70.84 ms total  (70.84 us/run, 14116 runs/s)
  scorer errors:    0
  pass1 sensitivity:   4.00%  (40/1000)
  pass2 sensitivity:   4.00%  (40/1000)
  pass2 |apex-truth| cycles: mean=92.09 median=87 max=214
  pass2 err histogram: 0:15 1:14 2:11 3:6 4:8 5:12 6:8 7+:926 

=== mismatched_library ===
  config: n_cycles=245 sigma=1 noise_floor=0.5 random_peaks=100 true_apex=30
  runs=1000 tol=±2 cycles
  build :   125.42 ms total  (125.42 us/run)
  score :    71.07 ms total  (71.07 us/run, 14072 runs/s)
  scorer errors:    0
  pass1 sensitivity:  88.80%  (888/1000)
  pass2 sensitivity:  88.80%  (888/1000)
  pass2 |apex-truth| cycles: mean=11.29 median=1 max=211
  pass2 err histogram: 0:317 1:519 2:52 3:1 4:0 5:1 6:2 7+:108 

=== absent_top_fragment ===
  config: n_cycles=245 sigma=1 noise_floor=0.5 random_peaks=100 true_apex=30
  runs=1000 tol=±2 cycles
  build :   123.33 ms total  (123.33 us/run)
  score :    69.65 ms total  (69.65 us/run, 14357 runs/s)
  scorer errors:    0
  pass1 sensitivity:  26.30%  (263/1000)
  pass2 sensitivity:  26.30%  (263/1000)
  pass2 |apex-truth| cycles: mean=70.83 median=49 max=213
  pass2 err histogram: 0:108 1:124 2:31 3:4 4:5 5:5 6:3 7+:720 

=== absent_precursor ===
  config: n_cycles=245 sigma=1 noise_floor=0.5 random_peaks=100 true_apex=30
  runs=1000 tol=±2 cycles
  build :   122.92 ms total  (122.92 us/run)
  score :    69.37 ms total  (69.37 us/run, 14414 runs/s)
  scorer errors:    0
  pass1 sensitivity:  86.50%  (865/1000)
  pass2 sensitivity:  86.40%  (864/1000)
  pass2 |apex-truth| cycles: mean=13.82 median=1 max=213
  pass2 err histogram: 0:306 1:504 2:54 3:4 4:1 5:1 6:3 7+:127 

=== summary (n=1000, tol=±2 cycles) ===
  scenario                    pass2%  pass1%   medErr    us/run
  clean                        100.0   100.0        0     69.53
  moderate_noise                99.8    99.9        1     69.99
  high_noise+interference       86.4    86.5        1     70.92
  heavy_interference             4.0     4.0       87     70.84
  mismatched_library            88.8    88.8        1     71.07
  absent_top_fragment           26.3    26.3       49     69.65
  absent_precursor              86.4    86.5        1     69.37

commit 3130a97

…_loop)

Result-identical; SUM 491.6 unchanged.
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