sketchy

Placement of DNA sequence on a reference genome by k-mer composition, with a calibrated posterior, and an exact-truth benchmark of read placement in human centromeres.

Summary

sketchy places a genomic fragment (a read, a contig, a haplotype) on a reference genome from its k-mer composition and reports a posterior probability for the call. The project set out to test whether composition can place sequence in the regions where seed-chain-extend alignment has traditionally been considered unreliable: alpha-satellite higher-order-repeat (HOR) arrays, human satellite (HSat) blocks, segmental duplications and the acrocentric short arms. Version 0.2 is a rewrite of the method and of its evaluation.

The principal result concerns the premise rather than the method. On reads whose origin is known exactly, cut from the read's own complete diploid genome (T2T HG002 v1.1) and given realistic errors, minimap2 and Winnowmap2 place 95-100% of reads correctly in every satellite class. Composition reaches 79% overall and 95% in active HOR arrays, at about one fifth of the wall time and one sixth of the memory. When the reference is a different haplotype or a different individual, array-level placement by the two approaches is indistinguishable, and both fail on the same classes. Composition holds the order of consecutive pieces of a centromere better than alignment in one of two truth-free walks.

A further test places assembly-scale segments, 100 kb tiles of the HG002 v1.1 acrocentric short arms, on CHM13 (§4.11). Every method, alignment or composition, puts 55% of them on their own chromosome, because the closest CHM13 arm is a heterologous one for 44% of the tiles: the short arms exchange sequence between chromosomes (Guarracino et al. 2023), and the chromosome identity of a short-arm segment is a property of the whole chromosome, not of the segment. On the sample's own assembly the same tiles are resolved to 98%.

The exact-truth benchmark (sketchy simulate, bench/cenbench.sh) is released as a resource: it evaluates any mapper on complete diploid centromeres, by annotation class, with confidence intervals, haplotype-blind scoring through a whole-genome liftover, and measured cost. Full methods: docs/METHODS.md. Review of v0.1 and the reasons for the rewrite: docs/REVIEW.md. Every table below is generated from the benchmark output files by bench/fill_tables.py.

1. Background

Alignment-free placement of sequence within one genome by sketch composition was introduced by MashMap (Jain et al. 2018; MashMap3, Kille et al. 2023). Rare k-mers have been used as anchors inside satellite arrays by centroFlye, TandemMapper and VerityMap (Bzikadze, Mikheenko and Pevzner 2020-2022) and by ascairn (Shiraishi et al. 2025), and Winnowmap2 (Jain et al. 2022) is the reference method for mapping into repeats. Complete human centromere sequences (Logsdon et al. 2024; the 2,110-centromere survey published in 2026) show that 45.8% of centromeric sequence is not reliably alignable between haplotypes, that HOR arrays mutate 16.6 times faster than their flanks, and that a chromosome may carry any of hundreds of centromere haplotypes. These findings bound what any placement method can achieve across haplotypes; they say nothing about placement on the sample's own assembly.

Version 0.1 of this repository (preserved at git show dec7410:README.md) evaluated composition against alignment only across haplotypes (HPRC assemblies on CHM13), with truth taken from minimap2, and inferred from those tests that alignment fails in satellites. The exact-truth benchmark introduced here separates the two questions.

2. Method

The method has four parts; each is stated in full, with every rejected alternative and its measurement, in docs/METHODS.md.

  1. Reference index. The reference is cut into 10 kb windows. A window keeps all of its distinct 21-mers when it has fewer than a crossover (5,054 on CHM13; an alpha-satellite HOR window has about 2,300) and 256 otherwise, selected by hash. Multi-copy windows with large vocabularies (monomeric and beta satellite, diverged HORs, segmental duplications) are re-sketched at four times the budget in a second pass. Every kept k-mer carries every position at which it occurs in the window. The index is a single memory-mapped file.
  2. Coarse stage. Every k-mer of the query is looked up; the query is not sketched. A window's evidence is the sum, over non-overlapping segments of shared k-mers along the query, of ln(N/df) for the rarest word of each segment, where df is the number of windows carrying the word. Tiles of the query are combined by shift-and-sum on a phase-corrected profile, and windows that share nothing are excluded.
  3. Fine stage. Inside each candidate region, every query k-mer votes for the diagonal of every stored occurrence of that word, weighted by ln(N/df), in 20 bp bins smoothed over a width that scales with the excursion of the diagonal expected under the read's own indel rate (set against real reads, whose indels are clustered; section 6). The peak gives strand and base-level position.
  4. Posterior. Candidate regions from the coarse profile are re-weighted by their fine evidence. Two temperatures, fitted on exact-truth reads and scaled with read length, convert evidence into probability; a third, smaller temperature makes the decision. The reported post is the posterior mass of the call and mapq its Phred form.

3. Benchmark design

Exact-truth reads. sketchy simulate cuts reads from the T2T HG002 v1.1 assembly (T2T Consortium 2024; 6.0 Gbp, both haplotypes complete through every centromere) and applies a stated error model, recording each read's origin in its header. Reads are drawn per class of the assembly's cenSat v2.0 annotation (200 per class) and from outside every annotated interval (1,000), from either strand. Error presets: ONT R10-like (1.5%, 65% indels, homopolymer-biased), ONT R9-like (7%), HiFi-like (0.3%) and assembly-like (0.2%). The model is simple by design and does not reproduce the context dependence of nanopore errors; the real-read tests exist for that reason.

Scoring. A call is correct when it lands on the right sequence within 10 kb of the truth. By default the right haplotype is required; haplotype-blind scoring lifts the truth to the other haplotype through a whole-genome minimap2 -x asm5 alignment of the maternal to the paternal assembly (unique sequence only; satellite arrays have no cross-haplotype base correspondence). Per-class accuracies carry 95% Wilson intervals; paired comparisons between methods use an exact McNemar test on the same reads (bench/paired_test.py).

Competitors. minimap2 2.30 (map-ont, map-hifi, asm20, asm5, asm10), Winnowmap2 2.03 (map-ont, asm20, with meryl repetitive k-mer lists built per reference) and MashMap3 3.1.3 (-s 5000 --pi 85 on reads, -s 10000 on the walks), each with its published defaults otherwise, each run with 5 threads on the same machine (Apple M1 Max, 64 GB). Wall time and peak resident memory are recorded for every run.

Other evaluations. (i) Cross-haplotype: reads from the maternal haplotype placed on an index of the paternal one, scored at the array level (right chromosome and same annotation class); (ii) cross-individual: the same reads placed on T2T-CHM13; (iii) 6,000 real HG002 ONT R10.4.1 ultra-long reads placed on the diploid assembly, scored by agreement between methods; (iv) v0.1's benchmarks, HPRC assembly fragments and R9 reads placed on CHM13 with minimap2 as truth; (v) two truth-free walks, in which a contig spanning a centromere is cut into consecutive 50 kb pieces that are placed independently and scored by Kendall's tau-a on the order of the placements.

4. Results

4.1 Exact-truth placement on the read's own genome

20 kb reads, ONT R10-like error, placed on the diploid assembly; fraction within 10 kb on the right haplotype.

region class n sketchy (95% CI) MashMap3 minimap2 map-ont Winnowmap2 map-ont
unique 1000 0.780 (0.75-0.81) 0.891 0.973 0.975
centromere transition (ct) 200 0.765 (0.70-0.82) 0.860 0.970 0.975
monomeric alpha-satellite (mon) 200 0.740 (0.68-0.80) 0.845 0.985 0.980
diverged HOR (dhor) 200 0.890 (0.84-0.93) 0.920 0.995 0.995
inactive HOR (hor) 200 0.975 (0.94-0.99) 0.745 0.990 0.990
active HOR (kinetochore) 200 0.950 (0.91-0.97) 0.530 0.990 0.995
mixed alpha 200 1.000 (0.98-1.00) 0.410 1.000 1.000
HSat1A 200 0.810 (0.75-0.86) 0.670 0.950 0.975
HSat1B 200 0.990 (0.96-1.00) 0.695 1.000 1.000
HSat2 200 0.475 (0.41-0.54) 0.570 1.000 1.000
HSat3 200 0.825 (0.77-0.87) 0.760 1.000 0.990
beta satellite (bSat) 200 0.870 (0.82-0.91) 0.865 0.945 0.930
gamma satellite (gSat) 200 0.715 (0.65-0.77) 0.860 0.950 0.950
other censat 200 0.890 (0.84-0.93) 0.570 0.950 0.975
rDNA 200 0.255 (0.20-0.32) 0.270 0.955 0.960
all 3800 0.792 (0.78-0.81) 0.738 0.976 0.978

Per-class accuracy

