N or P
Contents Glossary
22

When the answer is a matrix

When the problem has a matrix at its heart, the answer is in the linear algebra library — and the work is cheap.

Many business problems that look combinatorial collapse into a matrix computation once the structure is spotted. Google's PageRank ranks web pages as the principal eigenvector of a link-graph matrix. Recommendation systems use matrix factorization to predict missing entries in a user-item matrix. Search ranking, image compression (JPEG, SVD), principal component analysis, spectral clustering, network centrality — all matrix operations. Linear algebra is in the cheap category. Matrix multiplication, eigenvalue computation, matrix factorization all run in polynomial time, and Rust has multiple production-grade libraries — nalgebra for general use, faer for high-performance dense problems, ndarray for NumPy-like array operations. When a problem can be expressed in matrix form, the engineering work is in the data preparation, not the computation.

  1. 01
    PageRank in twenty lines of nalgebra — build the column-stochastic transition matrix, run power iteration, return the dominant eigenvector. The same shape solves spectral clustering, recommendation scoring, and influence ranking.
    use nalgebra::{DMatrix, DVector};
    
    fn pagerank(links: &DMatrix<f64>, damping: f64, iterations: usize) -> DVector<f64> {
        let n = links.ncols();
        let teleport = DVector::from_element(n, (1.0 - damping) / n as f64);
        let mut r = DVector::from_element(n, 1.0 / n as f64);
        for _ in 0..iterations {
            r = links * (&r * damping) + &teleport;
        }
        r
    }
  2. 02
    for _ in 0..iterations is the counted-loop idiom — "run this body iterations times, no step counter needed." The range 0..iterations (page 15) is an iterator yielding 0, 1, 2, …, iterations - 1, and the loop binds each yielded value to the pattern on the left. Here the pattern is just _, the wildcard from page 9 that matches anything and binds nothing. Same range, two patterns: for i in 0..n when you actually need the step number, for _ in 0..n to make it clear you do not. The underscore is documentation for the reader as much as it is syntax for the compiler.
    // Counted loop — run the body N times, discard the index:
    for _ in 0..5 {
        println!("again");
    }
    // prints "again" five times.
    
    // If you need the step number, bind it:
    for i in 0..5 {
        println!("step {}", i);     // step 0, step 1, ..., step 4
    }
    
    // On page 22, only the repetition matters — not the iteration index.
    // The _ tells the reader "I'm not using the loop variable on purpose":
    for _ in 0..iterations {
        r = links * (&r * damping) + &teleport;
    }
    
    // More verbose equivalents — both work, neither idiomatic:
    let mut k = 0;
    while k < iterations {
        r = links * (&r * damping) + &teleport;
        k += 1;
    }
    
    // For Rust, for/in is the idiomatic counted loop.  Reach for while only
    // when the stop condition is not a simple count.
  3. 03
    Two floating-point expressions set up PageRank's bookkeeping. 1.0 / n as f64 is the initial rank every page starts with — the total rank mass is 1.0 and it is split evenly across all n pages, so each page starts with 1/n. (1.0 - damping) / n as f64 is the teleport mass — at each iteration, with probability 1 - damping (typically 0.15 when damping is 0.85), the model says a random surfer teleports to a uniformly random page, contributing (1 - damping) / n to every page's rank. In both expressions, as f64 converts the usize count n to a 64-bit float for the division — Rust never implicitly converts between integer and float types (the same rule from page 15 applied across number kinds, not just across integer widths). Operator precedence: as binds tighter than /, so the parser sees 1.0 / (n as f64) and (1.0 - damping) / (n as f64).
    // n is a usize — Rust will not implicitly convert it to f64.
    let n: usize = links.ncols();
    
    
    // ── Initial rank — split 1.0 of mass evenly across n pages ─────
    //
    //   1.0 / n as f64  =  1.0 / (n as f64)
    //
    //   for n =     10 pages →  0.1     per page
    //   for n =    100 pages →  0.01    per page
    //   for n =  1,000,000   →  0.000001 per page
    //
    let mut r = DVector::from_element(n, 1.0 / n as f64);
    
    
    // ── Teleport mass per page ────────────────────────────────────
    //
    //   (1.0 - damping) / n as f64  =  (1.0 - damping) / (n as f64)
    //
    //   damping = 0.85,  n = 100  →  teleport = 0.15 / 100 = 0.0015 per page
    //
    let teleport = DVector::from_element(n, (1.0 - damping) / n as f64);
    
    
    // Operator precedence — `as` binds tighter than `/`:
    //
    //   1.0 / n as f64                 parses as   1.0 / (n as f64)
    //   (1.0 - damping) / n as f64     parses as   (1.0 - damping) / (n as f64)
    
    
    // One iteration of the PageRank update:
    //
    //   r_new  =  links · (r · damping)  +  teleport
    //             ────────────────────       ────────
    //             redistribute damping       add a flat teleport
    //             fraction of rank along     contribution to every
    //             outbound links             page
    //
    // Repeat until r stops changing (or for a fixed iteration budget).
Page, L., Brin, S. (1998) The PageRank Citation Ranking. One of the most cited papers in computing. Source →
In plain terms

Linear algebra is the secret hammer for an enormous class of business problems that do not look like math problems at first glance.

PageRank is the classic example. Google's original ranking algorithm models the web as a giant graph where each page is a node and each link is an edge. The "importance" of each page is defined as the principal eigenvector of a transition matrix derived from the link structure. Computing eigenvectors of huge matrices is a well-studied problem in linear algebra with fast iterative algorithms (power iteration). The famous PageRank paper from 1998 essentially says: pose the web ranking problem as a matrix problem, then use the linear algebra textbook.

Recommendation systems use the same trick. The user-item matrix records every interaction. Predicting missing entries — what would this user rate this movie — is matrix completion, solvable by low-rank factorization (SVD, alternating least squares, neural collaborative filtering). All matrix operations.

Spectral clustering uses the eigenvectors of the graph Laplacian to find groups in a network with few edges between them. Search ranking uses singular value decomposition to project queries and documents into a shared low-dimensional space. Image compression — JPEG — uses the discrete cosine transform, another matrix operation.

For business planning, the discipline is to ask: can I express this problem as a matrix? If yes, the work is cheap — Rust has three production-grade linear algebra libraries (nalgebra for general use, faer for high performance on dense problems, ndarray for NumPy-like operations on multi-dimensional arrays). The estimate is days. If no, the work might still be tractable but the toolbox shrinks.

The reason this matters is that recognizing the matrix is often the hardest part. Once you see that a problem is "find the eigenvector," "factor the matrix," "solve the linear system," "compute the SVD," you have moved the work from research to engineering. The libraries are decades old, fast, and well-maintained.

Reach for the matrix. The library does the rest.

← 21 Is this number prime
22/39
23 Boolean satisfiability — the original hard problem →