BIO-3-3
Fast heuristic and read-mapping tools
In BIO-3.2 you built the exact machine. Needleman-Wunsch and Smith-Waterman fill a dynamic-programming grid and hand you a provably optimal alignment under your scoring scheme. That guarantee is real, and it is expensive. Aligning a length-m query against a length-n target costs work proportional to m times n. For two short sequences that is nothing. For one query against a database of billions of bases, or for the billions of short reads a sequencer hands you (you will meet those in BIO-4.1), it is a wall you cannot climb.
So the field made a bargain. Give up the guarantee. Keep the speed. Every tool in this lesson is a heuristic: a method that usually finds the right answer fast but promises nothing. This is the single most important mental shift in practical bioinformatics. The exact DP is a law: it tells you the best alignment, full stop. A heuristic is a model: it tells you a good alignment, probably, most of the time, and it can be wrong in ways the DP never is.
Before you leave the exact world behind, run the guaranteed-optimal aligner one more time so you remember exactly what the heuristics are approximating.
matrix[i][j] = max( matrix[i-1][j-1] + s(a[i-1], b[j-1]), // diagonal: align the two residues matrix[i-1][j] + gap, // up: a gap in sequence b matrix[i][j-1] + gap // left: a gap in sequence a ) s(x, y) = match when x == y, else mismatch
Global alignment (Needleman-Wunsch) seeds row 0 and column 0 with k * gap and reads the score from the bottom-right corner.
| - | G | A | T | T | A | C | A | |
|---|---|---|---|---|---|---|---|---|
| - | 0 | -2 | -4 | -6 | -8 | -10 | -12 | -14 |
| G | -2 | 1 | -1 | -3 | -5 | -7 | -9 | -11 |
| C | -4 | -1 | 0 | -2 | -4 | -6 | -6 | -8 |
| A | -6 | -3 | 0 | -1 | -3 | -3 | -5 | -5 |
| T | -8 | -5 | -2 | 1 | 0 | -2 | -4 | -6 |
| G | -10 | -7 | -4 | -1 | 0 | -1 | -3 | -5 |
| C | -12 | -9 | -6 | -3 | -2 | -1 | 0 | -2 |
| U | -14 | -11 | -8 | -5 | -4 | -3 | -2 | -1 |
Write it yourself: the DP fill
The grid above is not magic. It is one nested loop. Given the two sequences and the scores, fill every cell from the three neighbors you already computed, then trace back from the best cell to recover the alignment. Try writing the fill from the recurrence before you read it.
// A thin sketch of needlemanWunsch (global). Local alignment is the same
// loop with a 0 floor and a traceback that starts at the largest cell.
function fill(a: string, b: string, match: number, mismatch: number, gap: number) {
const m = a.length;
const n = b.length;
const matrix: number[][] = [];
for (let i = 0; i <= m; i++) matrix.push(new Array(n + 1).fill(0));
// Seed the borders: a run of k gaps costs k * gap. (Local leaves these at 0.)
for (let i = 1; i <= m; i++) matrix[i][0] = i * gap;
for (let j = 1; j <= n; j++) matrix[0][j] = j * gap;
// Fill. Row i indexes a, column j indexes b.
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
const s = a[i - 1] === b[j - 1] ? match : mismatch;
const diag = matrix[i - 1][j - 1] + s; // align a[i-1] with b[j-1]
const up = matrix[i - 1][j] + gap; // a gap in b
const left = matrix[i][j - 1] + gap; // a gap in a
matrix[i][j] = Math.max(diag, up, left);
// Smith-Waterman (local) instead: Math.max(0, diag, up, left)
}
}
// The global score is the bottom-right corner.
return matrix[m][n];
}BLAST is a search engine over sequences
The Basic Local Alignment Search Tool, BLAST, is the workhorse. You give it a query and a database, and it returns the database entries that look like your query, ranked. It does this without ever aligning your query against the whole database. Instead it does what a web search engine does.
Here is the trick, called seed-and-extend. First BLAST chops your query into short overlapping words of length k, called k-mers (a k-mer is just a substring of length k, so the 3-mers of ACGT are ACG and CGT). It has already chopped the entire database into k-mers too, and stored, for every word, the list of places that word appears. That lookup table, from word to positions, is an inverted index, the same structure that lets a search engine jump from a term to the pages that contain it. To find where your query might match, BLAST does not scan the database. It looks up your query's words in the index and collects the hits. Each hit is a seed: a spot where a short piece of the query already matches the target exactly (or near enough). Then it extends each seed outward, left and right, scoring as it goes, until the running score falls off. The surviving high-scoring stretches are your alignments.
Indexing: k-mers, suffix structures, and the Burrows-Wheeler Transform
The whole speedup lives in the index. Three kinds show up, in rising order of cleverness.
A k-mer index is the hash table above: word to positions. Simple, fast to query, but it only knows about words of one fixed length k, and storing every position for a big genome costs a lot of memory.
A suffix structure indexes every suffix of the text at once. A suffix array is the sorted list of all starting positions of the string's suffixes. Because the suffixes are sorted, you can binary-search for any substring of any length, not just a fixed k. A suffix tree does the same job with more speed and more memory. These answer "where does this pattern occur" for patterns of any length, which a plain k-mer index cannot.
The Burrows-Wheeler Transform, BWT, is the one a systems programmer will actually enjoy. It is a reversible reordering of the string's characters (the same transform inside the bzip2 compressor) that clusters characters appearing in similar contexts, which is what makes the text compress well. The deep result is that you can search the BWT directly, without undoing it, using a companion structure called the FM-index. The FM-index turns "find this pattern" into a short sequence of rank lookups that walk the pattern backward one character at a time, in time proportional to the pattern length and independent of the genome size. And it is small: an FM-index of the entire human genome fits in a couple of gigabytes of RAM. This is the exact move a systems programmer respects, a compression transform that doubles as a searchable index, and it is why the dominant short-read mappers, BWA and Bowtie, are built on the BWT.
Here is the seed idea in code. Read it and trace it by hand. This panel is illustrative, there is no Run button yet, so reason it through rather than expecting output on screen.
def build_kmer_index(reference, k):
# An inverted index: every length-k word to the positions it starts at.
index = {}
for i in range(len(reference) - k + 1):
word = reference[i:i + k]
index.setdefault(word, []).append(i)
return index
def find_seeds(query, index, k):
# Walk the query one k-mer at a time and look each up.
# A hit means this query word occurs in the reference at those spots.
seeds = []
for j in range(len(query) - k + 1):
word = query[j:j + k]
for pos in index.get(word, []):
seeds.append((j, pos)) # (query offset, reference offset)
return seeds
# A seed is only a candidate. BLAST would now EXTEND each seed outward,
# scoring left and right until the running score drops, and keep the best.
# The index turned "align against a huge string" into "look up short words",
# which is the entire speed trick.
ref = "ACGTACGTTAGCACGTACGT"
idx = build_kmer_index(ref, 4)
print(find_seeds("ACGTA", idx, 4))
Notice the shape. Building the index is a one-time pass over the reference. After that every query is cheap, because you pay for the length of the query, not the length of the genome. That inversion, expensive index once and cheap lookups forever, is why these tools scale where the raw DP grid does not.
Read aligners: placing billions of short reads
A sequencer does not read a chromosome end to end. It shatters the genome and reports billions of short fragments, called reads, often around 100 to 150 bases each (the how and its biases are BIO-4.1). A read aligner, or mapper, takes each read and finds where on a known reference genome it came from. That is billions of searches against a three-billion-base target, so it has to be an FM-index job. BWA and Bowtie use the BWT index to find near-exact placements fast, allowing a few mismatches to cover the sequencing errors and the real differences between this individual and the reference.
The exact DP is not gone, though, and this is the part people miss. The index does not produce the final alignment, it produces a small set of candidate locations. The mapper then runs a real Smith-Waterman-style local alignment inside a tiny window around each candidate to get base-precise placement and exact gaps. Index to localize, DP to finish. The heuristic narrows three billion positions to a handful, and the exact algorithm from BIO-3.2 polishes those few.
Aligning many sequences, and assembling with no reference
Two more jobs round out the toolkit.
Multiple sequence alignment (MSA) lines up many sequences at once, so each column holds positions descended from a common ancestral position across a whole family of genes or proteins. This is how you find conserved regions, build a family profile, or reconstruct an evolutionary tree. Finding the optimal MSA is not just slow, it is NP-hard: the exact cost grows exponentially in the number of sequences, so no one computes it exactly for real data. Tools like Clustal, MUSCLE, and MAFFT use a progressive heuristic instead, aligning the most similar pairs first, guided by a rough tree, and merging outward. Another optimality guarantee traded for a result you can actually get.
Genome assembly is the no-reference case. When there is no genome to map to, you must reconstruct one from the overlapping reads themselves, like rebuilding a shredded document with no original to check against. Two graph models dominate. In the overlap approach, each read is a node and an edge joins reads that overlap, and the assembly is a path threading the reads together (this suits long reads). In a de Bruijn graph, you go finer: nodes are the distinct (k minus 1)-mers, edges are the k-mers, and a walk through the graph spells out the sequence (this suits huge piles of short reads). Either way, repeats are the enemy again. A repeat longer than your reads collapses into a single overloaded node or a tangle the walk cannot resolve, so the assembly comes out in broken pieces called contigs rather than whole chromosomes.
E-values: the number you must not misread
BLAST hands back more than an alignment. It reports an e-value, and this is where statistics quietly enters. In a database of billions of bases, some stretch will match your query well purely by chance, the way a long enough random text will contain your name somewhere. So a high raw score alone means little. The e-value answers the honest question: how many hits this good or better would I expect to see by chance in a database of this size. A tiny e-value like 1e-40 means "essentially never by chance", so the hit is worth believing. An e-value near 1 means "you would expect about one such hit at random", so it is probably noise.
Key terms
- heuristic
- A method that finds a good answer fast without guaranteeing the optimal one, traded against the exact dynamic programming of BIO-3.2 that does guarantee it.
- k-mer
- A substring of length k. Sequences are indexed and compared through their k-mers because short exact words are fast to look up.
- seed-and-extend
- BLAST's strategy: find short exact or near-exact word matches (seeds) through an index, then extend each outward with scoring to build a full local alignment.
- inverted index
- A lookup table from a word to the list of positions where it occurs, the search-engine structure BLAST uses to jump from query words to candidate locations.
- Burrows-Wheeler Transform (BWT)
- A reversible reordering of a string that both compresses well and, via the FM-index, can be searched directly for any pattern in a small memory footprint. The basis of BWA and Bowtie.
- read aligner
- A tool that maps billions of short sequencing reads onto a reference genome, using a BWT index to localize candidates and a local DP to finish each placement.
- multiple sequence alignment (MSA)
- Aligning many sequences at once so each column shares ancestry. Optimal MSA is NP-hard, so tools like MAFFT use progressive heuristics.
- e-value
- The number of hits scoring at least this well expected by chance in a database of this size. A statistic to interpret, dependent on database size, not a probability the hit is real.
Why backward search over the BWT actually works
The FM-index keeps two small pieces: for each character, a count of how many smaller characters exist in the whole text, and a rank function that answers "how many of character c occur in the BWT up to position i". With just those, you search a pattern from its last character to its first, shrinking a range of the sorted suffixes at each step to exactly those that start with the pattern suffix seen so far. When the range is non-empty at the end, its size is the number of matches and its contents are their locations. Every step is one rank lookup, so a whole-pattern search costs on the order of the pattern length, not the genome length. That is the concrete reason a mapper can align a billion reads against a three-billion-base genome on a laptop-sized memory budget.
Check yourself
1. What is BLAST's core strategy for searching a huge database quickly?
2. Why do short-read mappers like BWA and Bowtie build on the Burrows-Wheeler Transform?
3. You BLAST a protein query against a large database and get no significant hit. What can you correctly conclude?
4. The same alignment reports an e-value of 1e-8 against a small database and 1e-4 against a much larger one. What does this tell you?