Linear Programming
“programming” here means planning, not coding β the word predates the software sense. π a linear program optimises a linear objective over a region carved out by linear inequalities. it is the base camp of mathematical programming: quadratic, integer and non-linear programming all generalise it in one direction or another, and all of them lean on LP machinery (relaxations, duals, warm starts) to get anything done. clrs devotes chapter 29 to it (Cormen, Thomas H. and Leiserson, Charles E. and Rivest, Ronald L. and Stein, Clifford, 2009).
standard form
a linear program (LP) in standard form:
\begin{align*} \text{maximise} \quad & c^{\top} x \\ \text{subject to} \quad & A x \le b \\ & x \ge 0, \end{align*}
with \(c, x \in \mathbb{R}^{n}\), \(b \in \mathbb{R}^{m}\), \(A \in \mathbb{R}^{m \times n}\). every LP converts to this shape by mechanical rewrites:
- minimise \(c^{\top}x\): maximise \(-c^{\top}x\).
- equality \(a^{\top}x = b_i\): the pair \(a^{\top}x \le b_i\) and \(-a^{\top}x \le -b_i\).
- free variable \(x_j\): substitute \(x_j = x_j^{+} - x_j^{-}\) with \(x_j^{+}, x_j^{-} \ge 0\).
- \(\ge\) constraint: multiply through by \(-1\).
for the simplex method one more step gives slack form: each inequality \(a_i^{\top}x \le b_i\) becomes an equality \(a_i^{\top}x + s_i = b_i\) with a slack variable \(s_i \ge 0\). geometry unchanged, algebra friendlier.
an LP is exactly one of three things: infeasible (no \(x\) satisfies the constraints), unbounded (feasible points achieve arbitrarily large objective), or it has a finite optimum.
geometry
the feasible region \(\{x : Ax \le b,\; x \ge 0\}\) is an intersection of half-spaces β a convex polyhedron (a polytope when bounded). the objective \(c^{\top}x\) is linear, so its level sets are parallel hyperplanes; optimising means sliding a hyperplane as far as it will go while still touching the region.
vertex optimality theorem. if an LP has an optimal solution and its feasible region has at least one vertex, then some vertex is optimal. π sketch: take any optimal \(x^{*}\). if \(x^{*}\) is not a vertex, some direction \(d \ne 0\) keeps both \(x^{*} \pm \epsilon d\) feasible; optimality forces \(c^{\top}d = 0\) (otherwise one of the two directions improves), so we may walk along \(\pm d\) at constant objective until an extra constraint becomes tight. repeat: each step raises the count of tight, linearly independent constraints, and after at most \(n\) steps we stand on a vertex with the same objective value.
the consequence is enormous: a continuous optimisation problem over uncountably many points reduces to a search over finitely many vertices β at most \(\binom{n+m}{n}\) of them. the catch is that “finitely many” can be astronomically many, which is the whole story of LP algorithms.
a worked example
the LP used throughout this page: π
\begin{align*} \text{maximise} \quad & 3x + 5y \\ \text{subject to} \quad & x \le 4 \\ & 2y \le 12 \\ & 3x + 2y \le 18 \\ & x, y \ge 0. \end{align*}
the feasible region is a pentagon with vertices \((0,0), (4,0), (4,3), (2,6), (0,6)\). evaluating \(3x+5y\) at each: \(0, 12, 27, 36, 30\). the optimum is \(z^{*} = 36\) at \((2,6)\).
duality
every LP has a shadow. attach a multiplier \(u_i \ge 0\) to each constraint and combine: any feasible \(x\) satisfies \(u^{\top}Ax \le u^{\top}b\). if the multipliers are chosen so that \(A^{\top}u \ge c\) componentwise, then (using \(x \ge 0\))
\begin{equation} c^{\top}x \le (A^{\top}u)^{\top}x = u^{\top}Ax \le u^{\top}b = b^{\top}u. \end{equation}
so every such \(u\) certifies an upper bound on the primal optimum. finding the tightest certificate is itself an LP β the dual:
\begin{align*} \text{minimise} \quad & b^{\top}u \\ \text{subject to} \quad & A^{\top}u \ge c \\ & u \ge 0. \end{align*}
- weak duality (just proved): primal feasible \(x\), dual feasible \(u\) give \(c^{\top}x \le b^{\top}u\). corollary: if you exhibit a primal and dual pair with equal objectives, both are optimal.
- strong duality: if the primal has a finite optimum, so does the dual, and the optima are equal. this is the deep theorem (it needs farkas’ lemma or the correctness of simplex; clrs proves it via simplex termination (Cormen, Thomas H. and Leiserson, Charles E. and Rivest, Ronald L. and Stein, Clifford, 2009)).
- complementary slackness: \(x^{*}, u^{*}\) are simultaneously optimal iff \(u_i^{*} > 0 \Rightarrow a_i^{\top}x^{*} = b_i\) and \(x_j^{*} > 0 \Rightarrow (A^{\top}u^{*})_j = c_j\). a positive dual price forces its constraint to bind; a used primal variable forces its dual constraint to bind.
the dual of the worked example
\begin{align*} \text{minimise} \quad & 4u_1 + 12u_2 + 18u_3 \\ \text{subject to} \quad & u_1 + 3u_3 \ge 3 \\ & 2u_2 + 2u_3 \ge 5 \\ & u_1, u_2, u_3 \ge 0. \end{align*}
solve it by complementary slackness from the known primal optimum \((2,6)\):
- constraint 1 is slack (\(2 < 4\)) so \(u_1^{*} = 0\).
- \(x^{*} = 2 > 0\) forces \(u_1 + 3u_3 = 3\), so \(u_3^{*} = 1\).
- \(y^{*} = 6 > 0\) forces \(2u_2 + 2u_3 = 5\), so \(u_2^{*} = 3/2\).
dual objective: \(4(0) + 12(3/2) + 18(1) = 36 = z^{*}\). strong duality checks out. π
the simplex method
walk from vertex to adjacent vertex, always improving the objective; stop when no neighbour improves β by convexity that vertex is globally optimal. algebraically each vertex is a basic feasible solution (a choice of basic variables solved against the constraints, the rest pinned at zero), and the walk is bookkept in a tableau.
tableau walkthrough
slack form of the worked example: \(x + s_1 = 4\), \(2y + s_2 = 12\), \(3x + 2y + s_3 = 18\), objective row \(z - 3x - 5y = 0\). start at the vertex \((0,0)\) with basis \((s_1, s_2, s_3)\):
x y s1 s2 s3 rhs
s1 1 0 1 0 0 4
s2 0 2 0 1 0 12
s3 3 2 0 0 1 18
z -3 -5 0 0 0 0
entering variable β most negative objective coefficient: \(y\) (coefficient \(-5\); each unit of \(y\) buys 5 units of \(z\)). leaving variable β ratio test \(\min\{12/2,\ 18/2\} = 6\) picks \(s_2\) (row 1 has no \(y\) to run out of). pivot on the \(y\)-entry of the \(s_2\) row:
x y s1 s2 s3 rhs
s1 1 0 1 0 0 4
y 0 1 0 1/2 0 6
s3 3 0 0 -1 1 6
z -3 0 0 5/2 0 30
we are now at vertex \((0,6)\) with \(z = 30\). still a \(-3\) under \(x\), so enter \(x\); ratio test \(\min\{4/1,\ 6/3\} = 2\) makes \(s_3\) leave:
x y s1 s2 s3 rhs
s1 0 0 1 1/3 -1/3 2
y 0 1 0 1/2 0 6
x 1 0 0 -1/3 1/3 2
z 0 0 0 3/2 1 36
no negative coefficients remain: optimal. read off \(x = 2\), \(y = 6\), \(z = 36\). the path taken was \((0,0) \to (0,6) \to (2,6)\) β two pivots, hugging the boundary of the pentagon. bonus: the final objective row’s slack coefficients \((0, 3/2, 1)\) are exactly the optimal dual variables β simplex solves both problems at once.
worst case, simplex visits exponentially many vertices (the klee-minty cube drives dantzig’s pivot rule through all \(2^{n}\) corners), yet on real instances it is routinely near-linear in the number of constraints β one of the great embarrassments of complexity theory.1
degeneracy
if a basic variable sits at zero, a pivot may not move the vertex at all, and careless pivot rules can cycle forever. bland’s rule (always pick the lowest-index eligible variable) or lexicographic tie-breaking guarantees termination (Cormen, Thomas H. and Leiserson, Charles E. and Rivest, Ronald L. and Stein, Clifford, 2009).
interior-point methods
instead of crawling the boundary, punch through the middle: replace the constraints with a logarithmic barrier \(-\mu \sum_i \log(b_i - a_i^{\top}x)\), solve the smooth unconstrained problem by newton steps, and shrink \(\mu \to 0\) so the iterates follow the central path to the optimal face. karmarkar’s 1984 algorithm made this approach both polynomial-time and practical; modern primal-dual interior-point solvers need only a few dozen newton iterations almost independently of problem size. today’s rule of thumb: simplex wins on warm starts and repeated re-solves (its basis is a resumable certificate), barrier wins on huge sparse one-shot solves. the ellipsoid method settled polynomiality first (khachiyan, 1979) but is hopeless in practice β its enduring role is theoretical: it solves LPs with exponentially many constraints in polynomial time, given a separation oracle.
applications
the diet problem
the original LP (stigler, 1945): meet nutritional floors at minimum cost. variables = quantity of each food, constraints = one per nutrient, objective = total cost. solved honestly below β and, true to form, the solution is depressingly monotonous, because optimal vertices lie on few constraints and therefore use few foods. π
max-flow as LP
network optimisation is secretly LP. for a flow network \(G = (V, E)\) with capacities \(c_e\), source \(s\), sink \(t\):
\begin{align*} \text{maximise} \quad & \textstyle\sum_{(s,v) \in E} f_{sv} - \sum_{(v,s) \in E} f_{vs} \\ \text{subject to} \quad & 0 \le f_e \le c_e \quad \forall e \in E \\ & \textstyle\sum_{(u,v) \in E} f_{uv} = \sum_{(v,w) \in E} f_{vw} \quad \forall v \in V \setminus \{s, t\}. \end{align*}
the LP dual of max-flow is min-cut β max-flow/min-cut is strong duality wearing a graph costume. shortest paths and min-cost flow have equally direct formulations (Cormen, Thomas H. and Leiserson, Charles E. and Rivest, Ronald L. and Stein, Clifford, 2009). the constraint matrices here are totally unimodular, so the LP optimum lands on integer vertices for free β the full story is on the integer programming page, and the combinatorial algorithms that beat general LP solvers on these problems live under classical algorithms.
code
scipy.optimize.linprog (the highs solver) on both examples:
import numpy as np
from scipy.optimize import linprog
# the worked example: max 3x + 5y s.t. x <= 4, 2y <= 12, 3x + 2y <= 18
c = [-3, -5] # linprog minimises, so negate
A_ub = [[1, 0], [0, 2], [3, 2]]
b_ub = [4, 12, 18]
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=[(0, None)] * 2, method="highs")
print("primal: x* =", res.x, " z* =", -res.fun)
print("duals: y* =", res.ineqlin.marginals) # sign flipped by the min convention
# a small diet problem: minimise cost, meet nutrient floors
# foods: oats, chicken, spinach (per 100 g)
cost = [0.55, 1.40, 0.80] # dollars
protein = [16.9, 31.0, 2.9] # grams
energy = [389, 165, 23] # kcal
iron = [4.7, 1.0, 2.7] # mg
A = -np.array([protein, energy, iron]) # >= floors -> -A x <= -b
b = -np.array([60, 1800, 12])
res2 = linprog(cost, A_ub=A, b_ub=b, bounds=[(0, 8)] * 3, method="highs")
x = res2.x
print("\ndiet (units of 100 g): oats, chicken, spinach =", np.round(x, 3))
print("cost = $%.2f" % res2.fun)
print("protein %.0f g, energy %.0f kcal, iron %.1f mg"
% (protein @ x, energy @ x, iron @ x))
primal: x* = [2. 6.] z* = 36.0
duals: y* = [-0. -1.5 -1. ]
diet (units of 100 g): oats, chicken, spinach = [4.627 0. 0. ]
cost = $2.54
protein 78 g, energy 1800 kcal, iron 21.7 mg
the reported marginals \((0, -1.5, -1)\) are the duals \((0, 3/2, 1)\) with sign flipped by the minimisation encoding β matching the tableau’s final objective row and the complementary-slackness calculation exactly. and the diet LP does what diet LPs always do: 463 grams of oats a day, nothing else. the energy floor binds; protein and iron overshoot for free. vertex solutions are extreme by nature β if you want variety you must ask for it in the constraints.
see also
- quadratic programming β the objective goes quadratic, KKT replaces the tableau
- integer programming β demand integrality and lose polynomial time
- non-linear programming β the general constrained-optimisation story
- dynamic programming β the other “programming” that is really planning
- classical algorithms β max-flow and friends, solved combinatorially
spielman and teng’s smoothed analysis (2004) explains it: perturb any LP instance slightly at random and the expected number of simplex pivots becomes polynomial. adversarial instances are brittle.
References
Cormen, Thomas H. and Leiserson, Charles E. and Rivest, Ronald L. and Stein, Clifford (2009). Introduction to Algorithms, MIT Press. ↩︎
Backlinks (11)
1. Dynamic Programming /wiki/ccs/programming/paradigms/dynamic/
dynamic programming is two things wearing one name. to bellman it was a mathematical theory of multistage decision processes β sibling to linear programming in the “programming means planning” sense.1 π to a computer scientist it is a technique: solve a problem by combining solutions to subproblems, and never solve the same subproblem twice (Cormen, Thomas H. and Leiserson, Charles E. and Rivest, Ronald L. and Stein, Clifford, 2009). the two are the same idea at different altitudes, and this page covers both. worked implementations also live in this github repo.
2. Stochastic /wiki/ccs/programming/paradigms/stochastic/
a linear program assumes you know the data. stochastic programming admits that you do not β some coefficients are random β but insists you know their distribution, and asks for the decision that is best on average. π the structural insight that makes this a paradigm rather than a hack: split the decision in two. commit to \(x\) now, before the coin is flipped; after uncertainty resolves, take a corrective recourse action \(y\) that adapts to whatever happened. the objective charges you for both, weighting the second stage by expectation.1
3. Constraint /wiki/ccs/programming/paradigms/constraint/
constraint programming inverts the usual deal: you state what a solution must satisfy, and a general-purpose solver figures out how to find one. π no objective gradient, no simplex tableau β just variables, finite domains, and constraints, attacked by an alternation of inference (prune values that cannot appear in any solution) and search (guess, propagate, backtrack). this page builds the machinery from the formalism up; the sudoku solver and hashiwokakero write-ups on this wiki are the same machinery pointed at actual puzzles.
4. Functional Programming /wiki/ccs/programming/paradigms/functional/
unlike its siblings on this branch of the wiki β linear, quadratic, integer β this “programming” really is about writing programs. functional programming is the discipline of building software out of expressions that are evaluated rather than statements that are executed: no assignment, no mutation, no time. what remains is algebra, and algebra is something you can reason about. π
5. Goal /wiki/ccs/programming/paradigms/goal/
most optimisation asks for the best; goal programming asks for good enough, several times over. π you attach a numeric target to each of several objectives, measure how far the plan misses each target, and minimise the misses you dislike. the philosophy is herbert simon’s satisficing1 β real decision makers do not maximise a grand utility function, they set aspiration levels and stop when they are met β and the machinery is pure linear programming: goal programming was invented by charnes and cooper as an LP device2 and remains the most-used technique in practical multi-criteria decision making precisely because it never leaves LP territory.
6. Integer Programming /wiki/ccs/programming/paradigms/integer/
take a linear program and add one word β integer β and the complexity class jumps from polynomial to NP-hard. that word buys expressive power nothing continuous can match: yes/no decisions, either/or logic, fixed costs, sequencing, assignment. integer programming is the lingua franca of operations research precisely because “decide” is not a convex verb. π
7. Multi-objective /wiki/ccs/programming/paradigms/multi-objective/
single-objective optimisation is a polite fiction. real decisions trade cost against quality, return against risk, speed against accuracy β and the objectives disagree, otherwise you would not have listed them separately. π multi-objective optimisation refuses to mash them into one number prematurely. the price of that honesty: “the optimum” stops being a point and becomes a set β the pareto front β and half the subject is about how to trace it, the other half about how to pick from it.
8. Quadratic Programming /wiki/ccs/programming/paradigms/quadratic/
promote the objective of a linear program from a plane to a bowl and you get quadratic programming: minimise a quadratic function over a polyhedron. it is the smallest step beyond LP, yet it captures a startling share of applied mathematics β support vector machines, portfolio selection, ridge regression, model-predictive control β because “squared penalty subject to linear rules” is how half the world states its preferences. π
9. Robust /wiki/ccs/programming/paradigms/robust/
every linear program you have ever written down was a lie: the coefficients came from measurements, forecasts and vendor spreadsheets, and the optimal vertex β sitting, by design, on the boundary of the feasible region β shatters the moment any of them wobbles. π robust optimisation is the pessimist’s response: declare a set \(\mathcal{U}\) of realisations you refuse to be hurt by, and demand feasibility for every member of it. no distributions, no expectations, no scenarios β just a set and a worst case. the surprise, and the reason the field exists, is that this worst case can usually be folded back into a deterministic problem of the same (or nearly the same) complexity class.1
10. Wiki /wiki/
Knowledge is a paradox. The more one understand, the more one realises the vastness of his ignorance.