Expand description
§Chapter 5.3 — Sparse LU factorization of the basis: Lu
§Purpose
Every simplex iteration solves two or three linear systems with the basis
matrix B (Chapter 5.4). We factorize B once into sparse triangular
factors and keep them across iterations, modified by the Forrest–Tomlin
update (Chapter 5.5) until a fresh factorization is cheaper or safer.
Simplex bases are special: most of their columns are logical or nearly
triangular, so most pivots can be found without any arithmetic by
peeling off singletons; only a small bump (the kernel) needs genuine
Gaussian elimination, done with Markowitz pivoting under a threshold.
§Notation
B ∈ R^{m×m} has rows i and positions k (Chapter 5.1). The
factorization is a sequence of m steps; step s pivots on the entry
(p_s, q_s) (row p_s, position q_s). With the permutation matrices
P (row s of PB is row p_s of B) and Q (column s of BQ is
column q_s of B) we compute
P B Q = L U,L unit lower triangular and U upper triangular. We store both in the
original indices, never permuting values:
Las one column eta per step with a nonzero subdiagonal: the pivot rowp_sand the multipliersl_isfor rowsipivoted later;Uas its diagonalu_{p_s q_s}(indexed by the rowp_s) and its off-diagonal entriesu_ij(rowi, positionj, withstep(i) < step(j)), held twice: by rows and by columns. FTRAN needsUby columns, BTRAN by rows, and the Forrest–Tomlin update must delete a column and a row ofU— so both copies are dynamic (Chapter 5.2) and are kept identical.
The active submatrix S during the elimination has row counts r_i
and column counts c_j. A column singleton is a column with c_j = 1,
a row singleton a row with r_i = 1. The Markowitz merit of a
candidate pivot is (r_i − 1)(c_j − 1), an upper bound on the fill it
creates. With the threshold u (LuOptions::rel_pivot_tol ∈ (0, 1]), an entry is acceptable iff |s_ij| ≥ u · max_k |s_kj| and
|s_ij| > τ_abs (LuOptions::abs_pivot_tol). Entries of magnitude
at most ε_drop (LuOptions::drop_tol) are negligible.
§Algorithm L (LU factorization of a simplex basis)
Given the columns of B through a crate::ColumnSource (in the
simplex method: the basis heading and A).
L1. [Load.] Validate the source (Chapter 5.1, B1). Build S = B in
a column file with values and a row file with the pattern only;
record nnz(B) and max |b_ij|. Bucket columns and rows by count.
L2. [Empty columns.] Every active column with c_j = 0 is dead: it
can never be pivoted. Remove it (its position joins the deficiency
list).
L3. [Column singleton.] If some column j has c_j = 1, with entry
(i, v): if |v| > τ_abs pivot on (i, j) (step L6) — the L
column of this step is empty and row i is copied to U — else
declare j dead. Go to L2.
L4. [Row singleton.] Otherwise, if some row i with r_i = 1 has its
only entry (i, j) above τ_abs, pivot on it (step L6) — the U row
is empty, the multipliers s_kj / s_ij form the L column, and no
entry of S is updated. Go to L2. Steps L3–L4 constitute the
triangular phase: no arithmetic beyond one division per L entry.
Singleton pivots are forced (the only entry of their row or column in
any completion of the elimination), so no threshold is applied to
them; they cannot create growth in U.
L5. [Markowitz.] Otherwise (the bump), remove the empty columns as in
L2; if no active column remains, go to L7. Run Algorithm M to obtain
a pivot and a list of dead columns; remove the dead columns, pivot
(step L6) and repeat L5.
L5′. [Pre-ordered bump; only with Lu::set_pre_order(Some(PreOrder::Colamd)),
task L1.3.] Instead of L5: compute the
COLAMD order (Chapter 5.10, Algorithm C) of the columns of the bump
on its pattern; for each column c in that order that is still
active (after removing the empty columns as in L2): if γ_c = max_i |s_ic| ≤ τ_abs declare c dead; otherwise pivot (step L6) on
the acceptable entry (i, c) of least row count r_i (ties: the
larger |s_ic|, then the first in the column) — threshold partial
pivoting inside a fixed column order, as UMFPACK and SuperLU do.
With pre_order = None step L5 runs exactly as before, so the
factors are bit for bit those of the second edition.
L6. [Pivot on (p, c).] Append the step (p, c) and the diagonal
u_pc = s_pc. For every other (i, s_ic) of column c record
l_ic = s_ic / s_pc; for every other (p, s_pj) of row p record
u_pj = s_pj. Remove row p and column c from S. Then, for each
j of the U row, find the positions in column j of the rows of
the L column (one pass over column j against a map of those
rows) and set s_ij ← s_ij − l_ic u_pj for every i of the L
column, in its order, appending fill-in to both files; entries that
cancel to |s_ij| ≤ ε_drop are dropped, in the order of column j.
Only the entries just updated or filled can cancel — plus, the first
time column j is updated, entries of B that were negligible from
the start, so such a column is scanned whole once. Update the counts
of the touched rows and columns.
L7. [Finish.] If any column is dead, the basis is singular: report its
positions and the rows that never received a pivot
(LuError::Singular). Otherwise build the row and column
copies of U, the inverse step maps, and the statistics, including
the growth factor max |u_ij| / max |b_ij| and max |u_ij| itself,
the reference of the update policy (Chapter 5.5). Record the pivot
sequence (p_s, q_s) as the SymbolicLu of Chapter 5.12 (into
the arrays of the previous one: 2m copies, no allocation once
warm). Step L7 reads only the output of the elimination (Elim),
so Algorithm R of Chapter 5.12 shares it.
§Algorithm M (Markowitz search with threshold)
Let best be the acceptable entry of least merit found so far (ties go
to the larger ratio |s_ij| / max_k |s_kj|), and e the number of lines
examined that contained an acceptable entry.
M1. For k = 1, 2, …, m:
M2. [Columns of count k.] For each active column j with c_j = k:
compute γ_j = max_i |s_ij|; if γ_j ≤ τ_abs, mark j dead and go
on. Otherwise evaluate every acceptable entry with merit
(r_i − 1)(k − 1). Every unexamined candidate now lies in a row with
r_i ≥ k (rows of smaller count were examined whole) and a column
with c_j ≥ k, so its merit is at least (k − 1)²: stop if best
reaches that bound, or if e ≥ search_limit and best exists.
M3. [Rows of count k.] The same for each active row i with r_i = k,
scanning its columns j (merit (k − 1)(c_j − 1)); the bound for
unexamined candidates is now k (k − 1).
M4. Return best (possibly none) and the dead columns.
The search limit (default 4, LuOptions::search_limit) is Zlatev’s
restriction: the merit found after a few lines is almost always the
minimum, and the full search would cost O(nnz(S)) per pivot. If M
returns no pivot and no dead column, every active column was examined
and found empty, so L5 terminates.
§Invariants
Checked by LuFactor::check_invariants in the tests and after every
factorization in debug builds.
(F1) The non-vacant slots of (p_s) and (q_s) are permutations of
0..m, and the inverse maps step(p_s) = step(q_s) = s agree with
them (right after a factorization there are no vacant slots; each
update adds one, Chapter 5.5).
(F2) Every L multiplier l_is of step s has step(i) > s, where
step is the order of the last factorization (updates reorder U
but leave L alone).
(F3) Every off-diagonal u_ij has step(i) < step(j); the row and
column copies of U hold exactly the same entries; every diagonal
entry is nonzero.
(F4) Right after a factorization, L U = P B Q up to rounding; tested
by property tests against the dense product.
§The two files of S (task P3.37)
The column file and the row file of S are cross-linked (Chapter 5.2,
Algorithm T): every entry holds a hint of the position of its twin in
the other file, exact in long lines. Every deletion of L2, L6 and of the
cancellations of L6, and every lookup of s_ij from the row side (L4,
M3), is then O(1) in a long line instead of a linear search along it
(short lines are still searched, which is cheaper than keeping their
hints exact). The deletions keep the lines in the same
order as the searches did, so the elimination — pivots, fill, the order
of L and U — is the same, bit for bit. Invariant (S3) of Chapter 5.2
is checked after the load and after the triangular phase in debug
builds.
§The objects (second edition)
Lu<T, U> is the factorization object of the contract: the
options LuOptions, the stored factors Factors<T> (the steps,
L, U by rows and by columns, the spike, the work arrays and the
statistics LuStats), the basis-update policy U (Chapter 5.5,
crate::ForrestTomlin by default, which owns the row etas) and the
moving averages of the density policy (Chapter 5.4). The simplex method
programs against the trait LuFactor. In the first edition all of
this was one struct, Factor; splitting it moves no arithmetic: the
update reaches the factors through &mut Factors<T>, and the type
parameter U is resolved at compile time.
LuOptions merges the first edition’s FactorOptions with the two
tolerances the factorization borrowed from the solver’s Tolerances:
markowitz became LuOptions::rel_pivot_tol and zero became
LuOptions::drop_tol (the solver copies them in when it creates a
factor).
§Complexity
The triangular phase costs O(nnz(B)) plus one division per L entry.
A Markowitz pivot costs O(Σ_j c_j) over the columns of its U row,
plus the bounded search. (Before task P3.37 every deletion also searched
its line, O(Σ_i r_i²) per factorization: on hypothyroid-k1 half the
time of a factorization.)
§References
Markowitz, “The elimination form of the inverse and its application to linear programming”, Management Sci. 3, 1957; Suhl & Suhl, “Computing sparse LU factorizations for large-scale linear programming bases”, ORSA J. Comput. 2(4), 1990 (triangular phase, count lists, search); Zlatev, “On some pivotal strategies in Gaussian elimination by sparse technique”, SIAM J. Numer. Anal. 17, 1980 (restricted search); Maros (2003), ch. 5 (§5.4 LU of the basis) and ch. 8 (§8.2 bump); Davis, Direct Methods for Sparse Linear Systems (2006), ch. 6; Duff, Erisman & Reid (1986), ch. 7 and §9.4.
Structs§
- Factors
- The stored factors
P, L, U, Qof a basis (row and column files, MA28/Suhl style), the recorded spike, the work arrays of the solves and the statistics. Opaque: the basis-update policies reach it throughBasisUpdate. - Lu
- Sparse LU factors of a basis with a basis-update policy
U(Forrest–Tomlin by default): the contract’sLu<T, U>. - LuOptions
- Options of the factorization, of the solves and of the update policy.
- LuStats
- Statistics of the current factor, for logs and the phase-1 benchmark.
The update counters are in
UpdateStats.
Enums§
- PreOrder
- Column pre-orders available to the dynamic LU (
Lu::set_pre_order; the contract’sPreOrder, which it placed inLuOptions::pre_order).
Traits§
- LuFactor
- What every LU of the crate exposes; the simplex method programs
against this trait (the contract’s
LuFactor<T>).