Feat/sensitivity improvements - #44
Merged
Merged
Conversation
…ture values - peak_bounds: relocate the apex to the profile maximum when the supplied apex sits at zero height, so the boundary walks no longer collapse to a zero-width window around the wrong scan. - ms1_ms2_apex_rt_delta: bound to [0,1) via d/(d+width); a degenerate (~0) reference width could previously drive it to ~1e9. - by_ratio_agreement: distinct bounded log-odds metric (tanh), no longer a duplicate of by_ratio_consistency and no longer saturating for single-series. - cosine_robust_trim3: hold the last non-degenerate value instead of collapsing to 0.0 when trimming removes all present fragments. - add n_peak_scans / peak_window_degenerate and both_series_present indicators. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ation + index guards - fdr: (n_decoys+1)/n_targets numerator and collapse tied-score blocks to one q (was optimistic and dependent on arbitrary within-tie order). - rescore grouped_q: assign the group q only to the winning row per key; losing sibling targets no longer inherit the winner's low peptide/PG q. - rescore: precursor-level competition (CompeteGroupBy::PeptidoformCharge), experiment-wide multi-file rescore keyed on a unique row index, entrapment score readback keyed on row_id (candidate_id repeats across runs). - rt_im_train: use target-only RT-calibration anchors (decoys were admitted) and require enough anchors before trusting the residual window (avoids the ~1s overfit window with a handful of seeds). - index: fail if precursors are not ascending by precursor_mz (the fragment index's partition_point search assumes it) and warn on a decoy-free library. - report: label the count as precursor (peptidoform+charge), log a distinct stripped-sequence count. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…agments for z2 - MS2PIP charge-1 (TIC-fraction) and native charge-2 fallback intensities are max-normalized per charge group before the shared top-N ranking, so MS2PIP ions are no longer buried by the differently-scaled native charge-2 values. - charge2_from_precursor_charge default 3 -> 2: allow charge-2 fragments for charge-2 precursors (DIA-NN uses them for ~16% of z2 transitions). - config: CompeteGroupBy::PeptidoformCharge variant (precursor-level competition). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…readback, import sort - mokapot_worker: use mokapot's out-of-fold confidence estimates (targets + decoys) instead of averaging all fold models over all rows (which is not OOF). Falls back to fold-model averaging if the confidence API differs. - entrapment_worker: pass through a unique row_id so the caller maps scores back without candidate_id collisions across competed runs. - entrapment_corrected.py: exclude human decoys from the entrapment-false population (add & ~dec) at both counting sites. - import_diann_lib.py: sort precursors by m/z before assigning candidate_id so a direct target-only import is fragment-index-valid. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Fit the feature standardizer on each training fold only (was fit on the full data before splitting, leaking test-fold statistics into the scaling). - Cross-validation fold key is now base_peptide_id (was candidate_id % K), so all charge/modification variants of a peptide share a fold and none leaks between train and test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ex, dead-knob warnings - extract: emit a chromatogram row for every predicted transition, with a zero-intensity trace when the fragment was never observed. The feature families (similarity, entropy, ion-series, coelution, interference, nonzero) now see the full predicted set and penalize missing strong ions; mass-accuracy is unaffected because it already counts only fragments with obs_apex > 0. - extract: apex tiebreak on the top-K predicted signature ions' observed intensity (wires the previously-dead apex_top_fragments; 0 -> default 3) instead of the 3 brightest observed peaks, so a bright off-signature interferent cannot set the apex. - config: warn at load when a declared-but-unimplemented knob (precursor_tol_ppm, tolerance_regime, k_select, max_fragment_charge, scan_scale, decoy.source, decoy.ratio) is set away from its default, instead of silently ignoring it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… the gate - Project the sparse hit-groups onto the real acquisition scan grid (the covering isolation-window scans within the RT window) before apex counting and the co-elution run. Missing acquisition scans are now count-0 and break a run, so "consecutive scans" are truly consecutive (the sparse-group run counter previously ignored acquisition gaps, admitting transient/interferent matches). Falls back to sparse groups when no covering-window grid exists. - Compute MS1 apex isotope intensities BEFORE the fragment-Pearson gate and add an opt-in rescue (extract.ms1_rescue, default off): a candidate failing the single- scan Pearson is kept when it has adequate matched fragments and MS1 isotope support (mono present + plausible +1/mono ratio). Off by default because it relaxes acceptance; enable with FDR validation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…harness - empirical_reanalyse.py: first DIA-NN-`--reanalyse`-style pass. Learns per predicted-rank transition reliability from confident target IDs (fraction observed in-peak) and reweights or prunes the library fragments by that rank reliability. Applied symmetrically by rank to targets and decoys, so it does not by itself distort the target-decoy null; the user re-extracts with the new library. (Global-rank version; leakage-free out-of-fold learning is the P3 refinement.) - entrapment_holdout.py: honest held-out validation. Splits the human entrapment proteins into disjoint train/test halves by a stable protein hash, trains the classifier on E. coli targets vs human-TRAIN negatives, and evaluates FDR / E. coli sensitivity against the UNSEEN human-TEST null. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…th zero vector emit-all-transitions gave every never-observed predicted fragment a full grid- length zero trace, which multiplied the chromatogram list-values past arrow's 32-bit ListArray offset limit and panicked (Option::unwrap on None) on large runs (observed only after ~2 files). Absent transitions now emit an empty trace: the row is still present so the feature families see pred>0/obs=0, but the values count stays near the observed-only total. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Chromatogram rt/intensity were written as an arrow ListArray (32-bit offsets), capping total list-values at ~2.1B. When extraction accepts a very large candidate set (correlation gates opened up, or large multi-file runs), the chrom ListArray offset buffer overflows and the builder panics mid-write. Add a Col::LargeListF32 variant (arrow 64-bit LargeList) and write the chrom rt/intensity columns with it. The list_f32 reader now accepts both List (existing artifacts) and LargeList encodings, so chrom parquet from either binary reads back identically. Spectra keep 32-bit ListF32 (small per-scan peak lists, read via a direct downcast in spectra.rs). Also gitignore /_archive/ (local, superseded working docs). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tion-mode + config scaffolding Foundational, additive, behaviour-preserving building blocks for the sensitivity program (sensitivity_plan/). - mumdia-core::rejection::RejectionReason: the spec's earliest-loss reason codes (01 §4 / P0.3) with stable string codes, a pipeline-stage ordering, and an `earliest()` combinator. Unit tested. - mumdia::peaks::enumerate_peaks: non-destructive top-K chromatographic peak enumeration (local maxima + fractional-height boundaries + evidence rank), the core of "retain the correct peak instead of committing to one apex" (04 §3 / P1). Pure + unit tested with synthetic chromatograms (single peak, dominant interferent + retained true peak, truncation, prominence filter, shoulder collapse, determinism). - config: ExtractConfig.retain_top_peaks (default 1 = legacy) + validation; ExtractConfig.emit_candidate_audit (default false); CompetitionMode enum (winner_take_all default / none / features_only / unique_evidence / margin_gated) + CompeteConfig.mode/margin/unique_evidence_min_fragments/ emit_competition_audit. All serde-default so existing configs are unchanged. No default behaviour changes; K=1 and winner_take_all reproduce current output. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Non-destructive candidate-level observability. `mumdia audit` joins the artifact chain (library precursors -> psms -> competed -> scored) and writes candidate_audit.parquet with, per candidate, the stage flags (candidate_generated, traces_extracted, peak_generated, peak_selected, variant_selected, target_decoy_winner, passed_precursor_fdr, passed_peptide_fdr, reported) and the EARLIEST RejectionReason. Also writes <out>.metrics.json with the identification- loss waterfall and stage recalls. Reruns no compute and changes no pipeline output, so it is safe on any completed search. Extraction losses collapse to NO_PEAK_GROUP at artifact resolution; an optional in-extract audit sidecar (<psms>.audit.parquet) refines them per candidate when present (hook in place, emitter is future work). Verified on the E. coli/HYE run: search_space=8,334,126 extracted=341,754 reported=8,568; waterfall NO_PEAK_GROUP=7,992,372 FAILED_PRECURSOR_FDR=332,120 FAILED_PEPTIDE_FDR=1,066 REPORTED=8,568. Unit tests cover the ladder assignment and entrapment labelling. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CompeteConfig.mode drives within-group resolution via a pure, unit-tested resolve_competition(): - winner_take_all (default): unchanged legacy behaviour (keep top prelim per group; ties -> smallest index, bit-identical to the previous HashMap winner). - none / features_only: preserve every candidate (FDR/rescoring arbitrates); the sensitivity program's "preserve evidence until a calibrated decision". - unique_evidence: keep a loser with >= unique_evidence_min_fragments unique fragments (unique_fragment_count, else n_matched_fragments*(1-contested_frac), else n_matched_fragments; winner-take-all fallback when none present). - margin_gated: remove a loser only when winner_prelim - loser_prelim >= margin. Target/decoy label stays in the group key in every mode, so a target never competes its own decoy (null preserved). Optional emit_competition_audit writes <out>.compete_audit.parquet (loser, winner, scores, OUTCOMPETED_BY_* reason). ArtifactReport now records mode + removed count. 7 new unit tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…xt steps - ARCHITECTURE_MAP.md: 12-stage pipeline map with symbols/line numbers, an exhaustive "where candidates are dropped" table (~40 points -> reason codes), hook sites for each work item, and code-vs-spec differences (compete is within-label winner-take-all before rescore; target-decoy is q-values not a compete stage; one apex per candidate). - FEATURE_REGISTRY.md + feature_registry.yaml: all 360 features across 17 families with level/direction/source, a leakage audit vs spec 03 §3, and a gap table vs spec 03 §8. - IMPLEMENTATION_STATUS.md: baseline, assumptions, per-task status, tests, the candidate-audit waterfall on real data. - NEXT_STEPS.md: prioritized remaining work with exact hook sites (top-K extract wiring, in-extract audit emitter, competition-after-rescore, conflict features, new feature families, two-pass calibration, empirical-FDP acceptance loop). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When extract.emit_candidate_audit is set, the `run` orchestrator invokes the audit stage after rescore, joining the run's library/psms/competed/scored artifacts into candidate_audit.parquet + its waterfall metrics. Off by default (one cheap join pass; no effect on the production chain). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ipts Non-invasive diagnostics over existing Parquet artifacts (no engine change). - scripts/reference_apex_topk.py (P0.4 / spec 02 §5 Stage D, peak oracle): builds each candidate's consensus fragment-elution profile, enumerates peaks (port of mumdia::peaks::enumerate_peaks), and reports where the selected apex ranks among peaks (top-1/3/5/10). Optional --diann report gives reference-apex-in-top-K recall (auto minutes->seconds). Smoke (20k candidates, E. coli): mean 10.35 peaks/candidate, 95.2% have >=2 peaks; selected apex is rank-1 only 52.5% (top-3 79.2%, top-5 88.3%, top-10 94.8%, in no peak 3.5%). Strong quantitative support for top-K retention. - scripts/feature_ablation.py (P4.3 / spec 03 §4-5): grouped-CV family ablation with in-fold standardization (winsorized +/-8 SD), logreg + HistGBT, targets at empirical-FDP metric via target-decoy q. Leakage-guarded (meta/q/score excluded, group by peptidoform+charge). Smoke (60k rows, logreg): most useful families similarity/rt/entropy; on this subset removing interference/rich/coelution raises the count (single-subset, one-model caveat). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ion findings - BENCHMARK_GUIDE.md: how to run + read the candidate audit, reference-apex top-K recall, feature-family ablation, and entrapment-FDP acceptance gate, with the real CLIs and the E. coli smoke numbers, plus the end-to-end experiment recipe. - IMPLEMENTATION_STATUS.md: tasks 6-8 done; records the top-K finding (selected apex is rank-1 only 52.5%, top-5 88.3%) and the ablation leads. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ure families (P5.1/P5.3) Two additive Extended-battery families computed from the per-PSM Evidence (peak-bounded traces, apex, mass errors), interference-resistant (shape/breadth, not absolute intensity): - apex_dispersion (13): fragment-apex RT scatter (std/mad/max/mean dev + agree fraction), precursor-fragment apex delta, and consensus peak shape (symmetry, tailing, local maxima, shoulder, FWHM, truncation, apex position in window). - mass_uncertainty (10): fragment mass-error distribution (median/abs-median/std/ IQR/max/range over matched fragments), effective fragment count (inverse participation ratio), evidence concentration, and fraction of the top-3/top-5 predicted ions observed (breadth of the strong ions). Append-only; the Extended-count test is dynamic so it stays green. 8 unit tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ifest, normalizer (P0.1/P0.2/P4.2/P7.1/P7.2) Non-invasive Python diagnostics over existing artifacts (no engine change): - feature_audit.py (P4.2): per-feature missingness/quantiles/constant-flag, target/decoy/entrapment separation (Mann-Whitney), redundancy clusters (|rho|>=0.9), leakage + intensity-domination warnings, registry cross-check. - benchmark_report.py (P7.1): self-contained HTML (inlined CSS + base64 PNGs, no network) assembling waterfall, audit stratification, top-K recall, ablation, entrapment. - candidate_diagnostics.py (P7.2): per-candidate chromatogram + predicted-vs- observed plots + feature dump (pyarrow predicate pushdown, MS1 on twinx). - search_space_manifest.py (P0.1): effective search-space manifest from the library + DIA-NN/declared parity check with --fail-on-mismatch. - normalize_output.py (P0.2): MuMDIA scored -> spec 02 §4 common schema. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…_evidence_rank) Wide-window DIA fragment intensity is chimeric, so the tallest scan is often a co-isolated interferent. When extract.apex_evidence_rank = true, the apex is the scan with the most DISTINCT co-eluting predicted fragments (breadth of evidence), with observed signature-ion intensity used only as a sub-integer tiebreak; the RT prior still applies. Default false keeps the legacy signature-intensity apex bit-for-bit. The rolling distinct-fragment count still gates qualifying scans in both modes. Directly expresses the "evidence, not intensity, picks the peak" principle; validate ID effect with the entrapment gate before enabling. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
rt_im_train.adaptive_rt_window (default false) replaces the single global residual-percentile RT half-width with a LOCAL one: calibration anchors are binned by calibrated RT (adaptive_rt_bins, default 12) and each candidate gets its RT region's residual percentile, clamped to [rt_window_min_s, fallback_rt_window_s] and scaled by rt_window_multiplier. Well-calibrated regions get a tighter window (less interference), poorly-calibrated regions a wider one (more recall); empty/sparse bins fall back to the global width. Off by default (single global window unchanged). Validate ID effect with the entrapment gate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…backlog status feature_registry.yaml gains the apex_dispersion (13) and mass_uncertainty (10) families (383 features total). FEATURE_REGISTRY.md notes them; IMPLEMENTATION_STATUS.md records the full session-2 done/TODO backlog status and the acceptance-validation plan (entrapment gate on >=2 datasets before enabling any default-off knob). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ex_topk Adds a peak-ranking mode that ranks each peak by the number of distinct predicted fragments co-eluting in its RT window (breadth of evidence), not integrated intensity. Intensity is chimeric in wide-window DIA, so area-ranking rewards interferents; evidence-count ranking is interference-resistant. Enables the area-vs-count comparison for the peak-selection question and the eventual --diann reference-apex adjudication. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When extract.retain_top_peaks > 1, extraction enumerates peak groups over each candidate's per-scan distinct-fragment COUNT profile (co-eluting breadth, not intensity) via mumdia::peaks::enumerate_peaks, and writes <out-psms>.peaks.parquet (candidate_id, peak_rank, apex_rt, start_rt, end_rt, evidence_count, area), one row per retained peak. The main psms/chrom flow still reports the single selected apex, so target-decoy FDR is unaffected; the retained peaks are candidate inputs for an offline peak-selection model (reference_apex_topk.py measures the recall). K=1 (default) writes no peaks table and is byte-identical to before. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
search_seed.two_pass_mass_cal (default false): after the median-offset + 95th-percentile tolerance fit, re-fit on only the deviations inside the first-pass window (outlier rejection), giving a tighter offset + local mass-uncertainty estimate. Exports frag_ppm_sigma + cal_passes in masscal.json. Falls back to the single-pass result when too few in-window calibrants remain. Single-pass default unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ars (P2.1/2.2/2.3/P5.5/P6.2/P1.3) Non-invasive first-pass sidecars over existing artifacts (join into rescoring experiments; in-core wiring is future work): - conflict_features.py (P2.1/P2.2/P2.3 + P5.5): bounded fragment-claimant index + peak-group conflict graph -> per-candidate contested/unique fragment counts, conflict_group_size, shared_intensity_frac, and candidate-ambiguity margins (to best alt peptide / best decoy, competitor entropy) from prelim_score. - localization.py (P6.2): groups localization variants (same stripped seq + mod multiset, different sites) and computes site-determining ion count/intensity + localization_confidence. On E. coli finds 0 ambiguity groups (Carbamidomethyl fixed); validated on a synthetic phospho pair. - peak_selection_model.py (P1.3): grouped-OOF ranker over the top-K peaks table; compares learned vs evidence-count vs area ranking; DIA-NN or weak self-label. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…k-selection, conflict, localization) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… state - CLAUDE.md: pipeline lists `audit`; implementation-status table updated (audit row, top-K peaks / apex_evidence_rank / emit_candidate_audit on extract, two-pass mass cal, adaptive RT window, competition modes, 383-feature 14-family battery); new "Sensitivity program" section; what-needs-doing points at sensitivity_plan/NEXT_STEPS.md. - sensitivity_plan derived docs refreshed (spec files 01-06 untouched): ARCHITECTURE_MAP §1 no longer says top-K is unwired (it is wired: peaks.parquet, validated K=5); NEXT_STEPS reordered around closing the top-K loop + in-core wiring + P6.3 + entrapment-gate validation; BENCHMARK_GUIDE documents all 10 diagnostic scripts + --rank-by count + the TTOF-invalid note; README gains an implementation-status banner. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ract.gate_coelution) The min_frag_corr gate compared observed-vs-predicted fragment intensities at the single apex scan, which chimeric DIA interference inflates. gate_coelution=true instead thresholds min_frag_corr on the predicted-intensity-weighted mean Pearson correlation of each matched fragment's XIC to the signature-ion reference profile over the elution window (the OpenSWATH/DIA-NN co-elution discriminator): an interferent that only coincides at the apex does not co-elute with the peptide's own fragments. Default false (legacy single-scan Pearson). 3 unit tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… not the full window The first co-elution-gate cut computed the correlation over the entire (up to ~1177 s) extraction window, which is mostly zeros with a narrow peak -> noisy, unstable correlation that over-rejected real peptides (-2028 on E. coli). Now the score is computed only over the contiguous scans around the signature-reference apex above 10% of its height (the actual elution peak), where co-elution is meaningful. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…im, soft competition All additions default-off so the production chain stays byte-identical. Gate: - GateMode enum (ApexPearson default / PeakSpectral / SpectralEntropy / Coelution / Combined) replacing the gate_coelution bool; min_frag_corr thresholds the active mode. peak_window + peak_spectral_score helpers; spectral_entropy_similarity_sqrt exposed from features::entropy (shared kernel, no duplication). - emit_gate_diagnostics knob: the four gate_* diagnostic columns (and their extra per-candidate compute) only when set. Investigation found apex Pearson is the best single gate metric; peak-integrated/entropy/coelution regress at matched pool. Rescorer: - RescorerKind::NnTorch: PyTorch semi-supervised MLP sidecar (scripts/ nn_rescore_worker.py) over the shared positional-CLI PIN contract; run_mokapot refactored to run_pin_sidecar(script). doctor probes torch when selected. - Streaming memmap backend in the worker (auto >4GB PIN, or MUMDIA_NN_STREAM=1) so multi-run experiment-wide rescoring never loads the full PIN into RAM. - scripts/nn_semisupervised_rescore.ipynb: standalone PIN -> NN rescore notebook. Extraction: - extract_twopass_windows: parallelize the two-pass co-elution peak-claim across isolation windows (~4x faster, far lower memory; within-window arbitration). - --restrict-candidates allowlist for gate-first-then-compete; routes non-coelution claim strategies through the restrict-aware single-pass. - Soft competition features (default-off behind emit_contested_features): peak_contested_count_frac + peak_apportioned_frac from a Contested stats struct (won/lost intensity + counts + co-elution apportioned share). Extended 379 -> 381. 81 lib tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ound_from_confident, default on) The feature-stage elution peak that restricts trace-based feature calculation was detected per-candidate from each candidate's own top-3-predicted-fragment profile. That boundary is noisy and manipulable for chimeric decoys: their window can shrink onto a chance-aligned scan or balloon over noise (measured p10-p90 width 8.75-46.6s on HYE A_02, vs 11.6-43.9s for targets). Add features.bound_from_confident: learn a single pair of left/right elution half-widths once from the confident seed PSMs (spectrum_q <= 0.01, target-only, the same anchor set used for RT calibration / DeepLC fine-tune), at the features.bound_confident_pct percentile (default 50 = median), and apply it to every candidate around its own apex. Half-widths are asymmetric (8.71s left, 14.53s right on HYE A_02) so the real chromatographic tail is preserved. If the seed yields < 20 confident anchors the stage warns and falls back to per-candidate detection. On HYE A_02 (reverse decoys, single-seed NN rescore) this lifts 1% FDR peptides 57,991 -> 58,368 (+377) with decoy fraction unchanged at 0.99%. A percentile sweep is monotonic (pct 50 > 75 > 90): a tighter uniform window excludes more interference, so the median is the right default. Enabled by default; falls back safely and is deterministic. Also adds features.bound_peak_grace (default 0, legacy behaviour): number of consecutive sub-threshold scans to bridge when walking the per-candidate peak boundary. grace=1 was neutral (57,669) so the default stays 0. Helpers elution_peak_rt_bounds (shared per-candidate boundary logic) and global_bound_indices (map a global half-width onto the scan grid). A peak-shape plausibility feature family (native width vs the confident norm) was prototyped and dropped: hard decoys have target-identical peak widths (AUC 0.47), so the width axis cannot separate the decoys that survive to 1% FDR. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ence, q_train, auto-scaled batch) The DeepLC multitask fine-tune sidecar was invoked with positional args only, so its epochs (25), patience (10), confidence threshold (hardcoded spectrum_q <= 0.01), and batch size (512) were fixed. Two of those defaults are actively harmful: - Batch 512 underfits small confident seeds. An E.coli run with ~4k confident anchors gets ~8 gradient steps/epoch and never converges (held-out RT MAD 165s at 50 epochs, vs ~30s at batch 32). deeplc_finetune.py now takes `--batch 0` (default) which auto-scales to clamp(n_ref // 30, 16, 512) so every epoch runs >= ~30 steps regardless of seed size. - The fine-tune reference threshold was hardcoded, so rt_im_train.q_train never reached it. Added `--q-train`. Thread all four through as typed config fields on RtImTrainConfig (finetune_epochs=25, finetune_patience=10, finetune_batch=0=auto) and rt_im_train.q_train (already existed) via sidecar::run_deeplc_finetune into run.rs. Defaults reproduce the prior behaviour except the batch auto-scale, which only changes small-seed runs (previously broken). No change when finetune_deeplc is off. Also adds two diagnostic harnesses used to characterize the RT predictors: - ft_epoch_eval.py: fine-tune epoch sweep with train vs held-out RT MAD (random or q-band split), optional raw-lib baseline. Established that raw DIA-NN iRT is locally noisy (~110s MAD) and fine-tune tightens it ~8x (HYE) / ~4x (E.coli). - ft_calibrate_eval.py: DeepLC calibration vs fine-tune vs raw baseline on a common split. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ross-run quant
quant filtered candidates on peptide_q_value. That is per-run for a single-run
rescore, but under an experiment-wide rescore peptide_q_value is a GLOBAL value
carried on the single best PSM per peptide across all runs. Splitting the pooled
scored table by run and quantifying each run then keeps only the peptides whose
global-best PSM lands in that run and drops the rest, so per-run quant sets come
out disjoint and the cross-run intensity matrix is empty (0 complete-case, NaN
size factors, blank ProteoBench submission).
Add QuantQColumn { PeptideQ (default), PsmQ } and quant.q_filter. PsmQ filters on
the per-PSM q_value (per run) and is the correct choice for a precursor-level
cross-run quant such as a ProteoBench submission. Default PeptideQ preserves
existing single-run behaviour. Recorded in the quant report params.
Verified empirically: on the 6-run HYE set, PeptideQ gave 0 cross-run precursor
overlap (empty matrix); the per-run q gives ~54k overlap and a fully populated
84k-precursor submission with on-target A/B species ratios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First working match-between-runs, validated on the 6-run HYE set before build (see
mbr_plan.md). Transfers identifications across runs to boost per-run sensitivity and
fill the cross-run quant matrix, under a controlled false-transfer FDR.
Scope of this commit: the RESCUABLE tier -- precursors confident (q<=q_anchor) in
>= min_anchor_runs OTHER runs but sub-threshold in a target run where they WERE
already extracted, so no re-extraction is needed. Steps M1-M4:
- M1 anchors: per-run confident set from the experiment-wide scored table.
- M2 expected RT: predict a precursor's RT in the target run from the median of the
other runs' binned-median-aligned apex RTs. Leave-target-out residual ~17 s p95
(~15x tighter than the search window).
- M3 transfer test: accept when the observed apex sits within the learned window.
- M4 FDR: permuted-RT decoy-transfer null -> transfer q-value by target/decoy
competition on the RT residual.
Implemented as a Python sidecar (scripts/mbr_worker.py) over a positional contract,
matching the mokapot/deeplc/entrapment sidecar pattern, plus a typed config block
(MbrStrategy {None default / EmpiricalLibrary / RtTransfer / Full}, DecoyTransfer,
MbrConfig) and a `mumdia mbr` CLI command (sidecar::run_mbr). Default strategy None
is byte-identical; the command errors if strategy is None, < 2 runs, or mbr.python
is unset.
Validated on HYE (6 runs, q_transfer<=0.01): 25,278 accepted target transfers
(+5.7-9.9%/run), empirical decoy fraction 0.00%, RT window 20.2 s. Complete-case
precursors (present in all 6 runs) 33,740 -> 39,275 (+16.4%), the ProteoBench quant
metric. `mumdia mbr` reproduces the worker exactly.
Follow-ups (next commits): fold accepted transfers into the scored set + requant
(M5); the re-extraction tier for precursors absent from a run (65,805 candidates)
via extract --restrict-candidates at the transfer window; a fragment-consensus
correlation guard against RT-concordant interference; multi-run orchestration (M6).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
mbr_worker.py --out-scored writes the input scored_combined with each accepted transfer's (candidate_id, source) row q_value lowered to its transfer_q and an is_transferred flag added. A downstream quant/report with quant.q_filter=psm_q then includes the transfers, realizing the gain: on HYE, per-run confident 336,100 -> 361,378 (+25,278) and complete-case precursors (present in all 6 runs) 33,740 -> 39,275 (+16.4%), which is the ProteoBench quant metric. FDR-controlled at transfer q <= 1% (0% empirical decoy). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two additions to the MBR worker + CLI, both validated on the 6-run HYE set. Fragment-consensus guard (M4 enhancement, mbr.consensus_corr_min). The rescuable transfer alone fills the cross-run matrix (+16.4% complete-case) but DEGRADES quant precision: transferred precursors are sub-threshold because their signal is weak or interfered, and RT-concordant interference (human background at the predicted RT) compresses yeast/ecoli ratios toward 1:1 (ECOLI ratio IQR 0.72 -> 1.30, median -1.89 -> -1.74). The guard correlates each accepted transfer's fragment pattern in the target run against the empirical consensus (mean L1-normalized pattern over its confident runs) and rejects those below consensus_corr_min. At >=0.8 it keeps 14,469 of 25,278 transfers and recovers precision (ECOLI IQR back to 0.91, median -1.86; YEAST IQR 0.57 -> 0.41), so MBR becomes a completeness gain WITH preserved precision. mbr_worker.py takes --frag-csv + --consensus-corr-min; wired through sidecar::run_mbr and `mumdia mbr --frag ...` (+ --out-scored exposed). Re-extraction-tier target emission (M1 of the absent tier). mbr_worker.py --emit-transfer-targets writes per-run run_windows-format tables (candidate_id, rt_pred_cal, rt_lo, rt_hi) for the ABSENT set -- precursors confident in >= min_anchor_runs other runs but not extracted in a run (65,805 on HYE) -- at the tight cross-run-predicted RT window (+/- rt_window), plus a permuted-RT decoy-target file. These feed `extract --restrict-candidates <f> --run-windows <f>` to re-extract the absent precursors, the second sensitivity tier. Emission validated (extract-compatible schema, 65,805 targets); the extract + score + FDR loop over them is the next step. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…13/C14) - constants: ISOTOPE_SPACING to the true 13C-12C mass difference 1.003354835 (was 1.00286864, ~485 ppm off); shifts MS1 isotope-XIC m/z, so stored baselines must be regenerated - features: use the shared constants::PROTON in ms1/novel instead of local duplicate constants - fdr: entrapment_q gets tied-score-block collapse (determinism) and the +1 finite-sample pseudocount matching target_decoy_q; add validate_labels (target/decoy only), the helper used by the label whitelist - predict-frag: count and warn on DeepLC iRT misses (was a silent 0.0 anchor) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- rescore: emit run_psm_q (per-run PSM TDA, deterministic BTree split), experiment_psm_q (pooled), and precursor_q (peptidoform+charge) alongside the existing q_value/peptide_q_value/pg_q_value/global_q_value, which stay byte-identical; add the target_precursors_at_1pct stat; validate PSM labels; export the NN sidecar hyperparameters (MUMDIA_NN_FOLDS/ITERS/TRAIN_FDR) so the worker honours the config and the report records the values used - schema: bump psms_scored 1 -> 2 (additive columns) - align: validate PSM labels Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Re-committed from the working tree after intentionally resetting the branch to ca01ee5. Covers: - config/quant: QuantQColumn::RunPsmQ per-run quant filter (the correct choice for cross-run quant off an experiment-wide rescore) - config: extract.min_frag_corr default 0.5 -> 0.2 to relax the hard single-scan Pearson gate (comment.md S1), plus finite/[0,1] validation - main/convert: fold the convert peak caps into the convert config_hash and record top_peaks_ms2/ms1 in the report (comment.md A2/C4); top-peaks CLI docs - extract: record gate_mode / gate_coelution_min in the report params - search_seed: test that top_n_peaks=0 selects all and the seed cap keeps only the top-intensity peaks - main: CLI-parse tests for the peak-cap args - README: doc updates Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.