LP Fundamentals
Master the core algorithms that power linear programming solvers
In This Path
- What is Linear Programming? 15 min
- Sparse Matrix Storage 30 min
- LU Factorization 45 min
- The Simplex Method 45 min
- Worked Example 30 min
- Dual Simplex 30 min
- Presolve: Making Problems Easier 30 min
This path will take you from understanding what LP solvers do to being able to trace through the actual COIN-OR source code. By the end, terms like "reduced cost", "basis matrix", and "Forrest-Tomlin update" will make sense.
Check Your Knowledge
Before starting, make sure you're comfortable with:
- Matrix multiplication: $Ax = b$
- Solving linear systems (at least conceptually)
- What "optimization" means (finding best values)
If you need a refresher, Khan Academy's linear algebra course is excellent.
1. What is Linear Programming?
Linear Programming (LP) finds the best value of a linear objective subject to linear constraints.
The Standard Form
Every LP can be written as:
$$\min_{x} \quad c^T x \quad \text{subject to} \quad Ax = b, \quad x \geq 0$$
Where:
- $x$ is the vector of decision variables
- $c$ is the cost/objective vector
- $A$ is the constraint matrix
- $b$ is the right-hand side
A Concrete Example
Problem: A factory makes chairs and tables. Each chair needs 2 hours carpentry, 1 hour finishing, and profits $20. Each table needs 1 hour carpentry, 2 hours finishing, and profits $30. With 40 hours carpentry and 50 hours finishing available, how many of each should they make?
Formulation: $$\max \quad 20x_1 + 30x_2$$ $$\text{s.t.} \quad 2x_1 + x_2 \leq 40 \quad \text{(carpentry)}$$ $$\quad x_1 + 2x_2 \leq 50 \quad \text{(finishing)}$$ $$\quad x_1, x_2 \geq 0$$
Why "Linear"? Both the objective and constraints are linear functions - no $x^2$, no $xy$, no $\sin(x)$. This special structure enables fast algorithms.
Why LP Matters
LP solvers handle problems with millions of variables. They're used for:
- Supply chain optimization
- Financial portfolio allocation
- Airline crew scheduling
- Power grid management
- As subproblems in integer programming
In COIN-OR, LP problems are represented by the ClpSimplex class in Clp. The constraint matrix $A$ is stored as a CoinPackedMatrix in CoinUtils.
2. Sparse Matrix Storage
Real LP problems have matrices with millions of entries, but most are zero. Sparsity is the key to efficiency.
Dense vs Sparse
A dense 1000ร1000 matrix stores 1,000,000 numbers. But in LP, typically only 0.1-1% are nonzero - storing all those zeros wastes memory and computation.
Compressed Sparse Column (CSC) Format
COIN-OR uses CSC format. For a matrix:
$$A = \begin{pmatrix} 1 & 0 & 2 \ 3 & 0 & 0 \ 0 & 4 & 5 \end{pmatrix}$$
We store:
- values:
[1, 3, 4, 2, 5](nonzeros by column) - row indices:
[0, 1, 2, 0, 2](which row each value is in) - column starts:
[0, 2, 3, 5](where each column begins in values array)
Column 0 has values at positions 0-1, column 1 at position 2, column 2 at positions 3-4.
Exercise: Write out the CSC representation for: $$B = \begin{pmatrix} 5 & 0 \ 0 & 6 \ 7 & 8 \end{pmatrix}$$
Answer: values=[5,7,6,8], rows=[0,2,1,2], starts=[0,2,4]
Why This Matters
Sparse operations only touch nonzeros:
- Dense matrix-vector multiply: $O(mn)$ operations
- Sparse matrix-vector multiply: $O(\text{nnz})$ operations
For a 10,000ร10,000 matrix with 50,000 nonzeros, that's 50,000 vs 100,000,000 operations!
See CoinPackedMatrix in
CoinUtils
. The getVectorStarts(), getIndices(), and getElements() methods give you the CSC arrays.
3. LU Factorization
The simplex method needs to solve systems like $Bx = b$ at every iteration, where $B$ is the basis matrix. LU factorization makes this fast.
What is LU Factorization?
Factor a matrix into lower and upper triangular parts: $$B = LU$$
Then solve $Bx = b$ in two easy steps:
- Solve $Ly = b$ (forward substitution)
- Solve $Ux = y$ (backward substitution)
Triangular systems are fast because you can solve one variable at a time.
Markowitz Pivot Selection
When factoring sparse matrices, pivot choice matters enormously. A bad pivot creates fill-in (zeros become nonzeros).
The Markowitz criterion picks the pivot that minimizes expected fill-in:
$$\text{cost}(i,j) = (r_i - 1)(c_j - 1)$$
Where $r_i$ is the number of nonzeros in row $i$, and $c_j$ is the number in column $j$.
Why Markowitz works: If row $i$ has few nonzeros, eliminating it affects few other rows. If column $j$ has few nonzeros, the pivot column spreads to few rows. The product estimates total new nonzeros created.
Updating vs Refactoring
After a simplex pivot, $B$ changes by one column. Instead of refactoring from scratch ($O(m^3)$ worst case), we update the factorization ($O(m^2)$ typical).
COIN-OR uses Forrest-Tomlin updates: maintain $L$ and modify $U$ by eliminating a "spike" in the structure.
The CoinFactorization class in CoinUtils implements LU with Markowitz pivoting. Key methods:
factorize()- Initial factorizationupdateColumnFT()- FTRAN (solve $Bx=b$)updateColumnTranspose()- BTRAN (solve $B^Ty=c$)replaceColumn()- Forrest-Tomlin update
4. The Simplex Method
The simplex method walks along edges of the feasible polytope, improving the objective at each step, until reaching an optimal vertex.
Basic and Non-Basic Variables
At any vertex, some variables are basic (can be nonzero) and others are non-basic (fixed at bounds).
If we have $m$ constraints and $n$ variables, exactly $m$ variables are basic. The basis matrix $B$ is the $m \times m$ submatrix of $A$ for basic columns.
The Simplex Tableau (Conceptually)
Partition variables into basic ($x_B$) and non-basic ($x_N$):
$$Ax = Bx_B + Nx_N = b$$ $$x_B = B^{-1}b - B^{-1}Nx_N$$
The reduced costs tell us if we can improve: $$\bar{c}_N = c_N - N^T(B^{-T}c_B)$$
If all $\bar{c}_j \geq 0$ for minimization, we're optimal.
The Primal Simplex Algorithm
Step 1: Pricing
Find a non-basic variable $x_j$ with $\bar{c}_j < 0$ (can improve objective). If none exists, we're optimal.
Step 2: FTRAN
Compute the pivot column: $d = B^{-1}a_j$ where $a_j$ is column $j$ of $A$.
Step 3: Ratio Test
Find which basic variable $x_{B[i]}$ hits its bound first: $$\theta = \min_{d_i > 0} \frac{x_{B[i]}}{d_i}$$
The winner leaves the basis.
Step 4: Pivot
Update the basis: swap entering variable $j$ with leaving variable $B[i]$. Update factorization.
Step 5: Update Solution
$x_B \leftarrow x_B - \theta \cdot d$ and $x_j \leftarrow \theta$.
Why this works: Each pivot moves to an adjacent vertex with better (or equal) objective. Since there are finitely many vertices, we eventually reach optimum (with anti-cycling rules).
The primal simplex is implemented in ClpSimplexPrimal in Clp. The main iteration loop is in whileIterating().
5. Worked Example
Let's trace through a complete simplex solve on a small problem.
The Problem
$$\min \quad -2x_1 - 3x_2$$ $$\text{s.t.} \quad x_1 + x_2 + s_1 = 4$$ $$\quad x_1 + 2x_2 + s_2 = 6$$ $$\quad x_1, x_2, s_1, s_2 \geq 0$$
Initial basis: ${s_1, s_2}$ (slack variables), so $B = I$.
Initial solution: $x_B = (s_1, s_2) = (4, 6)$, $x_N = (x_1, x_2) = (0, 0)$. Objective = 0.
Iteration 1
Pricing: Reduced costs for non-basic variables:
- $\bar{c}_1 = -2 - (0,0) \cdot (1,1)^T = -2$
- $\bar{c}_2 = -3 - (0,0) \cdot (1,2)^T = -3$
Both negative. Choose $x_2$ (most negative) to enter.
FTRAN: $d = B^{-1}a_2 = I \cdot (1, 2)^T = (1, 2)^T$
Ratio test:
- $s_1$: $4/1 = 4$
- $s_2$: $6/2 = 3$ โ winner
$s_2$ leaves, $x_2$ enters. $\theta = 3$.
Update: $x_2 = 3$, $s_1 = 4 - 1 \cdot 3 = 1$, $s_2 = 0$. Objective = $-9$.
Iteration 2
New basis: ${s_1, x_2}$.
$$B = \begin{pmatrix} 1 & 1 \ 0 & 2 \end{pmatrix}, \quad B^{-1} = \begin{pmatrix} 1 & -0.5 \ 0 & 0.5 \end{pmatrix}$$
Pricing: $\bar{c}1 = -2 - (-3) \cdot (0.5) = -0.5$ (negative), $\bar{c}{s_2} = 0 - (-3) \cdot 0.5 = 1.5$ (positive)
Enter $x_1$.
FTRAN: $d = B^{-1}(1, 1)^T = (0.5, 0.5)^T$
Ratio test:
- $s_1$: $1/0.5 = 2$ โ winner
- $x_2$: $3/0.5 = 6$
$s_1$ leaves. $\theta = 2$.
Update: $x_1 = 2$, $x_2 = 3 - 0.5 \cdot 2 = 2$. Objective = $-2(2) - 3(2) = -10$.
Iteration 3
New basis: ${x_1, x_2}$.
Pricing: All reduced costs โฅ 0. Optimal!
Solution: $x_1 = 2$, $x_2 = 2$, objective = $-10$.
Exercise: Verify the final solution satisfies the constraints:
- $x_1 + x_2 = 2 + 2 = 4$ โ
- $x_1 + 2x_2 = 2 + 4 = 6$ โ
6. Dual Simplex
The dual simplex maintains dual feasibility (reduced costs have correct signs) while working toward primal feasibility (variable bounds satisfied).
When to Use Dual Simplex
Dual simplex is preferred when:
- Starting from a dual-feasible solution (common after adding cuts)
- The problem has many more rows than columns
- Re-optimizing after bound changes
Most modern solvers use dual simplex as the default.
The Algorithm
Step 1: Choose Leaving Variable
Find a basic variable violating its bounds (primal infeasible). If none, we're optimal.
Step 2: BTRAN
Compute the pivot row: $\rho^T = e_i^T B^{-1}$ where $i$ is the leaving row.
Step 3: Ratio Test (Dual)
Find entering variable that maintains dual feasibility: $$\text{min ratio of } |\bar{c}_j / \rho_j|$$
Step 4: Pivot
Swap variables and update factorization.
Dual simplex intuition: We start with a "good" objective bound but infeasible solution. Each iteration fixes one infeasibility while maintaining the objective bound's validity. When all infeasibilities are fixed, we've proven optimality.
See ClpSimplexDual in
Clp
. The dual simplex algorithm page has the full algorithm documentation.
7. Presolve: Making Problems Easier
Before solving, presolve transforms the problem into an equivalent but smaller/easier form.
Common Presolve Reductions
| Reduction | What it does |
|---|---|
| Fixed variable removal | If $l_j = u_j$, substitute $x_j = l_j$ |
| Singleton row | Row with one variable โ bound tightening |
| Singleton column | Column in one constraint โ substitute out |
| Forcing constraint | All variables at bounds โ fix them |
| Dominated column | Variable never useful โ fix at bound |
| Doubleton equality | Two variables in equation โ substitute |
Example: Singleton Row
If constraint is $3x_1 = 6$, then $x_1 = 2$ is forced. Remove $x_1$ from the problem.
Why Presolve Matters
- Reduces problem size (fewer pivots needed)
- Detects infeasibility/unboundedness early
- Tightens bounds (helps branch-and-bound later)
- Improves numerical stability
A good presolve can reduce solve time by 10-100x on real problems!
CoinUtils provides CoinPresolveAction and its subclasses for each reduction type. Clp's ClpPresolve orchestrates multiple passes. See presolve algorithms.
What's Next?
You now understand the core LP algorithms! Next steps:
- MIP Journey - Add integer variables and learn branch-and-bound
- Browse the source - Explore the actual implementations
- Algorithm index - Deep-dive into specific algorithms
You've learned:
- LP standard form and why linearity matters
- How sparse matrices make large problems tractable
- LU factorization and Markowitz pivot selection
- Both primal and dual simplex algorithms
- How presolve transforms problems
This foundation applies across all COIN-OR solvers!