A Systematic Approach to Algorithms

Vijay K. Garg · The University of Texas at Austin

Chapter 2. Lattice-Linear Predicate Detection

A finite distributive lattice, a lattice-linear predicate, and a single algorithm that drives much of the rest of the book.

The framework

The LLP algorithm searches a finite distributive lattice $L$ for the least element that satisfies a Boolean predicate $B$. A predicate is lattice-linear if, whenever $B(G)$ is false, at least one component $j$ of the current vector $G$ is forbidden: any larger vector $H \geq G$ with $H[j] = G[j]$ also fails $B$. The algorithm advances any forbidden component to the smallest value consistent with $B$ and repeats:

while ∃ j : forbidden(G, j):
    G[j] := smallest value that resolves the violation
return G

Three pieces are required for an LLP problem: a lattice $L$, a predicate $B$, and an advance rule. The demos below show several small concrete instances.

LLP-GCD

Compute the greatest common divisor of an array of positive integers. The lattice is $\{1, 2, \ldots, \max\}^n$ with the usual componentwise order; the predicate is “all components are equal.” The advance rule reduces a forbidden component by modulo with a smaller component.

Time complexity: $O(n \log \max_i A[i])$ — each component reaches the gcd in $O(\log)$ Euclidean-mod steps.

LLP-EuclidGCD

The 2-element specialisation of LLP-GCD; on a length-2 input it is exactly classical Euclid by repeated subtraction, but written in the LLP shape so the connection to LLP-GCD is visible.

Time complexity: $O(\max(a, b))$ via repeated subtraction; the standard mod-based variant achieves $O(\log \min(a, b))$.

LLP-JobScheduling-Forbidden

Minimum completion time per job given prerequisites $\text{pre}(j)$. The advance may fire repeatedly on the same $j$ if its predecessors keep changing.

Time complexity: $O(nm)$, where $n$ is the number of jobs and $m$ the number of precedence edges.

LLP-JobScheduling-Ensure

The same algorithm using the ensure (j) : LHS >= RHS sugar — the LL compiler desugars to the equivalent forbidden/advance pair.

Time complexity: $O(nm)$, where $n$ is the number of jobs and $m$ the number of precedence edges.

LLP-JobScheduling-Fixed

An efficient reformulation: each job is advanced at most once, gated by a side flag fixed[j] that becomes true once all of $j$'s predecessors are finalised. The same pattern reappears in LLP-Layering.

Time complexity: $O(n + m)$ (Kahn-style topological sweep), where $n$ is the number of jobs and $m$ the number of precedence edges.

LLP predicates for this chapter

Each LLP program in this chapter defines a state vector $G$ and a forbidden predicate; the algorithm runs until no $j$ is forbidden. The table below lists, for each algorithm, what $G[i]$ represents and the negation of the forbidden clause from the matching .llp source.

Algorithm $G[j]$ Negation of the forbidden clause
LLP-GCD $G[j]$ — value at index $j$ $\forall j,\, \forall i \in [0..n-1]:\ G[j] \leq G[i]$
LLP-EuclidGCD $G[j]$ — value at index $j$ $\forall j,\, \forall i \in [1..n]:\ G[j] \leq G[i]$
LLP-JobScheduling (forbidden) $G[j]$ — finish time of job $j$ $\forall j:\ G[j] \geq \max_{i \in \mathrm{pre}(j)} (G[i] + t[j])$
LLP-JobScheduling (ensure) $G[j]$ — finish time of job $j$ $\forall j:\ G[j] \geq \max_{i \in \mathrm{pre}(j)} (G[i] + t[j])$
LLP-JobScheduling (with fixed[]) $G[j]$ — finish time of job $j$ $\forall j:\ \mathrm{fixed}[j] \,\wedge\, G[j] \geq \max_{i \in \mathrm{pre}(j)} (G[i] + t[j])$

Looking ahead

Every algorithm chapter from Chapter 4 onwards recasts a classical algorithm in this framework: stable matching (Gale-Shapley), sorting (LLP-Sort), graph traversal (LLP-BFS), greedy algorithms, shortest paths, minimum spanning trees, dynamic programming, and network flow.