fix(delta): decoy displacement must exceed the event; add the missing BND decoy - #508
Merged
Merged
Conversation
… BND decoy
Two defects in the negative control, found while cross-checking job 20824321.
1. THE DECOY SHIFT DID NOT DISPLACE LARGE EVENTS. It moved every record a flat
2 Mb. Truvari matches sized variants on reciprocal overlap, so a 19.6 Mb
deletion shifted 2 Mb still overlaps 90% — and matching it is CORRECT
behaviour. Measured: decoy_scoreable recall=0.202, TP=17, and ALL 17 were
larger than the shift (min 2,048,336 bp against a 2,000,000 bp shift, median
4.3 Mb, max 19.6 Mb). Not one match was smaller than the displacement.
So the control was reporting 'the shift is too small' while its warning text
claimed 'the scorer is matching far more loosely than intended' — it
misdiagnosed its own failure, which is worse than being silent.
Displacement is now max(2 Mb, 2 x |SVLEN|), so it always exceeds the event and
overlap is zero by construction. END moves with POS, or the record would
describe a span it no longer occupies. Warning text corrected to say what a
match would now mean.
Verified on hand-computed cases:
500 kb DEL -> shift 2 Mb (floor) POS 1000000 -> 3000000, span kept
19.6 Mb DEL -> shift 39.24 Mb POS 5000000 -> 44239140, span kept
2.05 Mb DUP -> shift 4.10 Mb POS 3000000 -> 7096672, span kept
BND (no SVLEN) -> shift 2 Mb POS 9000000 -> 11000000, END==POS
Shift exceeds span in every case, so no shifted interval overlaps its original.
2. decoy_truvari WAS NEVER CALLED FOR BND. It ran once, for 'scoreable'. So the
headline manta_BND=0.935 had a positive control (selftest_BND=1.000 across all
62 records) and NO negative control — the half that distinguishes detection
from coincidence. --bnddist sets a proximity window and nothing measured how
often a displaced breakend still lands inside it.
BND is where the decoy works best: a breakend is a point (END==POS), so any
shift displaces it completely.
Neither changes a caller result. Both change whether the caller results are
believable.
Co-Authored-By: Claude <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.
Two defects in the negative control, found while cross-checking job 20824321. Neither changes a caller result; both change whether the caller results are believable.
1. The decoy did not displace large events
It shifted every record a flat 2 Mb. Truvari matches sized variants on reciprocal overlap, so a 19.6 Mb deletion moved 2 Mb still overlaps ~90% — and matching it is correct behaviour.
Measured:
decoy_scoreable recall=0.202, TP=17, and all 17 were larger than the shift:Not one match was smaller than the displacement. The control was reporting "the shift is too small" while its warning text claimed "the scorer is matching far more loosely than intended" — it misdiagnosed its own failure, which is worse than staying silent, because it points the reader at the wrong subsystem.
Displacement is now
max(2 Mb, 2 × |SVLEN|), so it always exceeds the event and overlap is zero by construction.ENDmoves withPOS, or the record would describe a span it no longer occupies.Verified on hand-computed cases — shift exceeds span in every one, so no shifted interval overlaps its original:
END == POS2.
decoy_truvariwas never called for BNDIt ran once, for
scoreable. So the headlinemanta_BND = 0.935had:selftest_BND = 1.000, all 62 recordsThat missing half is what distinguishes detection from coincidence.
--bnddistsets a proximity window, and nothing measured how often a displaced breakend still lands inside it.BND is also where the decoy works best: a breakend is a point (
END == POS), so any shift displaces it completely — no size-scaling needed.