Add a mapping-only mode to the labeled aligner

Merged
AD
14h ago
+180-0

Stacked on #690. Review only the top commit, "Add a mapping-only mode to the labeled aligner"; this PR will be retargeted to master after #690 merges.

What

LabeledAligner::map_batch:

  • finds and label-filters seeds exactly as alignment does (build_seeders / filter_seeds, including min_exact_match), but does not extend them;
  • reports, per query, the labels whose seeds cover the most query characters on the better strand, plus the number of covered characters and the query range they span (on the forward query).

This supports read accept/reject decisions (e.g. adaptive sampling) that need the label, not a base-level alignment.

The CLI option metagraph align --align-map-only is opt-in and off by default. It requires -a and supports TSV output only, one line per query:

header  strand  labels  covered_chars  query_begin  query_end      (unmapped: header * * 0 * *)

Nothing changes in the default align path.

Test

LabeledAlignerTest.MapWithoutExtension, typed over all 7 graph/annotation pairs, covers:

  • full coverage by one label;
  • a query split between two labels, where the larger coverage wins;
  • an unmapped query.

7/7 pass locally (assertion-enabled build, filtered run only).

Numbers (benchmark harness bench/align-harness, mode map; 24 bacterial genomes, k = 31, CANONICAL, labeled, seed 19)

Single-threaded, per read, alignment vs. map_batch on the same reads. The aligned and negative-accepted fractions are identical by construction (same seed filter).

readstop-1 label correct (align / map)p50 ms (align / map)p99 ms (align / map)
Illumina-like 150 bp.922 / .9213.0 / 1.95.8 / 2.7
ONT 400 bp, 3%.952 / .9458.0 / 3.520.0 / 5.5
ONT 400 bp, 5%.679 / .6756.0 / 2.617.4 / 4.7
ONT 1–5 kb, 3%.993 / .99369.2 / 27.8295 / 54.5
ONT 1–5 kb, 5%.827 / .83055.5 / 21.3563 / 47.4
ONT 1–5 kb, 10%, min_exact_match 01.000 / .99766.2 / 13.3397 / 29.5
ONT 400 bp, 10%, min_exact_match 0.967 / .9356.7 / 1.521.3 / 2.8

Multi-threaded CLI, -p 8, 945 long reads (median wall, peak RSS):

runwallpeak RSS
align, seed 1945.2 s0.95 GB
align --align-map-only, seed 1914.4 s0.67 GB

No regression in align, this PR vs its base, interleaved repeats; output checksums are identical in all three configurations:

configbasethis PR
labeled, seed 19 (6 repeats)45.2 s46.0 s
labeled, full k26.7 s27.8 s
unlabeled, seed 1938.0 s39.6 s

The seed-19 spread is 42–49 s on both sides, plus one 68 s outlier on this PR's first repeat.

šŸ¤– Generated with Claude Code


Summary by cubic

Adds a mapping-only mode to the labeled aligner that reports the best-supported label per query without extending seeds into alignments. This supports read accept/reject decisions (e.g., adaptive sampling) that need a label rather than a base-level alignment.

LabeledAligner::map_batch finds and label-filters seeds exactly as alignment does — including min_exact_match — but does not extend them. For each query it reports the labels whose seeds cover the most query characters on the better strand, the number of covered characters, and the query range they span on the forward query. The new metagraph align --align-map-only flag is off by default, requires -a, and rejects --json output; it emits one TSV line per query (header, strand, labels, covered_chars, query_begin, query_end), with unmapped queries reported as header * * 0 * *. The default align path is unchanged.

Tests

  • LabeledAlignerTest.MapWithoutExtension covers full coverage by one label, a split query where the larger coverage wins, and an unmapped query, across all graph/annotation pairs.

Benchmarks

  • On simulated Illumina and ONT reads, map_batch cuts per-read p50 latency by ~1.5–5Ɨ with top-1 label accuracy within ~1–3 percentage points of alignment.