Skip to content

fix(mbr): pad 3D second-pass traces for quantification; keep pad points outside the f32 window bound - #231

Merged
RobbinBouwmeester merged 8 commits into
mainfrom
quant/f4-trace-pad-3d
Oct 28, 2026
Merged

RobbinBouwmeester merged 8 commits into
mainfrom
quant/f4-trace-pad-3d

Conversation

@RobbinBouwmeester

Copy link
Copy Markdown
Member

Summary

Two related fixes to the second-pass trace pad. This changes default output and therefore needs an explicit decision against the 3D byte-identity rule, which is why it is a draft.

  1. 3D second passes are padded for quantification (43a544b). run_experiment requested TracePad::for_quant only when the spectra carry 1/K0. On 3D data the narrow second-pass RT window then truncated the fixed integration window: on ProteoBench Astral 2-35% of condition A and 73-94% of condition B quantities were integrated over fewer than 7 samples. The pad now applies to every second pass with extract.emit_rt_window, as it already did for 4D.
  2. Pad points stay outside the f32 window bound (16b7cb6). features::clip_rows cuts a padded trace back with chromatograms::window_span, which compares in f32, while extract decides in f64. A pad point just outside a bound whose RT rounds onto the f32 bound was read back as inside, so it leaked into a few feature rows (Astral 38 of 814,524). This leak already exists on main for 4D second passes (28-55 rows per run on diaPASEF). A pad point that would round inside is now stored one f32 step outside.

Measured

  • 3D (Astral seed 0, AIF seeds 0 and 2): features, psms_competed, scored_combined and every per-run scored.parquet are byte-identical to main. Only second-pass chromatograms and quantities change. Clipped integrations go to 0. ProteoBench Astral species-weighted |eps| improves by 0.011-0.019 with better CV and human SD on every seed (removal of a uniform condition offset, not an accuracy gain in the narrow sense); AIF, Low Input and diaPASEF quantification are unchanged within noise.
  • 4D (diaPASEF, one seed): the boundary fix removes main's leak, so second-pass features and identifications change by seed-level amounts (peptides -4, precursors -17, protein groups -17). Not yet measured with seeds and entrapment.

Before merging

  • Decide on the 3D byte-identity break (3D second-pass quantities and chromatograms change on purpose; CI smoke passes, its expectations do not cover these columns).
  • diaPASEF seeds and an entrapment run for the 4D identification change, or split 16b7cb6 out if 4D should wait.

🤖 Generated with Claude Code

RobbinBouwmeester and others added 5 commits October 25, 2026 17:13
The second pass of match-between-runs applied the quantification trace
pad (extract::TracePad::for_quant) only to runs whose spectra carry
1/K0, and printed the missing-pad warning only there. On 3D data the
narrow second-pass RT window clipped quant's fixed integration window
of an apex near its edge (ProteoBench Astral, 0.8.0: 2-29% of the A and
90-91% of the B quantities over fewer than 7 samples).

The pad now applies to every second-pass run, under a fixed quant
integration window and extract.emit_rt_window, with the warning when
the latter is off. This changes the 3D second-pass chromatograms and
quantities on purpose and breaks the 3D byte-identity of those two
artifacts; psms_extracted, features and the scored tables are those of
the unpadded table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The features stage cuts a padded chromatogram table back to the RT window in f32
(chromatograms::window_span), while extract decides in f64 which hits are inside.
A pad point just outside rt_lo or rt_hi whose RT rounds onto the f32 bound was
read back as inside the window, so a few feature rows differed from the
unpadded extraction (ProteoBench Astral second pass: 38 of 814,524 rows over
six runs), and the classifier then saw different training data. Such a point is
now stored one f32 step outside the bound. Without a pad nothing changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RobbinBouwmeester and others added 3 commits October 28, 2026 13:49
…238)

The documentation that #229 added describes the quantification trace pad as
4D-only; with this branch it applies to every second pass, so those passages
are rewritten in the past tense and the duplicated pad bullet is dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@RobbinBouwmeester

Copy link
Copy Markdown
Member Author

diaPASEF validation of the boundary fix (16b7cb6): base a836630 against 6bf15dc, NN seeds 0-2. The decision rule was written before the seed 1, seed 2 and entrapment numbers were looked at.

The fix removes the 4D leak on every seed: 23-55 second-pass feature rows per run differed on ProteoBench diaPASEF HYE (about 144,000 rows per run), 0-7 on the E. coli entrapment experiments. The first pass, the second-pass library, psms_extracted and run_windows stay byte-identical.

  • HYE 3 + 3, seed means, branch minus base: peptides +2 (seed spread 337), precursors +10 (410), protein groups -9 (84), per-run precursors on run_psm_q +100 (331). PSM decoy fraction 0.0099 in every arm. Units: experiment-wide peptide_q_value, precursor_q, pg_q_value; per run on run_psm_q.
  • Quantification (ProteoBench 0.18.6 offline, min_obs 3): ions +95 (spread 337), global median |eps| +0.0001, CV +0.0001. Species-weighted |eps| +0.0014 / +0.0017 / +0.0023 (base 0.2017, branch 0.2035, base spread 0.0015), from E. coli (+0.0050). This follows from the reshuffled second-pass identifications, not from changed quantities: of about 99,900 ions per seed quantified in the same runs in both arms, at most 4 change, by under 4e-5 relative. The branch accepts slightly more weak second-pass E. coli precursors in condition A. Three seeds do not decide whether this is systematic.
  • Entrapment: the existing E. coli diaPASEF entrapment is a single run without a second pass, so it cannot exercise this change. Three E. coli diaPASEF replicates with a 1:1 human entrapment proteome were therefore run as one experiment with the default second pass. Measured precursor FDP at precursor_q 1%: 0.614% (base) against 0.613% (branch), seed means, base spread 0.013 pp; peptide level 0.758% against 0.759%; real precursors 18,056 against 18,046 (spread 106). A control with three copies of one run gives 0.330% against 0.338%. Limit: the leak is rare on these runs (0-7 rows per run), so entrapment tests the change at low exposure; a matched-FDP count is not informative because both arms stay below 1% over the whole list.

Conclusion: no change in 4D sensitivity or FDR beyond seed noise. The one consistent movement is a +0.0018 species-weighted |eps| on diaPASEF from the reshuffled second-pass identifications.

The branch is now merged with main (after #229, #230, #232, #238). The docs that #229 added described the trace pad as 4D-only; those passages and the quant.fixed_scan_halfwidth reference are updated. cargo fmt, clippy -D warnings and cargo test --workspace pass (0 failures). Marking ready for review; what remains is the decision to accept the deliberate change of 3D second-pass quantities.

🤖 Generated with Claude Code

@RobbinBouwmeester
RobbinBouwmeester marked this pull request as ready for review October 28, 2026 17:21
@RobbinBouwmeester
RobbinBouwmeester merged commit b1ecb51 into main Oct 28, 2026
12 checks passed
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