Alignment resolves every satellite class. minimap2 and Winnowmap2 place 99-100% of reads from active HOR arrays, HSat2 and HSat3 and 96% of rDNA reads, to the base. Composition is the more accurate of the two sketch-based methods in alpha-satellite (active HOR 0.95 against 0.53 for MashMap3, inactive HOR 0.98 against 0.75; exact McNemar p < 10⁻¹² for both) and the less accurate in unique, monomeric and gamma-satellite sequence. Its errors are not imprecision: of the reads it places on the right haplotype and within 10 kb, 99.9% are within 1 kb, and the remainder are placed in the wrong copy of a repeat or on the wrong haplotype, megabases away (figure below). HSat2 and rDNA are the classes in which copies are too alike for a bag of words to separate.

Error distributions

4.2 Other read types

Fraction within 10 kb, right haplotype required.

read set n sketchy minimap2 map-ont Winnowmap2 map-ont minimap2 map-hifi
20 kb, ONT R9-like (7% error) 3800 0.739 0.967 0.967 --
5 kb, ONT R10-like 3800 0.668 0.902 0.903 --
100 kb, ONT R10-like 2280 0.882 0.992 0.994 --
20 kb, HiFi-like (0.3%) 3800 0.791 -- -- 0.983

Composition loses five points between 1.5% and 7% error where the aligners lose one, gains with read length, and loses most on 5 kb reads, which span a single window. On HiFi-like reads minimap2 map-hifi reaches 98.3%.

4.3 Placement and phasing

The two haplotypes of HG002 differ by about 0.1% in unique sequence. A 20 kb read carries about 20 heterozygous sites, and a method that samples 2.6% of the k-mers of unique sequence will usually not see the ones that phase it. Haplotype-blind scoring separates the two questions:

20 kb ONT R10, within 10 kb sketchy MashMap3 minimap2 map-ont Winnowmap2 map-ont
right haplotype required 0.792 0.738 0.976 0.978
either haplotype 0.867 0.783 0.984 0.986
unique sequence, either haplotype 0.984 0.988 0.998 0.997
active HOR, either haplotype 0.950 0.535 0.990 0.995
monomeric, either haplotype 0.820 0.880 0.990 0.990

Composition places 98% of unique reads at the right locus on one haplotype or the other; its deficit there is phasing. The deficits in active HOR, HSat2 and rDNA are unchanged under haplotype-blind scoring and are placement errors. Monomeric alpha-satellite is intermediate: a third of its deficit is phasing, because monomeric arrays are diverged enough to be sketched as sparsely as unique sequence.

4.4 Calibration

Expected calibration error (10 bins) of the reported posterior against correctness within 10 kb; for the aligners, mapq is read as the probability it encodes.

set sketchy post minimap2 mapq as probability Winnowmap2 mapq as probability
20 kb ONT R10 (the fit set) 0.110 0.119 0.074
20 kb HiFi (held out) 0.096 -- --
100 kb ONT R10 (held out) 0.107 0.052 0.011
20 kb ONT R9 (held out) 0.176 0.190 0.142
5 kb ONT R10 (held out) 0.140 0.182 0.246

Reliability

The temperatures fitted on the 20 kb R10 set transfer to HiFi reads and to 100 kb reads, and scaling them with read length carries the calibration to 5 kb reads. On 20 kb reads composition's calibration error is comparable to minimap2's mapq read as a probability and larger than Winnowmap2's; on 5 kb reads it is smaller than either aligner's, and on 100 kb reads larger than both. The shapes differ: on a diploid reference the aligners' mapq is under-confident (minimap2 assigns mapq below 10 to 30% of unique 20 kb reads and places 91% of those correctly), whereas composition's errors carry the same kind of evidence as its correct calls. Consequently a posterior threshold does not isolate a near-perfect subset: post ≥ 0.99 retains 38% of reads at 88% accuracy, whereas mapq ≥ 20 retains 61% at 100%.

4.5 Across haplotypes and across individuals

20 kb ONT R10-like reads from HG002's maternal haplotype placed (a) on an index of the paternal haplotype and (b) on T2T-CHM13, scored at the array level: right chromosome, and inside an interval of the same annotation class as the read's origin. In unique sequence the position is additionally checked against minimap2's confident (mapq ≥ 30) placement of the same read.

(a) Maternal reads on the paternal haplotype:

class n sketchy: right array minimap2: right array
bsat 150 0.613 0.573
censat 150 0.453 0.480
ct 150 0.820 0.827
dhor 150 1.000 1.000
gsat 150 0.953 0.953
hor 300 0.933 0.953
hsat1A 150 0.327 0.540
hsat1B 150 0.873 0.873
hsat2 150 0.993 1.000
hsat3 150 0.947 0.940
mon 300 0.793 0.757
rDNA 150 0.133 0.253
unique 800 0.936 (position agreement 0.997) 0.938
ALL 2900 0.805 0.820

