Conversation
trimBySequence's insertion and deletion loops step pos through the read but passed rdata (not rdata + pos) to Matcher::matchWithOneInsertion, so every iteration compared the start of the read with the adapter. An adapter with a one-base indel was only ever found at position 0. Pass rdata + pos, as the exact-match loop above already does. Adds AdapterTrimmer::test cases with a TruSeq adapter at position 40 carrying a one-base insertion and a one-base deletion; both fail without this change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The one-gap adapter search calls matchWithOneInsertion at every read position for every read without an exact adapter match (most reads), and it was the largest single function in worker profiles. It built full prefix and suffix mismatch arrays and then scanned them, including an O(cmplen) fill of sentinels on every call. The new version scans the prefix forward and the suffix backward and stops each scan as soon as it can no longer be part of a match within diffLimit. It then checks only the splits both scans reached. On unrelated sequence both scans stop after a few bases. Results are identical to the previous implementation, which is kept as matchWithOneInsertionReference (used for cmplen > 512 and by the new Matcher::test, which compares the two on 200k random, near-match and exact-match cases). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The one-gap loops compared the read start at every position, so once the compared length got short a read beginning with adapter-like sequence matched and had its tail cut (upstream cuts this 81 bp test read to 72 bp). Co-Authored-By: Claude Opus 5.5 <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.
Fixes #728.
trimBySequence's one-gap search compared the adapter with the start of the read at every position, instead of at the position being tested. So it missed adapters with a one-base indel, and it trimmed the tail of reads that only start like the adapter (~1.8% of reads on a public WGBS run). Details and a 5-read reproduction are in #728.Changes
rdata + posto bothMatcher::matchWithOneInsertioncalls inAdapterTrimmer::trimBySequence, the same offset the exact-match loop already uses.Matcher::matchWithOneInsertion.perfprofile of SRR10007843).diffLimit, then checks only the splits both scans reached. On unrelated sequence both stop within a few bases.matchWithOneInsertionReference. It's used forcmplen > 512and by the newMatcher::test.AdapterTrimmer::testgains a TruSeq adapter at position 40 with a one-base insertion and a one-base deletion (both fail on master), and a read that starts like the adapter (master cuts it from 81 to 72 bp).Matcher::testcompares the new and referencematchWithOneInsertionon 200k random, near-match and exact cases.Verification
fastp testpasses. Each new test fails without the change it covers.Matcher::testcatches deliberate off-by-one mutations of the new function (4 of 4 tried).Performance
Complete public runs,
fastp -w 16(PE with--detect_adapter_for_pe), GCE n2d-highmem-48, inputs in page cache. Mean of 2 runs in alternating build order.A separate benchmark on GitHub-hosted 4-CPU runners, which projects 300K–500K-pair subsets to a 50M-read run (synthetic PE/SE 150 bp and SRR891268 ATAC PE 50,
-w 1and-w 4), gave:Peak RSS is unchanged.
So the fix makes trimming correct at roughly the old cost. It isn't a speedup, since the search now does the work it was meant to do.
matchWithOneInsertionis still the largest worker function after this change. A bit-parallel or SIMD version of the one-gap search could take it further, but would change results in edge cases, so it's left out of this PR.Output changes users will see
🤖 Generated with Claude Code