What is taught wrongly

The filter that proposes everything

Seed-and-extend saves two thousand times the work at zero errors and costs more than doing nothing at four. Between them the selectivity falls through the floor, and where it falls is set by two numbers that can be computed before the filter is run — one of which does not contain the length of the text at all.

The same pattern, the same text, the same filter. Only the number of errors allowed changes.

kk seeds of candidates answers cells computed against the whole table
0 24 3 3 210 2,286× better
1 12 9 9 1,044 460×
2 8 20 15 3,220 149×
3 6 154 21 31,570 15.2×
4 4 2,679 27 662,490 0.7×
6 3 13,436 41 4,443,748 0.11×
7 3 16,142 75 5,984,256 0.08×

At four errors the filter proposes 2,679 of the 20,000 positions and the machinery of filtering costs 38% more than filling the rectangle it was avoiding. At seven it costs twelve times as much.

What the filter computes, against what it was avoidingThe flat line is the whole table: 480,000 cells, a pattern of 24 against 20,000 characters, and it does not move with k. The rising curve is what the filter computes — a banded table at every candidate — and it crosses the flat line at k = 4, where the seeds have fallen to 4 characters. Below that crossing the filter saves up to 2285.71x; above it, running the filter and then verifying its candidates costs more than filling the rectangle would have. The k axis is drawn as k+1 so that k = 0 has a place on a logarithmic scale.010³10⁴10⁵10⁶errors allowed, k+1table cells computedthe whole table: 480,0002412864433k = 4filter and verifynumbers above the points are the seed lengthscrossing at k = 4
Fig. 1 What filtering computes against what it was avoiding. The flat line is the whole table, which does not move with k. The numbers above the points are the seed lengths, and they are the thing that is actually moving.

Nothing in the filter has gone wrong. Every candidate is correct, every occurrence is found, the pigeonhole holds at every row. What has happened is arithmetic, and it has two thresholds in it.

The three stages, and both of them grow

It helps to see where the work goes, because the collapse is usually described as though the verification stage were the only casualty.

kk seed index ranks candidates verification cells
0 24 297 3 210
2 8 832 20 3,220
3 6 2,545 154 31,570
4 4 21,531 2,679 662,490
7 3 171,675 16,142 5,984,256

Both sides rise by nearly three orders of magnitude, and the index side rises for a reason this field has already measured: finding a seed is cheap and locating its occurrences is not. Counting the occurrences of a seed is about two ranks per character and is independent of how many there are; turning each occurrence into a text position is a walk to a sampled row, and that walk costs 860 times what the count does.

So a filter over a self-index pays the locate cost per seed occurrence, and seed occurrences are exactly the quantity that explodes. The filter stage is not immune to its own selectivity; it is charged by it twice, once by the walk and once by the verification downstream.

What never fails is the search itself. Backward search finds every exact occurrence of every seed in time proportional to the seed length, on a structure that has not changed and does not care. Everything that goes wrong goes wrong in what is done with the answer.

The seed length is the only variable

The pattern is 24 characters and the pieces are 24/(k+1)\lfloor 24/(k+1)\rfloor characters each. So the error budget does not act on the filter directly; it acts by shortening the seeds.

s=mk+1s = \left\lfloor \frac{m}{k+1} \right\rfloor

And a seed of length ss drawn from a text of nn characters over σ\sigma symbols is expected to occur

nσs\frac{n}{\sigma^{s}}

times by chance. That is exponential in ss and ss falls like 1/k1/k, so the chance hits rise doubly-exponentially in the error budget. Between k=3k=3 and k=4k=4 the seed goes from six characters to four, which multiplies the expected chance hits by σ2=16\sigma^2 = 16, and the candidate count goes from 154 to 2,679.

The fraction of the text a filter proposes, against the length of its seedsA pattern of 24 characters cut into k+1 pieces, over 20,000 characters of four symbols. The dashed rule on the right is the seed length at which a seed is expected to occur once by chance, log_4 n = 7.14: above it the filter proposes essentially only the real occurrences. The dashed rule on the left is where the expected proposals reach the whole text. The measured fraction crosses a half at 3 characters, which is 6 errors. The open rings are the closed form (k+1)(2k+1)/sigma^s, in which n does not appear — a longer text does not dilute this. Both axes are logarithmic.100.0010.010.1fraction of the text proposedlog_4 n = 7.101234,56,7seed length, characters · errors allowed abovepattern 24 · text 20,000 · four symbolsrings: the closed form
Fig. 2 The fraction of the text proposed, against the seed length. The rings are a closed form with no measurement in it; the two dashed rules are the thresholds derived below.

The first threshold: where chance hits appear at all

The chance term crosses one when σsn\sigma^s \approx n, that is

slogσn=log420,000=7.14s \approx \log_\sigma n = \log_4 20{,}000 = 7.14

Above it a seed is expected to occur less than once by accident, so essentially every candidate comes from a real occurrence and the selectivity is near 100%. The measurement agrees: at s=8s = 8 the filter proposes 20 candidates of which 15 are answers; at s=12s = 12 and s=24s = 24 every candidate is an answer.

This is the threshold that is usually quoted, and quoting it alone is what makes people think the filter degrades gracefully. It does not describe the collapse; it describes where the collapse begins.

The second threshold: where it proposes the whole text

The candidate count is the seed hits times the widening. With k+1k+1 seeds and 2k+12k+1 candidate starts per hit:

candidates(k+1)(2k+1)nσs\text{candidates} \approx (k+1)(2k+1)\,\frac{n}{\sigma^{s}}

Divide by nn to get the fraction of the text proposed:

candidatesn(k+1)(2k+1)σs\frac{\text{candidates}}{n} \approx \frac{(k+1)(2k+1)}{\sigma^{s}}

The nn cancels. The fraction of the text a filter proposes is a property of the pattern, the error budget and the alphabet, and a longer text does not dilute it. A filter that proposes 13% of twenty thousand characters proposes 13% of twenty million.

That fraction reaches one at

slogσ ⁣((k+1)(2k+1))s \approx \log_\sigma\!\big((k+1)(2k+1)\big)

which for k=6k=6 over four symbols is log491=3.25\log_4 91 = 3.25, and the seed at k=6k=6 is 3. Measured: the filter proposes 67% of the text.

kk seed predicted fraction measured fraction
3 6 0.68% 0.77%
4 4 17.6% 13.4%
5 4 25.8% 22.7%
6 3 142% 67.2%
7 3 188% 80.7%

The prediction tracks the measurement until it exceeds one, at which point it must stop: a prediction of 142% is a count of proposals, and the measurement is a set of distinct positions. The proposals collide, so the set saturates. The two quantities agree exactly as long as they are the same quantity, which is a check on the arithmetic rather than a defect in it.

The prediction has a second term and it is the one that is dropped

The formula above is the chance term. There is another, and leaving it out makes the filter look several times worse than it is exactly where it is working.

At a real occurrence, all k+1k+1 seeds fire and they all point at the same start. So a genuine occurrence contributes 2k+12k+1 candidates, not (k+1)(2k+1)(k+1)(2k+1). The full prediction is

candidateso(2k+1)the occurrences  +  (k+1)(2k+1)nσschance\text{candidates} \approx \underbrace{o\,(2k+1)}_{\text{the occurrences}} \;+\; \underbrace{(k+1)(2k+1)\frac{n}{\sigma^{s}}}_{\text{chance}}

for oo occurrences. On this text with three planted occurrences:

kk planted term chance term predicted measured
2 15 4.6 19.6 20
3 21 136.7 157.7 154
4 27 3,515.6 3,542.6 2,679
The candidate count, measured against the count computed before the runTwo terms, and the second is the one that is usually quoted alone. The chance term is (k+1)(2k+1)·n/sigma^s, from the seeds that occur by accident; the planted term is (2k+1) per real occurrence, and it is not multiplied by the number of pieces because at a real occurrence all k+1 pieces point at the same place. The rules mark the prediction and the bars are what was measured; the worst departure is 57.0%. Predicting the chance term alone makes the filter look several times worse than it is exactly where it is working.bars measured, rules predictedk = 0, seeds of 243predicted 3k = 1, seeds of 129predicted 9k = 2, seeds of 820predicted 20k = 3, seeds of 6154predicted 158k = 4, seeds of 42,679predicted 3,543k = 5, seeds of 44,532predicted 5,189k = 6, seeds of 313,436predicted 28,477k = 7, seeds of 316,142predicted 37,545pattern 24 · text 20,000 · rules mark the predictionworst departure 57.0%
Fig. 3 The measured candidate count against the count computed before the run. The rules mark the prediction and the bars are the measurement; the worst departure on the useful side of the threshold is under a quarter.

At k=2k=2 the planted term is three quarters of the prediction. Dropping it would predict 4.6 candidates against 20 measured — a factor of four out — and the natural conclusion from that would be that the model is wrong. The model is fine; the term nobody writes down is the one that dominates when the filter is doing its job.

And a check built only on the chance term would be passed by a filter with no occurrences in its text. That is why the sweep here plants occurrences and requires the answer to be non-empty at every row: a selectivity whose numerator is zero by construction is not a measurement of anything.

The alphabet moves both thresholds

Everything above is over four symbols, and both thresholds are logarithms base σ\sigma. So the alphabet does not shift the collapse, it scales it.

alphabet logσn\log_\sigma n saving at k=3k=3 saving at k=7k=7 fraction proposed at k=7k=7
two symbols 14.3 0.31× 0.06× 100%
four symbols 7.1 15.2× 0.08× 80.7%
twenty-six symbols 3.0 111× 9.6× 0.67%
The fraction of the text a filter proposes, against the length of its seedsA pattern of 24 characters cut into k+1 pieces, over 20,000 characters of twenty-six symbols. The dashed rule on the right is the seed length at which a seed is expected to occur once by chance, log_26 n = 3.04: above it the filter proposes essentially only the real occurrences. The dashed rule on the left is where the expected proposals reach the whole text. Across this whole sweep no seed falls under the lower threshold, so the fraction never reaches a half — the collapse is real and is further out than 7 errors. The open rings are the closed form (k+1)(2k+1)/sigma^s, in which n does not appear — a longer text does not dilute this. Both axes are logarithmic.100.001fraction of the text proposedlog_26 n = 3.001234,56,7seed length, characters · errors allowed abovepattern 24 · text 20,000 · twenty-six symbolsrings: the closed form
Fig. 4 The same sweep over twenty-six symbols, where a seed of three characters still occurs less than twice in twenty thousand and the filter is proposing under one per cent of the text at seven errors. The curve has the same shape and it sits four times further left.

Over two symbols the collapse is nearly immediate. A twelve-character seed at one error is already under log220,000=14.3\log_2 20{,}000 = 14.3, so chance hits appear straight away; by two errors the filter proposes 5.5% of the text and the saving is 2.7×; by three it has lost.

kk over two symbols seed candidates answers saving
1 12 42 9 98.5×
2 8 1,105 21 2.70×
3 6 7,563 103 0.31×
4 4 19,186 753 0.10×
6 3 20,000 9,809 0.07×
The fraction of the text a filter proposes, against the length of its seedsA pattern of 24 characters cut into k+1 pieces, over 20,000 characters of two symbols. The dashed rule on the right is the seed length at which a seed is expected to occur once by chance, log_2 n = 14.29: above it the filter proposes essentially only the real occurrences. The dashed rule on the left is where the expected proposals reach the whole text. The measured fraction crosses a half at 4 characters, which is 4 errors. The open rings are the closed form (k+1)(2k+1)/sigma^s, in which n does not appear — a longer text does not dilute this. Both axes are logarithmic.100.0010.010.11fraction of the text proposedlog_2 n = 14.301234,56,7seed length, characters · errors allowed abovepattern 24 · text 20,000 · two symbolsrings: the closed form
Fig. 5 Over two symbols, where the filter proposes every one of the twenty thousand positions at six errors. The answer set has also grown — 9,809 positions genuinely match within six errors — so this is a case where filtering could not have helped and the question itself was the wrong one.

The last row of that table is worth a second look, because it is a different failure from the ones above it. At six errors over two symbols there are 9,809 genuine occurrences in twenty thousand positions. Half the text matches. A filter proposing everything is not being defeated by chance hits; it is correctly reporting that nearly everything is an answer, and the thing to fix is the query rather than the algorithm.

Which means “the filter proposed everything” has two causes and they need telling apart. One is that the seeds got short and the proposals are noise; the other is that the answer got large and the proposals are right. The selectivity is the same in both cases and the remedy is not: the first is fixed by a longer pattern, the second by asking a different question. The two are distinguished by the numerator — 41 answers in the four-symbol case against 9,809 in the two-symbol one — which is why every plate in this family prints the answer count beside the candidate count.

And the answer to “does seed-and-extend work” is not a property of seed-and-extend. It is a property of mm, kk, σ\sigma and nn, and all four are known before a single index query is made.

Where this leaves the practical matcher

Three things follow, and none of them is that the filter is a bad idea.

The pattern length is the dial, not the error budget. The filter’s health depends on m/(k+1)m/(k+1) against logσn\log_\sigma n, so a caller who wants more errors and keeps the filter can ask for a longer pattern. That is exactly what read aligners do: they will not align a short read at high error rates, and the refusal is arithmetic rather than policy.

pattern length at k=3k=3 seed candidates answers
8 2 17,354 6,945
12 3 7,335 165
16 4 1,899 7
24 6 189 7
32 8 14 7
48 12 7 7
The fraction of the text a filter proposes, against the length of its seedsA pattern of 48 characters cut into k+1 pieces, over 20,000 characters of four symbols. The dashed rule on the right is the seed length at which a seed is expected to occur once by chance, log_4 n = 7.14: above it the filter proposes essentially only the real occurrences. The dashed rule on the left is where the expected proposals reach the whole text. Across this whole sweep no seed falls under the lower threshold, so the fraction never reaches a half — the collapse is real and is further out than 7 errors. The open rings are the closed form (k+1)(2k+1)/sigma^s, in which n does not appear — a longer text does not dilute this. Both axes are logarithmic.100.0010.01fraction of the text proposedlog_4 n = 7.10123456,7seed length, characters · errors allowed abovepattern 48 · text 20,000 · four symbolsrings: the closed form
Fig. 6 A pattern twice as long at the same error budget. The seeds stay above the threshold to five errors instead of two, and the curve does not fall at all across the range the shorter pattern collapsed in.

A filter that has stopped filtering should be turned off, and the condition is computable. The crossing where filter-plus-verify costs more than the rectangle is at roughly n/(2k+1)n/(2k+1) candidates — verifying a candidate is m(2k+3)m(2k+3) cells against the table’s nmnm — and the candidate count itself is predicted by the formula above. Nothing prevents an implementation from computing both and choosing, and this collection is aware of none that does.

A refusal is a legitimate answer and it is what the arithmetic supports. A short read at a high error rate is a query whose filter cannot work and whose whole-table alternative costs nmnm cells over a genome; refusing it is not a limitation of the aligner, it is the only honest response to a question with no cheap answer and, at high enough kk, no small answer either.

And “the filter is exact” is not a defence. Everything here is a correct algorithm returning correct answers. The pigeonhole holds, the verification is real, no occurrence is missed at any row of any table. The failure is entirely one of cost, it is invisible to every correctness test, and it degrades smoothly enough that nobody sees where it started — which is the shape of defect this field exists for.

The shape this is an instance of

Three of the last four fields on this site have produced a structure whose advertised behaviour is a conditional, and each time the condition was left off the advertisement.

A Bloom filter’s false-positive rate is the formula everybody sizes with, and it is right about the wrong quantity. A sketch’s guarantee is one query wide and is quoted as though it covered a table of them. A summary’s rank error is a fraction of something and the something is usually the whole stream when the question was about a tail.

This one has the same shape and a different mechanism. There is no probability in it, no guarantee to misread, and nothing about it is approximate — the filter is exact and its correctness is unconditional. What is conditional is whether it is worth running, and that condition is a piece of arithmetic in four known parameters that no statement of the algorithm includes.

The generalisation worth carrying is the question rather than the threshold. For any filter: what fraction of the input does it propose, at what parameters does that fraction reach one, and is the fraction computable before the filter runs? For seed-and-extend all three have closed-form answers. For most filters the third is the interesting one, because a filter whose selectivity can only be discovered by running it is a filter that cannot be turned off when it stops paying.

The disaster is bounded, and the bound is computable too

The collapse looks unbounded on the plates and it is not. The same four parameters that predict where the filter stops paying also cap how badly it can fail, and the cap is worth having because it changes what kind of risk this is.

The candidate count saturates at nn — there are only nn positions to propose, and the collisions noted above are what make the measurement fall below a prediction that has gone past one. Each candidate costs a banded verification of m(2k+3)m(2k+3) cells. So the filter’s total work, at its very worst, is

nm(2k+3)n\,m\,(2k+3)

against the whole table’s nmnm. The filter cannot cost more than 2k+32k+3 times the thing it was avoiding, whatever the alphabet, the text or the pattern.

The measurements sit under that ceiling and near it once the seeds are short. At seven errors the bound is 17 and the measured cost is 12.5 times the table; at six the bound is 15 and the measurement is 9.3. The gap between them is the saturation — 80.7% of positions proposed rather than 100% — plus the positions near the ends of the text where a band is clipped.

That is a different kind of risk from the one the falling curves suggest. A filter whose worst case were unbounded would be a hazard: a query with the wrong parameters could run for arbitrarily long. This one degrades to a bounded multiple of the exhaustive method, the multiple is linear in the error budget rather than exponential, and it is known before the query starts. A cost ceiling that can be computed from the query is the difference between a system that must refuse and one that may simply be slow, and it is the strongest argument for keeping the filter switched on even past the crossing — provided the caller is told which side of it they are on.

Making the cells cheaper does not move the crossing

The natural response to the crossing is to make verification faster, and the natural way to do that on this site is a column computed in machine words, which evaluates a whole column of the table in a handful of word operations instead of one operation per cell.

It does not move the crossing at all, and the reason is worth stating because it applies to every optimisation of this shape.

The crossing is a ratio of two cell counts. Bit-parallel evaluation is a discount on the price of a cell, and both sides of the ratio are made of the same cells — the filter’s bands and the whole table alike. Applying it to the verification and not to the baseline would be comparing an optimised implementation against an unoptimised one, which is the commonest way a benchmark flatters a filter. Applying it to both leaves the ratio exactly where it was. The absolute numbers fall by a large factor, every curve on every plate shifts down together, and the point where they cross does not move a millimetre.

So only two things can move it, and they are both changes to the structure of the comparison rather than to the speed of the arithmetic.

Fewer candidates. Longer patterns, a larger alphabet, a filter with a better selectivity at the same seed length — everything the sections above are about. This is the lever that works and it works on the term that is exploding.