(b) Maternal reads on CHM13 (a different individual; classes named as in CHM13's annotation):

class n sketchy: right array minimap2: right array
bsat 150 0.340 0.313
censat 150 0.313 0.333
ct 150 0.753 0.767
dhor 150 0.980 0.980
gsat 150 0.953 0.953
hor 300 0.947 0.947
hsat1A 150 0.493 0.500
hsat1B 150 0.933 0.933
hsat2 150 1.000 1.000
hsat3 150 0.933 0.933
mon 300 0.427 0.423
rDNA 150 0.287 0.153
unique 800 0.940 (position agreement 0.999) 0.940
ALL 2900 0.763 0.756

Across haplotypes the two methods are within two points of each other overall and fail on the same classes (HSat1A, beta satellite, other censat, rDNA). Across individuals both fall further and monomeric alpha-satellite joins the failing classes: 43% of monomeric reads land in a monomeric interval of the homologous CHM13 chromosome by either method. Reference distance, not the choice of method, determines array-level placement.

4.6 Real ultra-long reads

6,000 HG002 ONT R10.4.1 ultra-long reads (T2T consortium data; median 71 kb, 547 Mbp) placed on the diploid assembly by sketchy and by minimap2. No truth exists for real reads; the table reports agreement (same sequence, start within 10 kb) by the annotation class at the aligner's position.

class (at aligner position) n agreement agreement when sketchy post ≥ 0.9 (fraction kept) agreement when minimap2 mapq ≥ 30 (fraction kept)
unique 5458 0.778 0.754 (26%) 0.901 (43%)
ct 159 0.799 0.914 (44%) 0.932 (46%)
active_hor 95 0.979 1.000 (84%) 0.981 (55%)
HSat3 80 0.825 0.933 (75%) 0.938 (60%)
HSat1A 41 0.976 0.976 (100%) 0.973 (90%)
HSat1B 39 0.949 0.969 (82%) 1.000 (59%)
HSat2 35 0.829 0.966 (83%) 0.889 (51%)
rDNA 24 0.333 0.429 (58%) 0.714 (29%)
mon 21 0.905 1.000 (48%) 1.000 (48%)
ALL 5994 0.784 0.793 (30%) 0.906 (44%)

The methods agree on 98% of reads inside active HOR arrays and disagree most in unique sequence, where the disagreement is haplotype choice (minimap2 assigns mapq below 30 to 57% of these reads, and sketchy's posterior is below 0.9 for 74%). Filtering on either tool's confidence raises agreement; in active HOR arrays sketchy's posterior retains 84% of reads at 100% agreement where minimap2's mapq ≥ 30 retains 55% at 98%.

4.7 v0.1's cross-haplotype benchmarks on CHM13

Real HPRC assembly fragments (5-100 kb) and real R9 ultra-long reads placed on CHM13, with truth from minimap2 run offline. Inside satellites this scores agreement with an aligner's arbitrary choice among near-identical candidates, as v0.1 noted; fraction within 100 kb.

set n v0.2 within 100 kb v0.1 v0.2 within 1 kb alpha-satellite HOR HSat2 HSat3
HG002 fragments (5-100 kb) 2100 0.944 0.941 0.905 0.539 0.289 0.574
HG02723 fragments (never tuned on) 1500 0.937 0.935 0.893 0.486 0.200 0.640
HG002 ONT R9 ultra-long reads (~92% identity) 1176 0.940 0.956 0.521 0.519 0.125 0.462
satellite-enriched HG002 fragments 2400 0.805 -- 0.655 0.541 0.176 0.625
satellite-enriched HG02723 fragments 2400 0.756 -- 0.591 0.521 0.130 0.599

v0.2 matches v0.1 on the assembly fragments, is 1.5 points lower on the R9 reads, is better outside the satellites and in monomeric alpha-satellite, and places to the base rather than to a window (v0.1's median error was 2.2 kb; 90% of v0.2's fragment placements are within 1 kb). It is worse than v0.1 in HSat2 and HSat3 against minimap2's pick.

4.8 Truth-free walks

Kendall's tau-a on the order of independently placed consecutive 50 kb pieces (1 = perfect order; a method that places every piece at one locus scores 0).

(a) A real HG002 contig spanning the chr1 centromere, placed on CHM13 (v0.1's walk; the aligners' outputs are v0.1's, sketchy is re-run):

region pieces sketchy v0.2 MashMap3 best minimap2 best Winnowmap2
unique flank (ct) 21 0.848 1.000 1.000 (asm5) 1.000 (map-ont)
alpha-satellite HOR array 142 0.740 0.592 0.484 (map-hifi) 0.359 (asm20)
HSat2 block 220 0.008 0.001 0.061 (asm20) 0.033 (map-ont)
whole walk 383 0.622 0.609 0.582 (asm5) 0.584 (asm20)
method sketchy minus method, tau-a in the HOR array paired block-bootstrap p
minimap2 asm20 +0.381 < 0.002
minimap2 asm20 -N50 +0.330 < 0.002
minimap2 asm10 +0.387 < 0.002
minimap2 asm5 +0.310 < 0.002
minimap2 map-hifi +0.256 < 0.002
MashMap3 +0.147 0.036
Winnowmap2 asm20 +0.381 < 0.002
Winnowmap2 map-ont +0.460 < 0.002

The walk

(b) The maternal chr1 centromere of HG002 (120.0-138.5 Mb, 370 pieces) placed on the paternal haplotype:

region pieces sketchy minimap2 asm20 minimap2 map-hifi MashMap3
unique flank (ct) 41 1.000 0.980 0.980 0.966
alpha-satellite HOR array 79 0.806 0.807 0.757 0.803
HSat2 block 160 0.010 0.042 0.036 0.019
whole walk 370 0.583 0.625 0.628 0.611

Composition holds the order of pieces inside the HOR array better than every aligner and than MashMap3 on the CHM13 walk (tau-a 0.74 against at most 0.59; every margin significant on a paired moving-block bootstrap, MashMap3 at p = 0.04), and ties with minimap2 and MashMap3 on the walk between HG002's own haplotypes (sketchy against minimap2 asm20: −0.001, p = 0.79; against MashMap3: +0.003, p = 0.74). The two walks differ in reference distance (CHM13 is a different individual; the paternal haplotype is the same individual) and in contig, and the present data cannot separate the two explanations. In HSat2 no method orders the pieces.

4.9 Sensitivity to k and window size

Fraction within 10 kb on the 20 kb ONT R10 set, one parameter changed at a time.

class k=21, W=10 kb k=17 k=25 k=31 W=5 kb W=20 kb
unique 0.780 0.806 0.797 0.828 0.799 0.694
centromere transition (ct) 0.765 0.780 0.775 0.820 0.775 0.760
monomeric alpha-satellite (mon) 0.740 0.700 0.705 0.735 0.795 0.685
diverged HOR (dhor) 0.890 0.865 0.690 0.800 0.940 0.860
inactive HOR (hor) 0.975 0.980 0.950 0.930 0.950 0.975
active HOR (kinetochore) 0.950 0.930 0.925 0.930 0.865 0.900
mixed alpha 1.000 1.000 0.380 0.375 1.000 1.000
HSat1A 0.810 0.820 0.820 0.775 0.815 0.725
HSat1B 0.990 0.915 0.955 0.940 0.315 0.940
HSat2 0.475 0.650 0.515 0.615 0.690 0.750
HSat3 0.825 0.865 0.825 0.765 0.900 0.850
beta satellite (bSat) 0.870 0.875 0.855 0.810 0.845 0.895
gamma satellite (gSat) 0.715 0.655 0.765 0.770 0.690 0.745
other censat 0.890 0.860 0.890 0.870 0.795 0.895
rDNA 0.255 0.210 0.415 0.540 0.500 0.075
all 0.792 0.797 0.761 0.780 0.783 0.764

No setting dominates. Longer k-mers help unique sequence (k = 31: 0.83) and rDNA (0.54) and hurt HSat3 and the "mixed alpha" class, which collapses from 1.00 to 0.38 at k ≥ 25; shorter k-mers help HSat2 (k = 17: 0.65); 5 kb windows help HSat2, HSat3, diverged HORs and monomeric alpha-satellite while breaking HSat1B (0.32) and losing 9 points in active HOR arrays; 20 kb windows help HSat2 and cost 9 points in unique sequence. No single setting is clearly best overall (k = 17 and the default differ by half a point), and the class-specific pattern suggests that a per-class choice of k, or several k, would gain more than any single setting.

4.10 Compute

3,800 reads (76 Mbp) placed on the 6.0 Gbp diploid assembly, 5 threads, no other load.

method wall peak RSS per read note
sketchy 69 s 5.2 GB 18.1 ms index prebuilt (see below)
minimap2 map-ont 315 s 30.8 GB 82.9 ms builds its reference index in the same run (no .mmi)
MashMap3 275 s 46.5 GB 72.3 ms likewise
Winnowmap2 map-ont 789 s 13.1 GB 207.7 ms likewise; its meryl k-mer list took a further ~10 min

Cost

The aligners' times include building their reference index, which is how they are used on a new assembly; adding sketchy's index build (142 s, 22.6 GB peak) to its query time (211 s in total) still leaves it ahead of each of them, and at query time it is the lightest on memory by a factor of 2.5 to 9. On a prebuilt .mmi, minimap2's mapping alone would be two to three times faster than shown. Index sizes with every position stored: T2T-CHM13v2.0 3.58 GB, HG002 v1.1 diploid 5.67 GB, one haplotype 3.30 GB.

4.11 Assembly segments on the acrocentric short arms

The short arms of the five acrocentric chromosomes (13, 14, 15, 21 and 22) hold the rDNA arrays, HSat3 and HSat1 blocks, beta satellite and a distal region shared by all five arms (Floutsakou et al. 2013). They were sequenced for the first time in T2T-CHM13 (Nurk et al. 2022), and pangenome analysis has since shown that the arms exchange sequence with one another: over large stretches an arm is more similar to a heterologous acrocentric than to its homologue in another genome (Guarracino et al. 2023). Placing an assembly on the reference in these arms is therefore a different problem from placing reads on the sample's own genome, and the read benchmark above does not answer it.

The test cuts the short arm of each acrocentric chromosome of both HG002 v1.1 haplotypes, telomere to active HOR array, into non-overlapping 100 kb tiles (1,061 tiles; 320 tiles that fall in the assembly's unresolved rDNA gaps are dropped), and places them with every method, using the aligners' assembly presets, in three settings: on the diploid assembly they come from (exact truth; a placement on the other haplotype at the lifted position counts, as in §4.3), on the paternal haplotype alone (maternal tiles), and on CHM13. Across genomes the reference standard is the position lifted through a chromosome-scale alignment, each complete HG002 acrocentric chromosome aligned onto CHM13 with the whole chromosome as context, and a placement is "right" within 10 kb of it. A placement on the right chromosome elsewhere, on the short arm of another acrocentric, or anywhere else, is counted separately.

method placed right chromosome any acrocentric p-arm within 10 kb (either haplotype) own haplotype confident and wrong
minimap2 1.000 0.987 1.000 0.978 (0.97-0.99; n=1061) 0.978 0.000 (n=721)
winnowmap2 1.000 0.983 1.000 0.975 (0.96-0.98; n=1061) 0.975 0.000 (n=997)
mashmap3 1.000 0.881 1.000 0.733 (0.71-0.76; n=1061) 0.666 0.006 (n=165)
sketchy 1.000 0.961 1.000 0.936 (0.92-0.95; n=1061) 0.926 0.019 (n=645)
method placed homologous chromosome right array any acrocentric short arm same chromosome as the chromosome-scale alignment within 10 kb of it confident and heterologous
minimap2 1.000 0.577 0.538 0.983 0.702 (n=476) 0.149 (0.12-0.18) 0.358 (n=279)
winnowmap2 1.000 0.596 0.555 0.987 0.721 (n=476) 0.139 (0.11-0.17) 0.383 (n=449)
mashmap3 1.000 0.555 0.508 0.993 0.676 (n=476) 0.120 (0.09-0.15) 0.347 (n=167)
sketchy 1.000 0.549 0.516 0.994 0.674 (n=476) 0.149 (0.12-0.18) 0.333 (n=159)

Maternal short arms on the paternal haplotype

method placed homologous chromosome right array any acrocentric short arm same chromosome as the chromosome-scale alignment within 10 kb of it confident and heterologous
minimap2 1.000 0.556 0.525 0.989 0.783 (n=990) 0.255 (0.23-0.28) 0.402 (n=712)
winnowmap2 1.000 0.563 0.533 0.988 0.773 (n=990) 0.253 (0.23-0.28) 0.412 (n=988)
mashmap3 1.000 0.549 0.504 0.992 0.742 (n=990) 0.237 (0.21-0.26) 0.313 (n=469)
sketchy 1.000 0.547 0.515 0.978 0.738 (n=990) 0.233 (0.21-0.26) 0.353 (n=425)
class n minimap2: homologous chr / right array / chain chr / chain position winnowmap2: homologous chr / right array / chain chr / chain position mashmap3: homologous chr / right array / chain chr / chain position sketchy: homologous chr / right array / chain chr / chain position
ct 364 0.538 / 0.497 / 0.680 / 0.227 (362) 0.538 / 0.497 / 0.663 / 0.221 (362) 0.519 / 0.475 / 0.613 / 0.221 (362) 0.533 / 0.492 / 0.644 / 0.213 (362)
hsat3 260 0.738 / 0.715 / 0.909 / 0.311 (241) 0.742 / 0.723 / 0.896 / 0.320 (241) 0.735 / 0.704 / 0.880 / 0.307 (241) 0.746 / 0.723 / 0.900 / 0.303 (241)
hsat1A 133 0.286 / 0.286 / 0.912 / 0.275 (91) 0.293 / 0.293 / 0.846 / 0.275 (91) 0.301 / 0.271 / 0.912 / 0.253 (91) 0.211 / 0.211 / 0.747 / 0.220 (91)
mon 90 0.800 / 0.678 / 0.787 / 0.225 (89) 0.856 / 0.733 / 0.831 / 0.236 (89) 0.789 / 0.644 / 0.809 / 0.258 (89) 0.767 / 0.633 / 0.764 / 0.202 (89)
bsat 88 0.216 / 0.216 / 0.852 / 0.346 (81) 0.205 / 0.205 / 0.827 / 0.321 (81) 0.216 / 0.216 / 0.728 / 0.284 (81) 0.239 / 0.239 / 0.765 / 0.321 (81)
censat 41 0.293 / 0.293 / 0.488 / 0.171 (41) 0.293 / 0.293 / 0.463 / 0.049 (41) 0.220 / 0.220 / 0.512 / 0.073 (41) 0.293 / 0.293 / 0.415 / 0.098 (41)
hor 39 0.897 / 0.897 / 0.974 / 0.231 (39) 0.897 / 0.897 / 0.923 / 0.179 (39) 0.872 / 0.821 / 0.897 / 0.128 (39) 0.897 / 0.897 / 0.974 / 0.154 (39)
hsat1B 25 0.960 / 0.960 / 1.000 / 0.080 (25) 0.960 / 0.960 / 1.000 / 0.200 (25) 0.960 / 0.840 / 0.960 / 0.080 (25) 0.960 / 0.960 / 1.000 / 0.160 (25)
rDNA 16 0.062 / 0.062 / 0.250 / 0.188 (16) 0.125 / 0.125 / 0.375 / 0.188 (16) 0.250 / 0.250 / 0.375 / 0.062 (16) 0.125 / 0.125 / 0.125 / 0.125 (16)
unique 5 0.200 / 0.000 / 0.200 / 0.200 (5) 0.200 / 0.000 / 1.000 / 0.800 (5) 0.400 / 0.000 / 0.200 / 0.200 (5) 0.200 / 0.000 / 0.200 / 0.200 (5)
ALL 1061 0.556 / 0.525 / 0.783 / 0.255 (990) 0.563 / 0.533 / 0.773 / 0.253 (990) 0.549 / 0.504 / 0.742 / 0.237 (990) 0.547 / 0.515 / 0.738 / 0.233 (990)

Acrocentric short arms on CHM13

class n best CHM13 arm is the homolog margin < 0.5 pp margin < 2 pp minimap2: homologous chr when margin < 0.5 / >= 0.5 winnowmap2: homologous chr when margin < 0.5 / >= 0.5 mashmap3: homologous chr when margin < 0.5 / >= 0.5 sketchy: homologous chr when margin < 0.5 / >= 0.5
ct 364 0.541 0.453 0.615 0.497 / 0.573 0.473 / 0.593 0.406 / 0.613 0.461 / 0.593
hsat3 260 0.738 0.065 0.115 0.294 / 0.770 0.353 / 0.770 0.235 / 0.770 0.412 / 0.770
hsat1A 133 0.308 0.030 0.090 0.250 / 0.287 0.000 / 0.302 0.000 / 0.310 0.250 / 0.209
mon 90 0.767 0.300 0.433 0.519 / 0.921 0.593 / 0.968 0.593 / 0.873 0.481 / 0.889
bsat 88 0.239 0.148 0.261 0.000 / 0.253 0.000 / 0.240 0.000 / 0.253 0.077 / 0.267
censat 41 0.268 0.268 0.439 0.091 / 0.367 0.091 / 0.367 0.091 / 0.267 0.091 / 0.367
hor 39 0.872 0.051 0.077 1.000 / 0.892 1.000 / 0.892 0.500 / 0.892 1.000 / 0.892
hsat1B 25 0.960 0.000 0.000 -- / 0.960 -- / 0.960 -- / 0.960 -- / 0.960
rDNA 16 0.188 0.438 0.938 0.143 / 0.000 0.143 / 0.111 0.429 / 0.111 0.143 / 0.111
unique 5 0.400 0.200 0.600 0.000 / 0.250 1.000 / 0.000 0.000 / 0.500 0.000 / 0.250
ALL 1061 0.560 0.233 0.346 0.429 / 0.595 0.425 / 0.604 0.372 / 0.603 0.413 / 0.587
class n nearest copy on the other haplotype is the homologue is a heterologous arm no copy reported median matched fraction, homologue median matched fraction, nearest heterologous arm
ct 364 0.560 0.440 0.000 0.994 0.991
hsat3 260 0.712 0.285 0.004 0.961 0.000
hsat1A 133 0.391 0.609 0.000 0.261 0.356
mon 90 0.711 0.289 0.000 0.997 0.949
bsat 88 0.420 0.580 0.000 0.622 0.836
censat 41 0.122 0.878 0.000 0.000 0.984
hor 39 0.897 0.103 0.000 0.991 0.000
hsat1B 25 0.880 0.120 0.000 0.983 0.000
rDNA 16 0.188 0.812 0.000 0.973 0.985
unique 5 0.200 0.800 0.000 0.810 0.957
ALL 1061 0.573 0.426 0.001 0.974 0.876

On the assembly the tiles come from, with exact sequence, minimap2 and Winnowmap2 return 98% of the short-arm tiles to within 10 kb of their origin on either haplotype, composition 94% and MashMap3 73% (88% on the right chromosome; its coordinates are approximate at the scale of its 5 kb segments). Every method keeps every tile on an acrocentric short arm. Within one assembly, then, the short arms are resolvable at the 100 kb scale, and the few tiles each method sends to another arm are those whose closest copy in HG002 lies on another arm.

On the paternal haplotype the four methods still agree with one another (minimap2 and sketchy name the same chromosome for 81% of the maternal tiles), but only 55 to 60% of the tiles land on the paternal homologue, although 98 to 99% stay on an acrocentric short arm. This is not an error of tile-scale mapping. Aligning each maternal chromosome in full onto the paternal haplotype sends 34% of the liftable tiles (160 of 476) to a heterologous arm as well, and the tile placements agree with that chromosome-scale standard on the chromosome for 67 to 72% of tiles. Inside satellite arrays a tile rarely reproduces the chromosome-scale position (12 to 15% within 10 kb), as with reads in §4.5: the arrays differ between haplotypes in copy number and order, not only in sequence.

On CHM13 the picture repeats with a second genome: 55 to 56% of tiles on the homologous chromosome for every method, 98 to 99% on an acrocentric short arm, 74 to 78% on the chromosome the chromosome-scale alignment chooses and 23 to 26% at its position. The exchanges are chromosome-specific (table below): HG002 chr21 tiles go to CHM13 chr13 more often than to chr21 (80 against 49 for minimap2), chr13 tiles go to chr22 and chr21, chr14 tiles to chr22 and chr21, while chr15 and chr22 tiles mostly stay (77% and 70%). By class (minimap2; the other methods are within a few points), HSat1A (29%), beta satellite (22%), other censat (29%) and rDNA (6%) are rarely placed on the homologue; HSat3 (74%), monomeric alpha-satellite (80%), inactive HOR (90%) and HSat1B (96%) mostly are; the centromere-transition class, a third of all tiles, splits evenly (54%).

source n method chr13 chr14 chr15 chr21 chr22 other unplaced
chr13 216 minimap2 85 18 25 35 48 5 0
chr13 216 winnowmap2 92 16 22 30 54 2 0
chr13 216 mashmap3 90 10 21 28 64 3 0
chr13 216 sketchy 79 18 20 32 50 17 0
chr14 240 minimap2 17 127 5 33 57 1 0
chr14 240 winnowmap2 19 134 4 28 53 2 0
chr14 240 mashmap3 26 124 11 29 50 0 0
chr14 240 sketchy 22 126 7 34 50 1 0
chr15 251 minimap2 7 11 192 10 25 6 0
chr15 251 winnowmap2 7 13 188 12 25 6 0
chr15 251 mashmap3 5 18 186 11 26 5 0
chr15 251 sketchy 4 15 192 12 26 2 0
chr21 158 minimap2 80 7 10 49 12 0 0
chr21 158 winnowmap2 77 11 14 48 7 1 0
chr21 158 mashmap3 79 13 7 48 11 0 0
chr21 158 sketchy 80 12 9 47 9 1 0
chr22 196 minimap2 5 16 36 2 137 0 0
chr22 196 winnowmap2 7 15 35 2 135 2 0
chr22 196 mashmap3 6 19 36 0 135 0 0
chr22 196 sketchy 7 16 30 5 136 2 0

The identity margins explain these numbers. The best-matching CHM13 arm is the homologue for 56% of tiles, the fraction every method places there, and for 23% of tiles the best and the second-best arm are within 0.5 percentage points of matched fraction (35% within 2 points). Where the margin is clear the methods place 59 to 60% of tiles on the homologue, where it is not, 37 to 43%. The methods find the closest sequence, and the closest sequence is on another chromosome for two tiles in five. Mapping quality does not flag this: 40% of minimap2's placements at mapq >= 30, 41% of Winnowmap2's and 35% of sketchy's at the equivalent posterior are on a heterologous arm, because mapping quality measures how far the best hit beats the runner-up, not which chromosome it is on.

The same structure exists inside HG002 itself. Aligning every tile against the other haplotype with all hits kept, the nearest copy is the homologue for 57% of tiles and a heterologous arm for 43% (table above; a copy that is not reported scores below half of the best one). The classes divide as they do on CHM13: HSat3, monomeric alpha-satellite, inactive HOR and HSat1B have their nearest copy on the homologue (71 to 90%), while HSat1A, beta satellite, other censat and rDNA have it on a heterologous arm (58 to 88%), and the centromere-transition class splits (56% homologue) with median matched fractions of 0.994 and 0.991 to the two. The tiles that a method sends to another arm even on the tile's own assembly are mostly those whose nearest other-haplotype copy is heterologous (9 of minimap2's 14, 30 of sketchy's 41).

For an assembly, then, the chromosome identity of a short-arm segment cannot be read from the segment. By similarity, any method assigns 45% of 100 kb segments to a chromosome other than their own, and even the chromosome-scale standard used here, which carries the centromere and the long arm as context, assigns 35% of them to a heterologous arm. Composition has no advantage in this regime: it follows the same similarity to the same answers, at a fraction of the cost (41 s for the 1,061 tiles on CHM13 against 103 s for MashMap3, 168 s for minimap2 and 874 s for Winnowmap2, 8 threads). The per-tile results of all three settings are kept in docs/v2/acro/ as a resolvability track of the HG002 v1.1 short arms: which segments every method places consistently, which are ambiguous by identity, and where the chromosome-scale alignment disagrees with the tile.

5. Discussion

The results separate three questions that v0.1 conflated. Placement of reads on the sample's own complete assembly is solved by alignment in every satellite class at current error rates; the difficulty attributed to satellites belongs to incomplete references and to cross-haplotype placement. Across haplotypes and individuals, array-level placement is bounded by the biology and is the same for composition and alignment. Within-array order across haplotypes, the one measurement without a truth set, favours composition on one contig pair and is a tie on another.

Composition's practical value is therefore not accuracy. It is a placement at about 80% of an aligner's accuracy at one fifth of its time and one sixth of its memory, with a posterior that is calibrated on average, and with an explicit credible set (cred_span, cred_loci) that states which array a read belongs to when its position within it cannot be resolved. Those properties suit triage, read recruitment to arrays, and screening before alignment rather than replacement of it. The benchmark is the more general contribution.

The acrocentric test (§4.11) adds a limit that no placement method can move. On the sample's own assembly, 100 kb segments of the short arms are resolved to 98% by alignment and 94% by composition; on another genome, every method puts 55% of them on their own chromosome, because for 44% of the segments the closest arm in the reference is a heterologous one. The chromosome-scale alignment, which carries the centromere and long arm as context, still assigns a third of them to a heterologous arm. Chromosome assignment of short-arm sequence is a property of whole chromosomes, and a mapper's confidence, which measures how far the best hit beats the runner-up, says nothing about it. For an assembly the practical consequences are to place short-arm contigs with their flanking centromere and long arm attached, to read a heterologous placement as a statement about similarity rather than identity, and to treat the resolvability track in docs/v2/acro/ as a map of where the reference can and cannot decide.

6. Limitations

7. Reproducibility

cargo build --release && cargo test --release          # 30 tests, incl. end-to-end placement on a synthetic genome
scripts/fetch_data.sh --assemblies                     # CHM13 + annotation, HPRC assemblies (~4 GB)
curl -fL -o data/hg002v1.1.fasta.gz https://s3-us-west-2.amazonaws.com/human-pangenomics/T2T/HG002/assemblies/hg002v1.1.fasta.gz
curl -fL -o data/hg002v1.1.cenSatv2.0.bed https://s3-us-west-2.amazonaws.com/human-pangenomics/T2T/HG002/assemblies/annotation/centromere/hg002v1.1_v2.0/hg002v1.1.cenSatv2.0.bed

# the exact-truth benchmark, end to end (any mapper can be added as a PAF in <outdir>/bench/)
bench/cenbench.sh data/hg002v1.1.fasta.gz data/hg002v1.1.cenSatv2.0.bed out/cenbench 6 ont-r10 20000 200 1000

# the tables in this file
.venv/bin/python bench/fill_tables.py

The exact command lines behind every table are in docs/METHODS.md §9; bench/README.md describes each script. Winnowmap2 is built from source on Apple silicon as described in v0.1's README (git history).

Output columns

column meaning
chrom start end strand the call; start is the leftmost reference coordinate
mapq -10 log10(1 - post), capped at 60
post posterior of the call: the chosen region's posterior times the mass within 2 kb of the fine peak
coarse_start coarse_post the composition-only call and its mass within one window
region_post posterior of the chosen region after the fine update
llr gap coarse evidence at the peak, and its gap to the best start outside the guard region (nats)
cred_span cred_loci cred_frac the 90% credible set: bp span, separate runs, fraction of the genome
p_hat seen_frac estimated k-mer survival; fraction of the query's k-mers the index has seen
fine_ok fine_post fine_votes whether the fine stage anchored, its within-region mass, anchors under the peak
candidates credible runs with their masses

Repository layout

src/        seq.rs (BGZF reader) · kmer.rs · sketch.rs · index.rs · locate.rs · sim.rs · main.rs
bench/      cenbench.sh, run_methods.sh, score_v2.py, summarize_sim.py, score_xhap.py, score_agreement.py,
            paired_test.py, liftover.py, fill_tables.py, fig_v2.py, sweep.py + make_walk.py (walks),
            acro_tiles.py + score_acro.py + fig_acro.py (acrocentric short arms, §4.11), v1/ (v0.1 scripts)
docs/       METHODS.md, REVIEW.md, v2/ figures, v2/acro/ per-tile short-arm results, index.html (built by bench/build_site_v2.py)
tests/      end-to-end tests on a synthetic genome

References

Authorship

v0.2 was written by Claude Fable 5.1 (Anthropic) in autonomous sessions on 2026-09-01 and 2026-09-02 for @jlanej, who set the problem, supplied the data and the machine, and directed the follow-up questions (the scientific-register pass, the mechanism tests, the acrocentric short-arm test in §4.11 and the cleanup). It builds on v0.1 by Claude Opus 5 and @jlanej. Claude designed the method, the benchmark, the scoring and the figures, ran every experiment, and wrote this document, docs/METHODS.md and docs/REVIEW.md; @jlanej reviewed the results. MIT licensed.

Review of sketchy v0.1, and what v0.2 changes

This is the review that motivated the rewrite. It was written after reading every source file, every benchmark script and every number in the v0.1 README, and after re-running the v0.1 tool and its scoring scripts to confirm that the published numbers reproduce (they do: 94.1% of HG002 fragments within 100 kb, 60.8% for alpha-satellite HOR, 42.1% for HSat2).

In summary, the empirical work in v0.1 was careful and largely correct about its own limits; the method it evaluated was poorly matched to the problem, and the benchmark needed to decide the question it posed was absent. Version 0.2 addresses both.

1. What v0.1 got right

2. What was wrong with the method

2.1 The score was a heuristic with no probabilistic reading

v0.1 scored a tile against a window by a tf-idf cosine with the rarity weight raised to the power 2.5 in the numerator but power 1 in the reference norm. The exponent was tuned on the benchmark, and the norm asymmetry was chosen because the symmetric form scored worse. The resulting score was, as the README stated, "not bounded by 1, not comparable between queries". Every confidence statistic (snr, margin, tile_support, support_span, rival_frac) was then a post-hoc function of that score, and the audit found that support_span covered the truth only 26-65% of the time inside satellites.

A cosine is a similarity, not a likelihood. Nothing in it says how probable it is that the query came from this window rather than that one, so nothing downstream could be calibrated except by fitting a separate model on top (the split-conformal interval in stat_audit.py, which was never wired into the tool).

Version 0.2 replaces the score with identity evidence that has a direct probabilistic reading: each non-overlapping segment of shared k-mers contributes ln(N/df) for its rarest word, the log-odds of observing that word in the true window rather than in one drawn at random. (A term penalising unexplained words, the textbook likelihood, was implemented and rejected; docs/METHODS.md §3 gives the measurement.) The profile over windows becomes a posterior through a softmax whose temperature is fitted on reads whose origin is known exactly. The posterior mass of the call is the confidence and its Phred form is mapq; the README's Section 4.4 reports its calibration on held-out error models and read lengths.

2.2 The query was sketched, and that created a problem that then needed three fixes

The reference must be sketched (that is what makes the index 2 GB rather than 30 GB). The query does not need to be: it is one sequence, its k-mers cost nothing to enumerate, and looking up ten thousand hashes takes a millisecond. v0.1 nevertheless sketched the query by a rule of its own -- first the "ramp", then a per-query blend between ramp and step, chosen from seen_frac -- and then discovered that sketching the query more densely than the reference biases the cosine (the norms are over different hash spaces), tried a per-window norm correction, found pruning worked better, and shipped the blend. Roughly a third of locate.rs and of the README's method section dealt with this.

Version 0.2 does not sketch the query. Every query k-mer is looked up; a window can share only the words whose hash lies at or below its own threshold, a prefix of the hash space that is exactly what the reference stored, so no correction is required. The ramp, the step, the blend, --query-blend, --per-window-norm and seen_frac-driven pruning are removed.

The order-aware "link" channel (hashed pairs of consecutive minimizers) was the only positional information in v0.1. The matched ablation put its contribution at +0.009 with p = 0.29, and it accounted for 94 M of the 222 M postings. It was an indirect way to recover order from a bag-of-words model.

Version 0.2 stores every position of every retained k-mer (u32 with a strand bit) and runs a positional stage inside the candidate regions of the coarse posterior: every query k-mer votes for the diagonal ref - query of every stored occurrence, weighted by ln(N/df). This is the FASTA diagonal method (1985) with rarity weights, which v0.1's prior-art section itself named, and it gives base-level resolution, the strand (including for single-window queries, which v0.1 could not orient) and a second calibrated posterior.

2.4 Resolution was bounded by the window

v0.1's median error in unique sequence was 2.2-2.5 kb, a consequence of snapping to a 10 kb grid and interpolating rather than a property of the sequence. The fine stage removes that floor; on clean sequence v0.2 places to the base.

3. What was wrong with the evaluation

3.1 The benchmark could not answer the question posed

Every real-data number in v0.1 scores HG002 fragments against CHM13, with truth from minimap2. Inside a satellite array that conflates three quantities: the method's error, the aligner's error (78% of alpha-satellite truth calls carried mapq ≤ 5), and the genuine difference between two haplotypes' arrays, which Logsdon et al. (2024) quantify as 45.8% of centromeric sequence being unalignable between haplotypes. v0.1 acknowledged this, reported satellite accuracy as a range rather than a number, and ran one small within-sample test (59 satellite reads on the HG002 HPRC draft assembly, whose centromeres are incomplete).

Version 0.2 uses the T2T HG002 v1.1 diploid assembly, in which both haplotypes are complete through every centromere and which carries its own cenSat annotation, and simulates reads from it with stated error models. The origin of every base is then known exactly, inside the centromere and out, for both haplotypes. This benchmark can determine whether composition places a read within its array, at what resolution, and whether an aligner does. The cross-haplotype benchmarks (HG002 and HG02723 fragments against CHM13) are retained as the second question: what remains recoverable when the reference is not the sample's haplotype.

3.2 The failure of alignment was inferred from one contig walk

The claim that alignment fails in satellites rested in v0.1 on the walk, where sketchy's order statistic exceeded every aligner's inside the HOR array, and on the mapq of the truth calls. Neither measures an aligner's accuracy in a satellite, because neither had a truth. With exact-truth simulated reads, v0.2 measures minimap2, Winnowmap2 and MashMap3 on the same reads, by region class, with wall time and peak memory recorded, and reports the aligners' advantage where it exists.

3.3 Simulation was dismissed too early

v0.1 concluded that simulated divergence is a structurally different and easier problem after finding that tiling helped on simulated data and not on real cross-haplotype data. That holds for cross-haplotype placement, where the difference is the haplotype rather than the error model. It is not a reason to avoid simulation for the same-haplotype question, where simulation is the only route to exact truth inside a satellite. v0.2 uses both and states which question each answers.

4. Compute

v0.1 v0.2 (final: every position stored, repeat-aware second pass)
index, CHM13v2.0 2.87 GB, 65 s, 12 GB peak 3.58 GB, 95 s, 10.9 GB peak
index, HG002 v1.1 (6.0 Gbp diploid) -- 5.67 GB, 142 s, 22.6 GB peak
feature channels 2 (k-mer + minimizer pairs) 1, with every position
query sketching ramp/step/blend, per query none
per-query cost tiles × postings, plus a genome-wide dense pass per tile tiles × postings (sparse), one lookup per query k-mer, and a region-restricted diagonal vote

An intermediate v0.2 index with one position per word was 2.05 GB for CHM13 and built in 34 s; storing every position (+50%) is what bought within-array resolution, and the repeat-aware second pass adds another 17% for a five-point gain in accuracy. Query cost is measured in the write-up alongside the aligners on identical inputs.

5. What v0.2 does not change

The density-equalizing keep rule; the 10 kb window; the inverted index with a 22-bit bucket table over sorted keys; the block-parallel BGZF reader; the benchmark harness, samplers and the truth-free sweep. The README's prior-art section was accurate and is carried forward.

6. Claims from v0.1 that v0.2 retires


Review and rewrite by Claude Fable 5.1 (Anthropic) for @jlanej, September 2026.

Methods (v0.2)

sketchy places a query sequence on a reference genome using its k-mer composition, and reports a posterior probability for the call. Alignment appears in this repository only offline, to establish ground truth for the cross-haplotype benchmarks, and as a competitor in the benchmarks themselves. It is never available to the method.

The version-2 method has four parts: a density-equalizing reference sketch (unchanged from v0.1), a coarse stage that scores windows by composition, a fine stage that resolves position by rare-word diagonals inside candidate regions, and a calibrated posterior that combines the two. Each is stated here in full.

1. Features

Every canonical k-mer (default k = 21) is hashed with a splitmix64 finalizer. Canonical means the lexicographically smaller of the k-mer and its reverse complement, so a sequence and its reverse complement produce identical hash multisets. Alongside each hash the index keeps whether the forward k-mer was the canonical one (one bit), which is what lets the fine stage recover strand by comparing that bit between query and reference. A non-ACGT base resets the rolling window.

2. The reference index: a density-equalizing sketch, with positions

The reference is cut into windows of W bp (default 10 kb). A window's distinct k-mers are ranked by hash and the smallest are kept -- a bottom-k sketch -- with the keep count set by the step rule:

  keep(n) = n         if n <= V^2 / (2V - budget)     (the crossover)
          = budget    otherwise

V is the vocabulary of a fully unique window of this size, estimated from the reference at build time (98th percentile of distinct counts over 4,096 sampled windows; 9,980 for CHM13 at k = 21, W = 10 kb) and budget is 256. A 10 kb alpha-satellite HOR window holds about 2,300 distinct 21-mers, an HSat3 window about 4,000, a unique window about 9,950: the windows that need every word they have are exactly those with few enough words to afford it. Windows with less than 50% A/C/G/T are not indexed.

Because the kept set is a hash prefix (every hash at or below the window's threshold), two windows sketched at different densities are comparable on the prefix they share, and an unsketched query is comparable to every window on that window's own prefix. That property is what removes any need to sketch the query.

For every kept k-mer the index stores every position at which it occurs in the window (chromosome-local, with the strand bit). v0.1 stored one position per word per window; inside a tandem repeat almost every word occurs several times per window, and a word with one stored position can vote for only one of its copies. The fine stage's within-array resolution depends on this.

On disk the index is one file of little-endian arrays, 64-byte aligned, opened by mmap: a bucket table over the top 22 bits of each hash, the sorted distinct keys, a CSR offset array, and per posting the window id (u32), multiplicity (u16) and an offset into the position array. A key lookup costs about two cache misses.

3. Coarse stage: composition over windows

The query is cut into tiles of W bp (non-overlapping by default). Every k-mer of a tile is looked up; the query is never sketched.

Evidence. For a tile and a reference window, the identity evidence is

   E(w) = sum over non-overlapping shared segments of  ln( N / df(h*) )

where df(h) is the number of windows carrying h, N the number of windows, and h* the rarest word of the segment. A segment is opened by the first shared k-mer at a query position and absorbs every shared k-mer that starts within k bases of it; the next segment opens at the next shared k-mer beyond. ln(N/df) is the log-odds that a word would be seen in a window that carries it against a window drawn at random.

Two choices here are the result of measurements, not taste:

Words carried by more than max_df_frac of windows (default 1%) are skipped; they carry less than 4.6 nats of information each and dominate running time.

Shift-and-sum. A tile at query offset a matching window j is evidence that the query starts at window j - round(a/W); reverse-complemented, at j - round((qlen - b)/W). Each tile's evidence is added into forward and reverse profiles over all windows. The profiles are sparse: only windows that share a segment with some tile are non-zero.

Phase correction. A query whose tiles are out of phase with the reference windows splits each tile's evidence between two consecutive starts, so its peak is about half of an in-phase query's. Two haplotypes of one genome are never in phase with each other, and on the raw profile the choice between them was decided by phase rather than evidence (haplotype accuracy 53% in unique sequence). The decision profile is therefore L(s) + L(s+1) on each strand.

Posterior. With temperature T,

   P(s) = exp(L(s)/T) / sum over starts and strands with L > 0 of exp(L/T)

The MAP start, its posterior mass within one window (coarse_post), the cred-level credible set (its bp span and number of separate runs: cred_span, cred_loci), and the evidence gap to the best start outside the guard region around the MAP (gap, nats) are reported. T is calibrated (section 6); it is not 1, because segments are still far from independent.

Candidate regions. Every maximal run of starts whose decision-profile value is at least 40% of the peak, on either strand, is a candidate region, padded by the query's length in windows plus two so the query can sit anywhere inside. The MAP's region comes first; at most six are kept. The rule is independent of T on purpose.

3a. k-mer survival

For up to eight tiles spaced along the query, the window with the most evidence is found and the fraction of the tile's k-mer occurrences it explains, divided by the fraction of the hash space that window retained, is recorded; p_hat is the median over tiles. For a read from its own reference this is (1 - e)^k: 0.69 on the 1.5%-error set (expected 0.73), 0.21 on the 7% set (expected 0.22). It sets the fine stage's smoothing width and is reported.

4. Fine stage: rare-word diagonals inside candidate regions

Inside a candidate region, every query k-mer q at query offset x that is present in the index (and not above the df cutoff) is looked up, and for every stored occurrence at chromosome position r in a window of the region it casts a vote on a diagonal:

   same strand bit:      d = r - x          (forward)
   opposite strand bit:  d = r + x          (reverse; the query start is d - qlen + k)

with weight ln(N/df(q)), into histograms binned at 20 bp. The histograms are smoothed with a box whose half-width scales with the excursion of the diagonal expected under indels, 10 * sqrt(qlen * 0.65 * e) bp (at least one bin), where e is the query's error rate inferred from its k-mer survival (section 3a) and 0.65 is the indel share of nanopore errors. The peak gives the strand and, from the weight-averaged diagonal of the anchors under the peak, the query's start to the base. The width matters, and in opposite directions on simulated and real reads. A fixed ±200 bp costs two points at 1.5% error on simulated 20 kb reads (the variant k-mers that separate one tandem copy from the next are averaged away, in HSat3 and monomeric arrays especially) and a fixed ±20 bp costs six points at 7% error (the diagonal drifts out of the window); three standard deviations of the random walk is the optimum on simulated reads (79.8% within 10 kb). On real R10.4.1 ultra-long reads, whose indels are clustered (section 7), three sigma gives the lowest agreement with minimap2 of any setting tried (75.4%) and ten sigma the highest (78.4%, against 77.2% for the fixed 1% rule), for a cost of 0.6 points on simulated reads; ten is the default. This is the FASTA diagonal method (Lipman & Pearson 1985) with rarity weights and every occurrence of every retained word; it scores no gaps and compares no bases.

Two quantities are computed from each region's histogram: the per-anchor peak, used for the position and its posterior within the region, and a segment peak (the same non-overlap rule as the coarse stage, applied along the query per diagonal bin), used to choose between regions. Per-anchor sums are what separate one HOR copy from the next -- collapsing them into segments cost 17 points of accuracy in active HOR arrays -- but on their own scale they would overrule the coarse stage wherever anchors are dense, which is exactly where the coarse stage should have the final word on which array or haplotype (section 5).

5. Choosing the region: composition updated by position

Composition and position have complementary strengths, measured on exact-truth reads from the diploid HG002 assembly. Inside HOR arrays, few k-mers are single-copy and the fine stage is starved, and composition chooses the right array and haplotype far more often (active HOR 89.5% within 10 kb against 70%). In monomeric alpha satellite, beta satellite and unique sequence, the arrangement of the words matters more than the words, and position chooses better (beta satellite 80% against 33%). Neither alone is best everywhere.

The region is therefore chosen by a Bayesian update of the coarse posterior: region i with coarse mass m_i and segment fine peak F_i has posterior

   P(i) = m_i exp(F_i / T_f)  /  ( sum_j m_j exp(F_j / T_f) + (1 - sum_j m_j) )

where the last term is the coarse mass outside every candidate, which receives no fine update. The reported post is P(i*) times the within-region posterior mass of the fine peak (within fine_tol = 2 kb of it); mapq is -10 log10(1 - post), capped at 60. When no anchor is found in any region the coarse call stands and post is coarse_post.

6. Calibration

Both temperatures are fitted on reads whose origin is known exactly -- sketchy simulate cuts reads from an assembly, records their coordinates in the header, and applies a stated error model -- over a grid of (T, T_f) pairs (sketchy calibrate), with the decision temperature held fixed. The fit set is 3,800 20 kb ONT-R10-like reads from the HG002 v1.1 diploid assembly. Temperatures scale linearly with query length from that 20 kb reference, within a factor of eight: fitted separately, the best fine temperature is about four times smaller on 5 kb reads and four times larger on 100 kb reads, because a wrong call inside a near-identical repeat accumulates evidence along the whole read exactly as a right call does. Scaling by length halves the calibration error on 5 kb reads (0.30 to 0.14). Accuracy is flat (79.8%) for any coarse T at or above 64, so the coarse prior barely matters to the decision; the reported posterior is best balanced at T = 256, T_f = 128 (expected calibration error 0.111, negative log-likelihood 0.85 per read; the best ECE on the grid was 0.090 at 512/64 and the best NLL 0.68 at 512/256, and the shipped pair sits between them). The decision temperature is 8. The evaluation sets are different error models, read lengths and, for the real-read tests, different data altogether; the write-up reports expected calibration error on each.

7. Read simulation

Per base, with probability rate, an error occurs and is a substitution, insertion or deletion in the stated proportions; deletions inside homopolymers of length >= 4 are hp_bias times more likely; an insertion extends with probability ins_ext. Presets: asm (0.2%), hifi (0.3%, indel-dominated, homopolymer-biased), ont-r10 (1.5%), ont-r9 (7%). Reads are drawn uniformly, or per annotation class from a BED, from either strand. The model does not reproduce the context dependence of real nanopore errors; the real-read benchmarks exist for that reason.

One consequence was measured directly (bench/drift.py, from minimap2's CIGAR strings): the alignment diagonal of a real HG002 R10.4.1 ultra-long read wanders far more than the model's. Real reads have a lower indel rate than the ont-r10 preset (0.0039 against 0.0103 per base) but a maximum excursion of the diagonal from its starting offset of 96 bp (median) and 433 bp (90th percentile) per read of median 77 kb, 5 to 13 times the sqrt(L * r_indel) a random walk of independent indels predicts, against 1.4 to 1.9 times for simulated reads; within any 20 kb of a real read the 90th-percentile excursion is 180 bp, against 32 bp for simulated 20 kb reads. Indels in real reads are clustered. This is why the fine stage's smoothing width, which the simulations alone would set at the random-walk scale, is set wider (section 4), and it is a caution for any benchmark that tunes a mapper's gap tolerance on simulated nanopore reads.

8. Outputs

column meaning
chrom start end strand the call; start is the leftmost reference coordinate
mapq Phred-scaled post, capped at 60
post posterior of the call (region times position)
coarse_start coarse_post the composition-only call and its mass within one window
region_post posterior of the chosen region after the fine update
llr coarse evidence at the peak, nats
gap coarse evidence gap to the best start outside the guard region, nats
cred_span cred_loci cred_frac the 90% credible set: total bp span, separate runs, fraction of the genome
p_hat fraction of the query's comparable k-mer occurrences the best window explains
seen_frac fraction of the query's k-mers the index has ever seen
fine_ok fine_post fine_votes whether the fine stage anchored, its within-region mass, anchors under the peak
candidates the credible runs with their masses

9. Command lines behind every table

Indexes (final settings; --repeat-min-df 2 for a haploid reference, 3 for a diploid one):

sketchy index data/chm13v2.0.fa.gz        -o data/chm13v2.0.skx      --repeat-min-df 2
sketchy index data/hg002v1.1.fasta.gz     -o data/hg002v1.1.skx
sketchy index data/hg002v1.1.pat.fa.gz    -o data/hg002v1.1.pat.skx  --repeat-min-df 2 --saturation-from data/hg002v1.1.skx

Exact-truth read sets from the diploid assembly, one per error model and length:

B=data/hg002v1.1.cenSatv2.0.bed; R=data/hg002v1.1.fasta.gz
sketchy simulate $R -o sim_L20k_ontr10.fa  -n 1000 -l 20000  --model ont-r10 --bed $B --per-class 200 --seed 11
sketchy simulate $R -o sim_L20k_ontr9.fa   -n 1000 -l 20000  --model ont-r9  --bed $B --per-class 200 --seed 12
sketchy simulate $R -o sim_L20k_hifi.fa    -n 1000 -l 20000  --model hifi    --bed $B --per-class 200 --seed 13
sketchy simulate $R -o sim_L100k_ontr10.fa -n 600  -l 100000 --model ont-r10 --bed $B --per-class 120 --seed 14
sketchy simulate $R -o sim_L5k_ontr10.fa   -n 1000 -l 5000   --model ont-r10 --bed $B --per-class 200 --seed 15

Placement and competitors (5 threads each; Winnowmap2 needs a meryl repetitive-k-mer list for the reference, WM_K15):

WM_K15=meryl/hg002v11_repetitive_k15.txt bench/run_methods.sh sim_L20k_ontr10.fa $R data/hg002v1.1.skx out/bench_L20k_ontr10 5 ont
bench/summarize_sim.py --queries sim_L20k_ontr10.fa --dir out/bench_L20k_ontr10 --tol 10000 --metric w10kb
bench/score_v2.py out/bench_L20k_ontr10/sketchy.tsv --tol 10000 --strip-hap        # per-class table with ECE
sketchy calibrate -x data/hg002v1.1.skx sim_L20k_ontr10.fa --tol 10000 --fine-tol 10000   # temperature grid
bench/fig_v2.py classbars|reliability|errcdf|cost ...                                       # figures

Cross-haplotype, maternal reads on the paternal index (the haplotype FASTAs are split from the diploid assembly; data/hg002v1.1.pat.skx is built from the paternal one with --repeat-min-df 2 as above):

gzip -dc $R | awk '/^>/{p=($0 ~ /_PATERNAL/)} p' | bgzip > data/hg002v1.1.pat.fa.gz
gzip -dc $R | awk '/^>/{p=($0 ~ /_MATERNAL/)} p' | bgzip > data/hg002v1.1.mat.fa.gz
grep _MATERNAL $B > censat_mat.bed
sketchy simulate data/hg002v1.1.mat.fa.gz -o mat_L20k_ontr10.fa -n 800 -l 20000 --model ont-r10 --bed censat_mat.bed --per-class 150 --seed 21
sketchy locate -x data/hg002v1.1.pat.skx mat_L20k_ontr10.fa -o sketchy_pat.tsv
minimap2 -cx map-ont --secondary=no data/hg002v1.1.pat.fa.gz mat_L20k_ontr10.fa > minimap2_pat.paf
bench/score_xhap.py --queries mat_L20k_ontr10.fa --bed $B --sketchy sketchy_pat.tsv --paf minimap2_pat.paf

Assembly segments on the acrocentric short arms (§4.11): the short arm of each acrocentric chromosome of both HG002 haplotypes, telomere to the start of its active HOR array, is cut into non-overlapping 100 kb tiles (tiles with fewer than 90% called bases, the unresolved rDNA gaps, are dropped) and placed with every method on the diploid assembly, on the paternal haplotype (maternal tiles) and on CHM13, with the aligners' asm20 presets. The reference standard across genomes is the position lifted through a chromosome-scale alignment (blocks >= 20 kb at mapq >= 30); the identity margin of a tile is the difference, in matched fraction, between the best and the second-best CHM13 short arm among every alignment minimap2 reports for it.

D=out/v4/acro; B=data/hg002v1.1.cenSatv2.0.bed
bench/acro_tiles.py --fasta data/hg002v1.1.mat.fa.gz --bed $B --len 100000 --prefix acromat_ --out $D/tiles_mat_L100k.fa
bench/acro_tiles.py --fasta data/hg002v1.1.pat.fa.gz --bed $B --len 100000 --prefix acropat_ --out $D/tiles_pat_L100k.fa
cat $D/tiles_mat_L100k.fa $D/tiles_pat_L100k.fa > $D/tiles_L100k.fa
samtools faidx data/hg002v1.1.mat.fa.gz chr13_MATERNAL chr14_MATERNAL chr15_MATERNAL chr21_MATERNAL chr22_MATERNAL > $D/hg002_acro_chroms.fa
samtools faidx data/hg002v1.1.pat.fa.gz chr13_PATERNAL chr14_PATERNAL chr15_PATERNAL chr21_PATERNAL chr22_PATERNAL >> $D/hg002_acro_chroms.fa
samtools faidx data/chm13v2.0.fa.gz chr13:1-15547593 chr14:1-10092112 chr15:1-16678794 chr21:1-10962853 chr22:1-12788180 | sed -E 's/^>(chr[0-9]+):.*/>\1/' > $D/chm13_parms.fa
minimap2 -t 6 -cx asm20 --secondary=no data/chm13v2.0.fa.gz $D/hg002_acro_chroms.fa > $D/hg002acro_vs_chm13.paf   # reference standard
minimap2 -t 2 -cx asm20 -N 20 -p 0.5 --secondary=yes $D/chm13_parms.fa $D/tiles_L100k.fa > $D/tiles_vs_chm13parms.paf   # margins
samtools faidx data/hg002v1.1.pat.fa.gz chr13_PATERNAL chr14_PATERNAL chr15_PATERNAL chr21_PATERNAL chr22_PATERNAL > $D/hg002_acro_pat.fa; samtools faidx data/hg002v1.1.mat.fa.gz chr13_MATERNAL chr14_MATERNAL chr15_MATERNAL chr21_MATERNAL chr22_MATERNAL > $D/hg002_acro_mat.fa
minimap2 -t 8 -cx asm20 -N 20 -p 0.5 --secondary=yes $D/hg002_acro_pat.fa $D/tiles_mat_L100k.fa > $D/mat_vs_patacro.paf   # nearest copy on the other haplotype
minimap2 -t 8 -cx asm20 -N 20 -p 0.5 --secondary=yes $D/hg002_acro_mat.fa $D/tiles_pat_L100k.fa > $D/pat_vs_matacro.paf
cat $D/mat_vs_patacro.paf $D/pat_vs_matacro.paf > $D/tiles_vs_otherhap.paf
minimap2 -t 4 -cx asm20 --secondary=no data/hg002v1.1.pat.fa.gz $D/hg002_acro_mat.fa > $D/matacro_vs_pat.paf   # reference standard across haplotypes
for ref in data/hg002v1.1.pat.fa.gz data/chm13v2.0.fa.gz; do   # Winnowmap2 k=19 lists, as for the diploid assembly
  tools/winnowmap/meryl count k=19 output mdb $ref memory=8 threads=6 && tools/winnowmap/meryl print greater-than distinct=0.9998 mdb > $D/$(basename $ref .fa.gz)_repetitive_k19.txt; rm -rf mdb
done
WM_K19=out/v2/meryl/hg002v11_repetitive_k19.txt bench/run_methods.sh $D/tiles_L100k.fa     data/hg002v1.1.fasta.gz data/hg002v1.1.skx     $D/same 8 asm
WM_K19=$D/pat_repetitive_k19.txt                bench/run_methods.sh $D/tiles_mat_L100k.fa data/hg002v1.1.pat.fa.gz data/hg002v1.1.pat.skx $D/xhap 8 asm
WM_K19=$D/chm13_repetitive_k19.txt              bench/run_methods.sh $D/tiles_L100k.fa     data/chm13v2.0.fa.gz    data/chm13v2.0.skx    $D/xind 8 asm
P="--placed sketchy=SET/sketchy.tsv --placed minimap2=SET/minimap2_asm20.paf --placed winnowmap2=SET/winnowmap2_asm20.paf --placed mashmap3=SET/mashmap3.paf"
bench/score_acro.py --setting same --queries $D/tiles_L100k.fa     --target-bed $B --liftover out/v2/xhap/mat_vs_pat.paf --margin-same $D/tiles_vs_otherhap.paf ${P//SET/$D/same} --tiles-out $D/same_tiles.tsv --md $D/same.md
bench/score_acro.py --setting xhap --queries $D/tiles_mat_L100k.fa --target-bed $B --liftover $D/matacro_vs_pat.paf ${P//SET/$D/xhap} --tiles-out $D/xhap_tiles.tsv --md $D/xhap.md
bench/score_acro.py --setting xind --queries $D/tiles_L100k.fa --target-bed data/chm13v2.0_censat_v2.1.bed --liftover $D/hg002acro_vs_chm13.paf --margin $D/tiles_vs_chm13parms.paf ${P//SET/$D/xind} --tiles-out $D/xind_tiles.tsv --md $D/xind.md
bench/fig_acro.py --tiles $D/xind_tiles.tsv --out docs/v2/fig_acro_xind.png; bench/fig_acro.py --tiles $D/xhap_tiles.tsv --out docs/v2/fig_acro_xhap.png

v0.1's HPRC benchmarks (truth from minimap2 -cx asm20 -N 30 -p 0.5 against CHM13, run once and kept in out/*_truth.tsv) are re-scored with bench/score_v2.py <placed.tsv> --truth <truth.tsv> --tol 100000; the truth-free walk is bench/make_walk.py + bench/sweep.py run/score exactly as in v0.1, with this version's locate in bench/sweep_methods.json.


Written by Claude Fable 5.1 (Anthropic) with @jlanej, September 2026; v0.1 methods by Claude Opus 5 and @jlanej.