Use Rust FDR kernels in perform_fdr with exact tie-break handling - #841
Use Rust FDR kernels in perform_fdr with exact tie-break handling#841GeorgWa wants to merge 3 commits into
Conversation
Wires the alphadia_search_rs FDR kernels into alphadia/fdr/fdr.py: - get_q_values and keep_best delegate to the Rust kernels when available. - perform_fdr routes through the fused sort-free fdr_finalize whenever no fragment competition is needed (the common path), computing keep-best and q-values in a single counting pass. The fragment competition path retains the exact (now parallel-sorted) chain. Guarded by availability and ALPHADIA_RUST_FDR (set to 0 to force the reference pandas implementation for A/B benchmarking). On a 13M-row batch this cuts FDR finalization from ~39s to ~1.2s (~32x); survivors match exactly and q-values agree with the pandas reference to ~3e-5. Adds parity tests comparing the Rust path against the pandas reference. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
perform_protein_fdr calls get_q_values with extra_sort_columns=["pg"], the protein-group accession (a string). The Rust q-value path coerced that column to int64, raising ValueError during protein FDR. Take the Rust path only when the tie-break column is integer-typed; otherwise fall back to the reference pandas sort (string-safe). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Rust q-value kernel takes an integer tie-break key, so the previous guard sniffed the column's dtype and fell back to pandas for protein FDR, which breaks ties on the protein group accession. Inferring the code path from a dtype means a future integer accession id would silently switch numerics. Factorizing with sort=True numbers the unique values in sorted order, so ordering by the codes reproduces ordering by the original values and the kernel stays exact for any sortable dtype. Missing values are mapped to the largest key so they sort last, as pandas sorts them. Only the kernel's single-key limit still routes multi-column tie-breaks to the reference path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Context: why there is a tie-break at all, and an open question about removing itDraft for now — see the status note at the bottom. The tie-break
Hence the three sort keys: score for the ranking, decoy ascending so targets precede decoys within a tie, and Ties are not hypothetical. Classifier probabilities saturate to exactly 0.0 / 1.0 for confident predictions, and in protein FDR the features ( What this PR doesThe Rust kernel takes the tie-break as The requirement here is stronger than determinism: the pandas implementation remains a live path (always for fragment competition, and everywhere when the kernels are unavailable), so the two must agree numerically or the same input yields different q-values depending on whether the extension is installed. Open question: one q-value per equal-score blockAssigning every row in an equal-score block the FDR evaluated at the block's end removes the tie-break entirely — no tie-break column, no factorize, no dtype question, and It is also more defensible. Within a tie block the current convention places every target ahead of every decoy, so the leading targets see
Bit-exact where there are no ties. On data with no signal at all and dense ties, targets-first reports 3,906 IDs at 0.1% FDR where the truth is 0 — those are manufactured by the sort convention. Blocks would be grouped by exact float equality, deliberately: the ties we care about come from bit-identical classifier outputs, not from near-equality. Two scores differing by one ULP are genuinely different scores and belong in different blocks, so Not in this PR because it is a behaviour change, not a refactor: reported IDs will drop wherever ties are dense, the numbers above are synthetic distributions rather than alphaDIA data, and both implementations would have to change in the same commit. Wants an e2e comparison first. StatusDraft until MannLabs/alphadia-search-rs#130 ships the kernels in a released Replaces #820, which was stacked on the unreviewed |
Uses the Rust FDR kernels for the hot spots in
perform_fdr, keeping the pandas implementation as the reference path:fdr_q_values/fdr_keep_bestreplace the pandas multi-key sort + cumsum + groupby inget_q_valuesandkeep_best.fdr_finalizereplaces thesort -> get_q_values -> keep_best -> get_q_valueschain with a single counting pass, used when fragment competition is not required.ALPHADIA_RUST_FDR=0forces the reference path for A/B benchmarking, and it is also used automatically when the kernels are unavailable.get_q_valuesneeds an integer tie-break key for the kernel. Precursor FDR breaks ties onprecursor_idx, but protein FDR breaks ties onpg, a string accession. Rather than branching on the column's dtype,_integer_tiebreakfactorizes non-integer columns withsort=True, which numbers the unique values in sorted order, so ordering by the codes reproduces ordering by the original values and the kernel stays exact for any sortable dtype. Missing values map to the largest key so they sort last, as pandas sorts them.Requires MannLabs/alphadia-search-rs#130. Until those kernels ship in a released
alphadia-search-rs,_RUST_FDR_AVAILABLEis False, the reference path runs, andtest_fdr_rust_parity.pyskips.Replaces #820, which was stacked on an unreviewed branch; this branch is cut from a clean
main.🤖 Generated with Claude Code