Repository navigation
Conversation
Left-aligning an insertion or deletion pads every allele with aln_win reference bases and trims again until the shift stops. Each padding step memmoves the whole long allele, so a tandem duplication of length L costs about L/aln_win copies of L bytes. A 10 Mb <DUP> or <DEL> takes 40 s, and a chromosome-arm-scale event about an hour. Shifting a biallelic indel by one base only rotates its sequence, so realign_left() now hands the biallelic case to realign_left_indel() the first time an allele runs out: it compares the reference against the indel sequence cyclically to count the shift, then rotates once. The output is identical to the generic loop, including the case of the bases taken from a soft-masked reference. Multiallelic records keep using the generic loop. Right-alignment only appends and already runs in linear time, so it is unchanged. A 10 Mb left-aligned dup or del now takes 0.17 s instead of 40 s and a 100 Mb one 1.5 s; small variants are unaffected. Output checked against the previous code on ~270k random indels, dups, symbolic alleles and multiallelic records on soft-masked references with IUPAC codes, in both directions. Assisted-by: Claude:claude-fable-5-1 Signed-off-by: Dave Lawrence <davmlaw@gmail.com>
davmlaw
force-pushed
the
norm-linear-realign
branch
from
October 12, 2026 02:37
644595e to
a9f4e29
Compare
davmlaw
marked this pull request as ready for review
October 12, 2026 07:14
This branch has not been deployed
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.
Human PR summary
Faster left-align algorithm for
<DEL>and<DUP>. 100mb example goes from 65 mins to 1.5 secondsThis is a C implementation of existing code - which was recently used to perform HGVS normalization when I discovered the faster algorithm, I had the agent investigate what other tools did, run benchmarks and evaluate whether bcftools would gain from it as well. I had the agent make a few minor edits based on my feedback, it looks good and tests pass but I haven't written C for ~20 years
This has been previously reported in #2500 - 23 kb DRAGEN
<DUP>in #2500, which led to--no-realign=NUMAI summary
Motivation
Since #1919 and #2145,
bcftools norm -fleft-aligns symbolic<DEL>and<DUP>alleles by expanding them to sequence, which made realignment reachable for CNV-scale events. Left-aligning inrealign_left()pads every allele withaln_win(100) reference bases and trims again until the shift stops, and each padding step memmoves the whole long allele, so a tandem duplication of length L costs about L/100 copies of L bytes. A 10 Mb<DUP>takes 40 s and a 100 Mb one 65 minutes (measured below). That is what a user ran into with a 23 kb DRAGEN<DUP>in #2500, which led to--no-realign=NUM, and it is the realignment behind the SV reports in #2330. #2601/#2602 extend<DUP>realignment further, so more large events will take this path.Change
Shifting a biallelic insertion or deletion one base to the left is possible iff the reference base to the left equals the last base of the inserted or deleted sequence X, and the step rotates X right by one base. So the shift can be counted with a single cyclic comparison and X rotated once, instead of rediscovering it 100 bases at a time.
realign_left()now hands the biallelic case to a newrealign_left_indel()the first time an allele is trimmed away (which is the point at which the record is known to be a plain insertion or deletion). Multiallelic records keep using the generic loop. Right-alignment only appends to the alleles and already runs in linear time, so it is unchanged.The function comment carries a worked example. The same approach is used by
roll_left()in the biocommonsbioutilspackage (bioutils.normalize, 2019) and is the basis of the ambiguity-region expansion in NCBI's SPDI notation (Holmes et al., Bioinformatics 2020, doi:10.1093/bioinformatics/btz856); the pad-and-trim form is the one described forvt normalize(Tan, Abecasis & Kang, Bioinformatics 2015, doi:10.1093/bioinformatics/btv112).The output is identical to the generic loop, including the case of the bases when the reference is soft-masked: bases rotated in from the reference keep the reference case, the rest of X stays uppercase.
Benchmarks
Single-record VCFs on a synthetic reference with planted tandem duplications,
bcftools norm -f ref.fa one.vcf, best of 3 with a warm cache, Linux x86-64. "floor" is the same command with--no-realign, i.e. parsing and REF checking only.<DEL>, left<DUP>, left<DUP>, leftPeak RSS for the 100 Mb
<DUP>was 200 MB before and 300 MB after, both dominated by the allele strings.Checks
test/test.pl -f test_vcf_normpasses (170 tests, one new).Output compared byte for byte with the previous code on ~270k random records: insertions, deletions, insertions copying nearby sequence, delins, SNVs, multiallelic records, symbolic
<DEL>/<DUP>, positions at both ends of the contig, soft-masked references with IUPAC codes and N, left- and right-aligned. All identical.Output also compared byte for byte with the previous code on real VCFs against the Ensembl GRCh38 toplevel reference, all identical:
clinvar.vcf.gz), left-aligned--right-align<DEL>/<DUP>with |SVLEN| ≤ 500 kb, left-aligned (-c s, as gnomAD writes REF=N)ClinVar and the GIAB calls are already left-aligned, so the random records and the gnomAD SVs are what exercise the new path.
An AddressSanitizer/LeakSanitizer build runs the norm test suite, 68k of the random records and a 1 Mb tandem duplication with no reports.
New test
norm.realign-indelcovers a rotated shift that picks up lowercase bases, the first-base anchor rule, a shift stopped by an N, symbolic alleles and a multiallelic record.Assisted-by: Claude:claude-fable-5-1