Skip to content

Stop adapter readthrough from repeating the adapter (#865) - #866

Merged
joshfactorial merged 1 commit into
developfrom
fix/adapter_readthrough_repeat
Oct 30, 2026
Merged

joshfactorial merged 1 commit into
developfrom
fix/adapter_readthrough_repeat

Conversation

@joshfactorial

Copy link
Copy Markdown
Collaborator

Closes #865. Same defect as ncsa/NEAT#344; this ports ncsa/NEAT#345.

Problem

append_adapter_readthrough filled a short-insert read's tail with adapter[i % adapter.len()]. A tail longer than the 33 bp TruSeq or 19 bp Nextera stem looped back into a second copy of it. On the new integration fixture (150 bp reads, ~40 bp inserts) with the cyclic code put back, 2527 of 2630 reads carried the stem more than once. No molecule has that sequence.

Adapters are off by default, so default runs were never affected.

Change

  • Full-construct presets. Each preset is now stem, then a placeholder index, then P7 (read 1) or P5 (read 2):
    • TruSeq uses single-index adapter 1, ATCACG.
    • Nextera uses N701 and S501. S501 is read in read 2 as GCGATCTA.
    • The sequences are identical to NEAT#345. A test checks that each preset ends in the reverse complement of the P7 or P5 oligo, computed in the test rather than copied from the constants.
  • New adapters.overrun key, polyg (default) or random. It sets what fills a read past the end of the construct.
    • Fill bases take quality scores and are soft-clipped.
    • Fill bases take no substitution errors.
    • Unknown values are rejected with an error that names the key.
  • Custom adapters are used as given, and fill starts where they end.
  • Docs: the docs-site adapter page is updated. The template's stale claim that adapter readthrough is "outside the scope of this simulator" now points to the adapters block.

Semver: a new config key means a MINOR bump. Seeded adapter runs whose tails pass the stem will produce different sequence, which is the fix. Reads whose tail ends inside the stem keep the same sequence and rng draws; the unit test asserts polyg and random agree there.

Evidence

Unit tests (fastq_tools.rs):

  • A 5 bp insert in a 5000 bp read gives the 12 bp adapter, then 4983 G with zero non-G.
  • random fill comes out within 0.22–0.28 for each base, and does not reproduce the adapter.
  • Must-not-fire: a tail that ends inside the adapter has no fill.

Integration test (tests/adapter_readthrough_sequence.rs, H1N1, 30x, 2×150, inserts 40 ± 5):

  • No read carries the stem twice.
  • More than 90% of reads contain the stem, so the first check is not vacuous.
  • Every read ends in 20 G under polyg.
  • Must-not-fire: no read ends in 20 G under random.

Mutation checks (each confirmed applied; fastq_tools.rs restored and verified with cmp):

mutant result
restore i % adapter.len() 2 unit and 2 integration tests fail (2527/2630 reads repeat the stem)
apply substitution errors to poly-G fill unit and integration fail (491/2630 reads lose the G tail)
random returns G both random tests fail

The workspace suite, clippy -D warnings and fmt all pass locally.

Not verified

  • No trimmer has been run on the new output. fastp's --trim_poly_g and cutadapt are the obvious checks.
  • The Add optional sequencing-adapter simulation to gen-reads output #125 adapter validation, which found adapters callable and their effect within noise, was measured on the old cyclic sequence and has not been re-run. The docs page now says so.
  • Realism-panel runs with ADAPTERS=truseq should have unchanged clip lengths, because the aligner clips everything past the insert either way. That is reasoning, not a measurement.

🤖 Generated with Claude Code

append_adapter_readthrough sourced adapter bases as
adapter[i % adapter.len()], so any read whose tail was longer than the
adapter looped back into a second copy of it. Same defect as
ncsa/NEAT#344.

Presets are now the whole construct a read runs through: stem, a
placeholder index, then P7 (read 1) or P5 (read 2), same sequences as
NEAT#345. Past the construct the read is filled per the new
adapters.overrun key: polyg (default, two-color no-signal) or random
(four-color). Fill bases take quality scores and soft clips but no
substitution errors. Custom adapters are used as given.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@joshfactorial
joshfactorial merged commit 162d816 into develop Oct 30, 2026
9 checks passed
@joshfactorial
joshfactorial deleted the fix/adapter_readthrough_repeat branch October 30, 2026 13:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant