The other axis

A bound that has to be paid for

The pruning removes seventy per cent of a search tree for seventy-two rank operations. It also needs an FM-index of the reversed text — 17,033 bits against the forward index's 17,032 — which doubles the structure whose small size was the entire argument for walking an index.

A search that walks an index rather than filling a table has one argument in its favour and it is not speed. It is that the index is small: the text that does not have to be kept is the essay about what that means, and the whole field of self-indexing is built on it.

The pruning in the branch that cannot reach an answer removes seventy per cent of the search tree. This essay is what it costs, and the cost is in the one currency the structure was chosen for.

What the pruning costs, in the currency it saves in and in the one it does notOne search at k = 3 on 4,000 characters. Computing D is 72 rank operations over the pattern; it removes 27,906 interval extensions from the tree and leaves 12,051. In that currency the bound is 388 extensions removed per rank spent. The currency it does not save in is space: D is computed in an index of the reversed text, which is 17,033 bits against the forward index's 17,033 — the same structure again, for a pruning.ranks to compute D72extensions removed27,906extensions remaining12,051one search · k = 3 · 4,000 charactersand one more index: 17,033 bits4,000 characters · m = 16388 extensions a rank
Fig. 1 Three columns and they are not summed: what the bound costs in operations, what it removes, and what is left. The fourth number — the second index — is in the caption because it is not in the same units as anything on the plate.

The bill in operations, which is trivial

Seventy-two rank operations over a sixteen-character pattern, and they remove 27,906 interval extensions at a budget of three. That is 388 extensions removed per rank spent.

There is no exchange rate under which that is a bad trade, and it is worth checking why rather than accepting it: an interval extension is two rank operations on the same wavelet tree plus a comparison, so the two acts differ by a small constant and not by a class. A trade of 388 to one between two acts within a factor of three of each other is a trade under any accounting.

A rank and an interval extension are close relatives — an extension is two ranks plus some arithmetic — so the two acts are within a small factor of each other, and 388 to one survives any reasonable adjustment.

That is unusual on this site and worth saying when it happens. Most of the trades here need a plate because the answer depends on which resource the reader is short of; this one does not.

The bill in bits, which is not

Computing DD requires extending the pattern to the right while maintaining the interval of text positions where it occurs. An FM-index extends to the left. So the bound needs a second index, over the same text read backwards.

Measured: 17,033 bits for the reverse index against 17,032 for the forward one. The structure doubles.

That is not an implementation detail that a cleverer version would avoid. The two indexes are over different strings — a text and its reverse — and neither can answer the other’s questions. There is a published structure that does both, the bidirectional index, and it is not free either: it stores the transform of both strings and adds machinery to keep two intervals in step.

What the bound removes from the search treeInterval extensions performed by the walk at each budget, with the bound and without it, on 4,000 characters over 4 symbols. The unpruned tree runs 16 to 39,957; the pruned one runs 16 to 12,051, which is 69.8% of the tree removed at k = 3. Both find the same 7 positions — the sweep refuses to report a row where they differ. The vertical axis is logarithmic.010010³10⁴interval extensions48.1%68.5%69.8%errors allowed · share removed belowno pruningpruned on D4,000 characters · m = 1669.8% removed at k = 3
Fig. 2 What the second index buys, for reference: the tree with the bound and without it, at four budgets.

Why doubling the index is worse than it sounds

Because of what the structure was chosen for.

A bounded-error search can be answered three ways, and this collection has built all three. Fill the whole dynamic-programming table, which is n×mn \times m cells and needs the text. Use a filter to propose candidate windows and verify them, which needs the text. Or walk an index, which needs no text at all.

The third is not chosen because it is fastest — the search that spends a budget measures the crossing where it stops being so, and it is at a budget of four. It is chosen because the index is smaller than the text and answers without it. A version of it that carries two indexes has spent the advantage it was selected for.

The arithmetic: the forward index is 17,032 bits over three thousand characters of four-symbol text, and the packed text is 6,000 bits. So one index is 2.8 times the packed text, and two are 5.7 times. Against a table-filling search that keeps 6,000 bits of text and allocates a row at a time, the “small” structure is now nearly six times the size.

This is the trade an index larger than what it indexes is about, arriving from an unexpected direction: not because the index is badly built, but because a pruning needed a second copy of it.

Three currencies, and the exchange rates are not available

The filter strand ended with a four-way plate and no winner, for a reason three savings in three currencies states: a counting filter saves cells, a seed filter saves text reads, an index walk saves both while spending ranks, and there is no rate between a cell, a read and a rank that this collection is willing to assert.

The pruning adds a fourth column to that plate and it is the only one denominated in bits. Ranks removed and bits added cannot be compared at all: one is a cost per query and the other a cost per structure, so the exchange rate between them is the number of queries — which is the reader’s, not the measurement’s.

The honest form of the result is therefore a break-even: the pruning pays when the number of queries times the operations saved exceeds whatever the reader charges for 17,033 extra bits. At a budget of three that is 27,906 operations a query, so a reader running a thousand queries saves twenty-eight million operations for two kilobytes. Almost anyone would take it. A reader running one query would not.

That framing is the same one the rule that pays on a long enough text uses for a precomputation against a scan, and the same one what a second pass buys uses for a pass against a bound. It is the standard shape for a fixed cost against a repeated one, and the only thing that makes it hard here is that the fixed cost is space rather than time.

The sizes, itemised

The reverse index is the same class of object as the forward one, so its parts are the same parts, and the near-identity of the two totals is worth a moment.

An FM-index over nn characters holds a wavelet tree over the transform, the rank directories that make the tree answerable, a bit vector marking the sampled rows, the sampled positions themselves, and a table of first-column offsets. The transform of a string and the transform of its reverse are different permutations of the same multiset of characters, so the wavelet tree’s payload is identical — the same symbol frequencies give the same Huffman shape and the same total code length.

What differs is the run structure, and with plain bit vectors nothing charges for runs. So the two totals come out one bit apart, and that one bit is a rounding in the sample marking.

That is a small illustration of a result this field already has: the transform that emits nothing measures that the Burrows-Wheeler transform changes the size of a plain index by nothing at all, because it is a permutation and a plain bit vector charges one bit per bit. Reversing the text is another permutation, and it changes nothing either. With compressed bit vectors the two would differ, by however much the two texts’ run structures differ.

What could be computed without the second index, and what could not

Worth asking, because “a second index is required” is the kind of claim that is often an implementation talking.

A bound on the pattern’s suffixes needs no second index: the forward index extends leftwards, so reading the pattern right to left and counting the resets gives a lower bound on the errors in each suffix, for free. It is a perfectly good bound. It bounds the wrong thing — the walk consumes the pattern from the right, so what it has left is a prefix — and using it prunes states that hold answers, which the fourth essay on this ladder measures.

A bound on the prefixes computed some other way would need to know, for each prefix, whether it occurs in the text. Nothing in the forward index answers that without extending rightwards. A suffix array would, at nlog2nn\lceil\log_2 n\rceil bits and a text; a suffix automaton would, at more; a qq-gram table would give a weaker bound at σqlog2n\sigma^q\lceil\log_2 n\rceil bits.

So the second index is not the cheapest possible way to compute the bound, but every alternative is a second structure of comparable size, and the reverse index is the one that reuses code that already exists and is checked. The claim that survives is: a bound on prefixes requires a structure that can be extended rightwards, and the forward index is not one.

What a bidirectional index would change

The published alternative deserves a paragraph, because it changes the accounting rather than removing the cost.

A bidirectional FM-index maintains two intervals — one in the forward index and one in the reverse — and can extend a pattern in either direction. It costs both transforms, so the payload is the same two structures; what it saves is the sampling and some of the machinery, and it buys the ability to compute the bound and run the search over one object.

More interestingly, it makes a different search possible: a bidirectional walk can start in the middle of the pattern and grow outwards, which is a strictly better search order for a bounded-error walk because it reaches the pruning’s evidence sooner. That is not built here and it is the most obviously valuable thing this ladder does not have.

What it does not do is make the bound free. The information the bound is computed from — where each piece of the pattern stops occurring — requires the reverse transform, and the reverse transform is the cost.

The bound is worth almost nothing on two symbols and almost everything on twenty-sixThe share of the search tree the bound removes, at k = 2 on 3,000 characters. Over 2 symbols it removes 0.8% — a piece of the pattern almost always occurs somewhere in a binary text, so the interval never empties and D stays at zero — and over 26 it removes 80.5%. The same property that makes a wide alphabet branch more at every state is what makes its intervals empty sooner, and the pruning is the second effect cashed in against the first.2 symbols0.8%5,396 → 5,3514 symbols70.8%3,742 → 1,09226 symbols80.5%10,348 → 2,020share of the tree the bound removes · k = 23,000 characters · m = 140.8% to 80.5%
Fig. 3 And the case where the second index buys nothing at all: on a binary alphabet the bound removes 0.8% of the tree, for the same doubling of the structure.

The reverse index is not a copy of the forward one

A small point that decides whether the doubling is avoidable, and it is not.

The reverse index is not the forward index read backwards, and it cannot be derived from it cheaply. The Burrows-Wheeler transform of a string sorts that string’s rotations; the transform of the reversed string sorts a different set of rotations, and there is no cheap map between the two orders. Reconstructing one from the other means recovering the text and transforming it again, which is the whole construction.

So a reader who wants the bound builds two transforms, sorts two suffix arrays, and stores two wavelet trees. Nothing about the two structures is shared except the alphabet and the symbol frequencies.

That is the sharpest form of the essay’s claim. The doubling is not an artefact of keeping two objects where one would do; it is that the bound is computed from information the forward index does not contain, and the information is exactly as large as the index.

The case where it is unambiguously the wrong trade

Two symbols. The bound removes 0.8% of the search tree and costs 17,033 bits.

That is not a marginal call, it is a clear loss, and it is worth having a case like that in a ladder about a technique. A pruning whose value depends on the alphabet has a range over which it is worth its space and a range over which it is not, and the boundary is where the saved operations times the query count crosses the space charge.

On two symbols the saved operations are 45 per query. On twenty-six they are 8,328. A reader with a binary alphabet — a bit string, an indicator sequence, a compressed representation — should not build this, and nothing in the published accounts of the technique says so, because the published accounts were written for a four-symbol alphabet where the answer is comfortably yes.

What the space buys when it is spent on something else

A fair comparison needs the alternative use of the bits, and there is an obvious one: spend them on a denser suffix-array sampling instead.

The forward index’s sampling rate is the dial the sampling that goes the other way measures. At rate 32 the sampled positions are n/32n/32 values of log2n\lceil\log_2 n\rceil bits — about 1,100 bits of the 17,032 here — and halving the rate to 16 doubles that and halves the walk each located occurrence performs.

So 17,033 bits would buy a sampling rate of about one in two, which would make locating essentially free. That is a real alternative and it is worth naming, because a reader with a fixed space budget is choosing between accelerations rather than deciding whether to accelerate.

Which is better depends on the query. A search with a large budget and few occurrences spends its time in the tree, and the pruning wins by a wide margin. A search with a small budget and many occurrences spends its time locating, and the sampling wins. This collection has both dials measured now, on the same index, in the same units — which is the point of building the structures rather than citing them.

A lower bound on the errors the rest of the pattern must costThe pattern "atgaattcatgagtgacaag" against 3,000 characters. The step line is D — computed in one pass by extending the pattern in an index of the reversed text and counting the points where the interval empties, since a piece occurring nowhere must hold an error. The upper line is the least number of errors any alignment of that prefix really needs, from the quadratic table. D never exceeds it, which is what makes pruning on it safe; it is tight at every position here. The bound cost 90 rank operations and 2 resets.atgaattcatgagtgacaag00000111111112222222errorsthe pattern, left to right · D belowleast errors neededD, the bound4 symbols · m = 2090 ranks · 2 resets
Fig. 4 The bound the bits buy, drawn along one pattern. Everything in this essay is the price of computing that step function.

What the plate has to show

Three bars and a caption, and the caption carries the bits.

The bars are in one currency — operations — and the bits are not in it, so putting them on the same axis would require an exchange rate and would be exactly the error the previous ladder is about. Putting them in the caption is a compromise and it is the site’s convention: a number that belongs to the argument and not to the axis goes in the prose.

What must not happen is the bits being left out. A plate showing 72 ranks removing 27,906 extensions is a plate showing a technique that is free, and the technique is not free — it is the most expensive thing on this ladder in the currency the whole structure was chosen for.

The plate this ladder ends on

Four numbers, and no single one of them is the answer.

What the bound removes from the search treeInterval extensions performed by the walk at each budget, with the bound and without it, on 4,000 characters over 26 symbols. The unpruned tree runs 16 to 187,692; the pruned one runs 16 to 24,254, which is 87.1% of the tree removed at k = 3. Both find the same 7 positions — the sweep refuses to report a row where they differ. The vertical axis is logarithmic.010010³10⁴10⁵interval extensions33.3%41.0%87.1%errors allowed · share removed belowno pruningpruned on D4,000 characters · m = 1687.1% removed at k = 3
Fig. 5 The tree with and without the bound on a twenty-six symbol alphabet, where the second index is most obviously worth its bits.

The tree removed, the ranks spent, the bits added and the alphabet the whole thing is a function of. A reader with a wide alphabet and many queries should build it; a reader with a binary alphabet should not; a reader in between should measure the share removed on their own data, which is one search with the bound and one without.

That is a weaker recommendation than “use the pruning” and it is the one the measurements support. This collection’s habit is to end on the parameter rather than on the verdict, because the verdict is the reader’s and the parameter is what a measurement can supply.

Four ways to index 16,384 characters, weighedEach bar is what the structure retains, computed from its own shape rather than from a serialisation, on English-like of 16,384 characters. The dashed rule is the packed text at 81,920 bits. All four return the same occurrences for the same pattern; they differ in size by 147 times. One of them cannot answer at all unless the text is kept beside it, and its bar includes that text.suffix array + text311,29619.00 b/chcounter array per symbol5,407,710330.06 b/chFM-index, plain100,9476.16 b/chFM-index, compressed36,8042.25 b/chthe packed textone bar shaded darker needs the text · English-likesigma 21, sample 64
Fig. 6 The structures the index-walk was chosen over, by size. Two of them keep the text; the walk keeps two indexes instead.
The states that survive, and the answers they carryEach budget's surviving leaves — intervals of rows reached with the pattern exhausted — against the distinct occurrences they name. The gap between them is the duplication the walk cannot avoid: a string within k errors of the pattern is reachable by several sequences of moves, and every one of them is walked. The vertical axis is logarithmic.110errors allowed, kleaves · occurrences0123leaves reachedoccurrences4,000 characters · m = 16 · 4 symbolsno crossing in range
Fig. 7 What the walk produces for its work — intervals of rows reached within the budget — which the pruning does not change.

Stored bits and working set are not the same doubling

The doubling is real and it is worth asking when the second index is touched, because the answer changes which resource is doubled.

The bound is computed once per pattern, in seventy-two ranks, before the walk starts. After that the walk extends intervals in the forward index and consults an array of DD values; it never returns to the reverse structure at all.

So the reverse index is a preprocessing structure, not a query structure. It is read for a few dozen operations per pattern and then idle for however many thousand extensions the walk performs — which means it does not have to be resident while the search runs. It can page from disk, live on a slower tier, or be shared between processes searching the same text, and none of that touches the walk’s cost.

The doubling is in stored bits and not in working set, and the two are different arguments. The index is small usually means it fits in memory, which is what the text that does not have to be kept establishes a self-index for; the 5.7 times the packed text is a storage figure, and the memory figure stays at 2.8 plus whatever the seventy-two ranks touch.

And for a fixed pattern set the reverse index can be discarded entirely. Compute every pattern’s DD array once, keep the arrays — a few bytes per pattern — and delete the second structure. That is the case a signature scanner or a read aligner is in, and it turns a permanent doubling into a build-time one.

Which does not make the trade free, and it moves it into the currency this ladder is better at. What remains is bits on disk against operations at query time, rather than memory against memory — and three savings in three currencies is the reason to say which of the two the doubling was in before deciding it is too expensive.

What is being claimed

The bound costs 72 rank operations and removes 27,906 interval extensions, which is 388 to one and needs no exchange rate to read.

It also costs a second FM-index, 17,033 bits against the forward index’s 17,032, because the bound extends the pattern rightwards and a backward search extends leftwards.

That doubles the structure whose small size was the argument for the search, taking it from 2.8 times the packed text to 5.7.

So the result is a break-even in queries rather than a saving: the fixed cost is space and the variable cost is operations, and the rate between them is how many times the index is searched. A structure built once and searched a million times is a different object from one built once and searched once, and nothing in a bound or a tree count distinguishes them.

And on a binary alphabet it is a clear loss — 0.8% of the tree, for the same doubling. That is the reading this ladder most wants a reader to keep: the technique is excellent, its published accounts are written from a four-symbol world, and the parameter that decides it is one those accounts hold fixed.

Where walking the index stops being cheaper than filling the rectangleThe whole table costs 48,000 cells at every budget, because its size is the text times the pattern and neither depends on k. The unpruned walk passes it at k = 4 and the pruned walk at k = 4, counting the bound's own ranks against it. The crossing does not move: the tree multiplies by an order of magnitude per error and the pruning removes a constant share of it, which buys a fraction of one budget. The vertical axis is logarithmic.010010³10⁴10⁵operationserrors allowedno pruningprunedthe whole table3,000 characters · m = 16crossing at k = 4
Fig. 8 The comparison that is unaffected by any of this: the walk against a table that keeps the text. The pruning changes what the walk costs and not what it is.

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

Every essay whose body links to this one.

The objects this essay names

Each one links to every other essay that touches it.

Backtracking searchError budgetFM-indexIndex sizeLower boundMeasurementPruningReverse indexSelf-indexSpace overheadTrade offUnit of cost