A discount the baseline cannot have. Verification of one candidate may stop the moment the band’s best surviving cell exceeds kk, because that candidate is then known not to be an answer. The full table has no such exit: it must report every position within kk, so it cannot abandon a column on the grounds that one alignment has failed. That asymmetry is real, it is not measured here, and it is the only per-cell change that genuinely favours the filter — which makes early abandonment worth more to a seed-and-extend matcher than any amount of vectorisation.

The general form is the one to carry away. An optimisation that applies to both arms of a comparison changes the numbers and not the decision, and a great many reported improvements to filtering are of exactly that kind. The question to ask of any such change is not how much faster it made the filter, but whether it could have been applied to what the filter was avoiding.

What is not measured here

Other filters, which have different thresholds. A qq-gram counting filter, a filter that walks the index with an error budget, a filter over overlapping seeds rather than a partition — each has its own arithmetic and each of them fails somewhere. What generalises is not the threshold but the question: what fraction of the input does this filter propose, and at what parameters does that fraction reach one?

The distribution of the text. Every text here is drawn uniformly, so n/σsn/\sigma^s is exactly right. Real text is not uniform, and a seed drawn from a common region occurs far more often than the formula says while a seed from a rare region occurs less — so the variance of the candidate count on real data is larger than anything here shows, and the mean is not where the trouble is.

The other end. At k=0k=0 the filter is the index answering the question and the table is not used at all. The 2,286× saving in the first row is therefore not a filter’s saving; it is the saving of not needing a filter, and it belongs to the index rather than to this algorithm.

The funnel at 6 errors: 20,000 positions to 14 answersA pattern of 24 characters over four symbols, allowed 6 errors. The index proposes 13,422 starting positions out of 20,000; each is verified by a banded table of at most 24x36 cells; 14 survive. The whole thing computes 4,439,114 table cells against the 480,000 of filling the rectangle over the whole text — a factor of 0.1. The selectivity, which is the only number that says whether the filter was worth running, is 0.1%.pattern of 24, 6 errors allowedpositions in the text20,000candidates proposed13,422candidates verified13,422occurrences14pattern 24 · 6 errors · 4,439,114 cells against 480,000selectivity 0.1%
Fig. 7 The funnel at six errors, where the four stages have nearly collapsed into two. Thirteen thousand of twenty thousand positions are proposed and forty-one are answers; the shape that made the first plate an argument has become a slope.
The candidate count, measured against the count computed before the runTwo terms, and the second is the one that is usually quoted alone. The chance term is (k+1)(2k+1)·n/sigma^s, from the seeds that occur by accident; the planted term is (2k+1) per real occurrence, and it is not multiplied by the number of pieces because at a real occurrence all k+1 pieces point at the same place. The rules mark the prediction and the bars are what was measured; the worst departure is 32.2%. Predicting the chance term alone makes the filter look several times worse than it is exactly where it is working.bars measured, rules predictedk = 0, seeds of 323predicted 3k = 1, seeds of 169predicted 9k = 2, seeds of 1015predicted 15k = 3, seeds of 828predicted 30k = 4, seeds of 6198predicted 247k = 5, seeds of 5896predicted 1,322k = 6, seeds of 45,217predicted 7,148k = 7, seeds of 47,549predicted 9,420pattern 32 · text 20,000 · rules mark the predictionworst departure 32.2%
Fig. 8 The prediction against the measurement on a thirty-two-character pattern, where the seeds stay long enough that both terms of the formula are visible in the same sweep.

What this makes readable

Essays that name this one as a prerequisite.

Named alongside this one

Essays reaching for the same objects. Nobody chose these; they are what the concept index makes visible.

What links here

The 8 essays that link to this one and share the most of its objects, of 14 that link here.

The objects this essay names

Each one links to every other essay that touches it.

AlphabetApproximate matchingClosed formEdit distanceFalse-positiveFilteringFittingHonest limitMeasurementSeed-and-extendSelectivityThreshold