Skip to content

Recover anchors from filtered hits after chaining - #657

Open
ksahlin wants to merge 1 commit into
mainfrom
anchor-recovery-rework
Open

ksahlin wants to merge 1 commit into
mainfrom
anchor-recovery-rework

Conversation

@ksahlin

@ksahlin ksahlin commented Oct 14, 2026

Copy link
Copy Markdown
Owner

This is a rework of the anchor recovery in #622 (Nicolas Buchin) with
the same output, about half the code and a faster implementation.

Hits whose hash is too frequent are filtered before chaining. After
chaining, they are now looked up again, restricted to diagonal bands
around the extracted chains, and the best run of their anchors is
inserted into each gap of each chain (and before and after it). As in
#622, chains are extracted down to 0.5 times the best score, at most 200
per orientation (new option --mc).

The output is byte-identical to #622 (rebased on v0.19.0) in
mapping-only and in extension mode on 29 simulated datasets (chrY,
CHM13, maize B73 and Mo17, rye Lo7 and Lo7_V3, fruit fly; 50 and 150 nt,
5, 10 and 20 kb). The implementation differs:

  • Recovery is a child module of the chainer (src/chainer/recovery.rs),
    so chain.rs and chainer.rs do not move: 6 files change (+558/-2)
    instead of 15 (+1054/-19).
  • Filtered hits that share index entries (a repeat in the read) scan
    them once. A bitmap of the reference bins near the chains rejects most
    entries, and runs of entries before the next window are skipped.
  • The gap DP skips predecessors at the same reference position (they
    cannot link) and those whose score cannot be improved, using the score
    without the log2 term as an exact upper bound.
  • Buffers are reused between the chains of a read.

Total mapping time relative to v0.19.0, #622 -> this change (Apple M4
Max, -x, 1 thread, median of 3 runs; 5, 10 and 20 kb reads):

  • chrY: 2.66-1.49x -> 1.88-1.30x
  • CHM13: 3.57-2.62x -> 1.70-1.50x
  • maize B73: 3.10-2.29x -> 1.75-1.42x
  • maize Mo17: 4.20-3.49x -> 3.01-2.55x
  • rye Lo7: 3.93-3.21x -> 1.77-1.58x
  • rye Lo7_V3: 8.60-5.84x -> 4.73-4.12x
  • fruit fly: 1.18-1.08x -> 1.13-1.06x
  • 50 and 150 nt reads: 7.10-2.06x -> 4.11-1.61x

Short reads remain expensive: recovery runs on up to 200 chains per
orientation (about 30 per 150 nt read). That cost comes from the chain
extraction settings, which this change keeps as in #622.

This is a rework of the anchor recovery in #622 (Nicolas Buchin) with
the same output, about half the code and a faster implementation.

Hits whose hash is too frequent are filtered before chaining. After
chaining, they are now looked up again, restricted to diagonal bands
around the extracted chains, and the best run of their anchors is
inserted into each gap of each chain (and before and after it). As in
#622, chains are extracted down to 0.5 times the best score, at most 200
per orientation (new option --mc).

The output is byte-identical to #622 (rebased on v0.19.0) in
mapping-only and in extension mode on 29 simulated datasets (chrY,
CHM13, maize B73 and Mo17, rye Lo7 and Lo7_V3, fruit fly; 50 and 150 nt,
5, 10 and 20 kb). The implementation differs:

- Recovery is a child module of the chainer (src/chainer/recovery.rs),
  so chain.rs and chainer.rs do not move: 6 files change (+558/-2)
  instead of 15 (+1054/-19).
- Filtered hits that share index entries (a repeat in the read) scan
  them once. A bitmap of the reference bins near the chains rejects most
  entries, and runs of entries before the next window are skipped.
- The gap DP skips predecessors at the same reference position (they
  cannot link) and those whose score cannot be improved, using the score
  without the log2 term as an exact upper bound.
- Buffers are reused between the chains of a read.

Total mapping time relative to v0.19.0, #622 -> this change (Apple M4
Max, -x, 1 thread, median of 3 runs; 5, 10 and 20 kb reads):

- chrY: 2.66-1.49x -> 1.88-1.30x
- CHM13: 3.57-2.62x -> 1.70-1.50x
- maize B73: 3.10-2.29x -> 1.75-1.42x
- maize Mo17: 4.20-3.49x -> 3.01-2.55x
- rye Lo7: 3.93-3.21x -> 1.77-1.58x
- rye Lo7_V3: 8.60-5.84x -> 4.73-4.12x
- fruit fly: 1.18-1.08x -> 1.13-1.06x
- 50 and 150 nt reads: 7.10-2.06x -> 4.11-1.61x

Short reads remain expensive: recovery runs on up to 200 chains per
orientation (about 30 per 150 nt read). That cost comes from the chain
extraction settings, which this change keeps as in #622.

Is-new-baseline: yes
@ksahlin
ksahlin force-pushed the anchor-recovery-rework branch from e350805 to bfe1f80 Compare October 14, 2026 05:07
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