fix(align): a soft-clipped variant of a pair is not a second locus - #254
Open
BenjaminDEMAILLE wants to merge 1 commit into
Open
fix(align): a soft-clipped variant of a pair is not a second locus#254BenjaminDEMAILLE wants to merge 1 commit into
BenjaminDEMAILLE wants to merge 1 commit into
Conversation
#31 reported NH up to 20 where STAR caps at 14 on the same data. The extra loci are not different places in the genome: they are the same alignments with the leading exon soft-clipped off, scoring one point lower, kept as separate multimapper entries. STAR removes them in stitchWindowAligns.cpp, where a transcript whose blocks are a subset of another's and which scores lower is dropped. The equivalent check here is gated on the two transcripts having the same number of exons, and these variants have one fewer, so they slip past the very comparison meant to catch them. The rule is applied to whole pairs, after the score-range filter: a pair whose mate1 and mate2 are both covered by a better-scoring pair is dropped. Equal scores keep both, matching STAR's strict comparison. Measured, nf-core/rnaseq test data (50 000 pairs, --outFilterMultimapNmax 20): deepest NH STAR 14 before 20 after 14 uniquely mapped STAR 41691 before 41665 after 41684 multi-mapped STAR 934 before 961 after 942 and on the project's yeast tier (10 000 pairs of ERR12389696), no regression: uniquely mapped STAR 7861 before 7860 after 7860 same chr/pos/CIGAR before 98.4825% after 98.4825% same NH before 99.9644% after 99.9763% Three earlier attempts at this regressed the yeast tier and are not in this diff: applying the rule inside the stitcher, and applying it to single transcripts either before or after the quality filters. All three cost ~39 uniquely mapped reads, because a subset alignment is sometimes the only one that survives the score and overhang gates, and because align_read is reused for the paired mate-overlap merge with those gates disabled. Closes #31.
This was referenced Aug 27, 2026
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.
Closes #31.
What the extra loci were
Not different places in the genome. The reported NH=20 read carries 24 records at
AS:i:174and 16 atAS:i:173, and the 173s are the same alignments with the leading 9-base exon soft-clipped off:STAR produces only the 174s.
The rule, and why it was not firing
stitchWindowAligns.cppdrops a transcript whose blocks are a subset of another's and which scores lower:The equivalent check here (
stitch.rs:2385) is gated onwt.exons.len() == existing.exons.len(). These variants have one exon fewer, so they slip past the comparison meant to catch them.This PR applies the rule to whole pairs, after the score-range filter: a pair whose mate1 and mate2 are both covered by a better-scoring pair is dropped. Equal scores keep both, matching STAR's strict
<.Measured
nf-core/rnaseq test data, 50 000 pairs,
--outFilterMultimapNmax 20:The whole NH histogram now matches STAR's shape at the tail (
…7:4 9:2 12:4 14:2on both sides).Project's yeast tier, 10 000 pairs of ERR12389696 (via
test/yeast_tier.shin #253):So: the fixture that showed the bug moves toward STAR on three figures, and the faithfulness benchmark does not move except for a small NH improvement.
Three attempts that are not in this diff
Worth recording, because each looked right and each cost about 39 uniquely mapped reads on the yeast tier:
same_structuregate in the stitcher. Faithfulness 98.48% → 97.98%, 92 mates lost.The reason is the same in all three: a subset alignment is sometimes the only one that survives the score and overhang gates, and
align_readis reused by the paired-end mate-overlap merge with those gates disabled, so pruning there removes candidates the pairing stage would have used. Operating on assembled pairs avoids both.I also mis-measured once along the way: an earlier edit to
stitch.rsfollowed me across a branch switch and sat in three consecutive "clean" runs, which is why they returned suspiciously identical numbers. The figures above are from a tree whose only change is this one, checked withgit diff --stat.Tests
Four unit tests on the new rule: the reported case, the same case with the subset arriving first (order must not decide), two genuinely distinct loci at different scores (both kept), and an equal-scoring subset (kept, as STAR keeps it).
cargo test,cargo clippy --all-targets -- -D warningson a cold cache, andcargo fmt --checkare green.🤖 Generated with Claude Code