Part II · trunk A · integer programming · spine
Split the problem, bound each piece with an LP, and throw away every piece that cannot win. What to split on and which piece to look at next decide everything.
needs 05~2 h slides + 3 h lablab: uv run co lab 07
07 · overview
Branching on a fractional variable, three reasons to prune, the global bound, and the gap.
Depth-first finds solutions. Best-first proves optimality. What solvers actually ship.
Most-fractional (as good as random), pseudocosts, strong branching, and reliability branching.
Primal heuristics, reading the gap, and a list of what makes SCIP 6–75× better than yours.
Words with a dotted underline are glossary terms: click one for its definition. G lists every term in the unit, N shows the notes. Optional extra: interactive figures, linked from the slides they support by a small ▸ chip; skip them freely.
section 1
Divide the feasible set, and conquer only the pieces that could still contain the optimum.
07 · tree · example
Dots: the 7 integer points. Ring: the root LP optimum. Branching on $y$ keeps the strip $y \le 2$ and the empty region $y \ge 3$; the gap between the dashed lines holds no integer point.
Minimizing $-x - 5y$ means maximizing $x + 5y$: $y$ is worth five times as much.
Dropping integrality, the optimum is the corner $(2, \tfrac83)$ with value $-\tfrac{46}{3} = -15.33$. That is a lower bound: no integer point does better.
$y = 2.67$ is fractional, so branch on it: $y \le 2$ or $y \ge 3$. The next slide is the whole tree.
07 · tree
07 · tree
Upper bound $U$: the incumbent's objective value, or $+\infty$ before one is found.
Lower bound $L$: the smallest LP bound among open nodes. Every unexplored integer point lies in some open node.
$$L \le z^* \le U, \qquad \text{gap} = \frac{U - L}{|U|}$$On the example, after the root and its two children: $U = +\infty$, $L = -12.29$. After $(2,2)$ is found: $U = -12$, and the only open node has bound $-4.67 \ge U$, so $L$ jumps to $-12$ and the gap closes to 0.
The solver stops when the gap is 0, or below a tolerance (HiGHS and Gurobi default to a relative gap of $10^{-4}$; SCIP to 0).
Push $U$ down: find good integer solutions early, so pruning by bound starts early.
Push $L$ up: branch and cut so the open nodes' bounds rise toward $z^*$.
Everything in a MILP solver serves one of these two. A solver log is two columns, "primal bound" and "dual bound", converging.
section 2
Which open node to look at next.
07 · selection
Always a child of the last node. Reaches integral leaves quickly, so an incumbent exists early. Small memory. It can spend a long time in a bad subtree.
Always the open node with the smallest bound. That raises $L$ as fast as possible. The frontier can grow huge, and incumbents come late.
A node whose bound is below the optimum can never be pruned, so every search must branch on it. Best-first branches on those nodes and (up to ties) nothing else.
full argument in the notes (N)
Lab then, most-fractional branching:
| instance | DFS | best |
|---|---|---|
| knapsack 22×3 #0 | 1 373 | 251 |
| knapsack 22×3 #1 | 3 681 | 1 593 |
| UFL 8×20, M = 10C | 87 | 79 |
What solvers ship: hybrids of node selection. Dive depth-first from a promising node to find a solution, then jump back to a node with a good bound or a good estimate (best estimate), with periodic plunges.
07 · selection · example
| pop | node | bound | outcome |
|---|---|---|---|
| 1 | root | −15.33 | branch on $y$ |
| 2 | $y \le 2$ | −12.29 | branch on $x$ |
| 3 | $x \le 2$ | −12 | integral: $U = -12$ |
| 4 | $x \ge 3$ | −4.67 | bound $\ge U$: pruned |
It takes the smaller bound first, $-12$ before $-4.67$, finds the optimum there, and the other node dies without being branched.
| pop | node | bound | outcome |
|---|---|---|---|
| 1 | root | −15.33 | branch on $y$ |
| 2 | $y \le 2$ | −12.29 | branch on $x$ |
| 3 | $x \ge 3$ | −4.67 | branch on $y$ |
| 4 | $y \le 0$ | −3.14 | branch on $x$ |
| 5 | $x \le 3$ | −3 | integral: $U = -3$ |
| 6 | $x \le 2$ | −12 | integral: $U = -12$ |
It dives into the last child created, $x \ge 3$, and digs to a poor solution $(3, 0)$ before coming back. Four extra nodes, two of them infeasible children.
section 3
Which variable to split on. The single biggest influence on tree size.
07 · branching
Most-fractional branching. Intuition: a variable at 0.5 is "most undecided" (its fractionality is largest), so decide it.
Measured: about as good as branching on a random fractional variable (Achterberg, Koch & Martin 2005, on MIPLIB).
Fractionality says nothing about how much the bound will move. A variable at 0.5 with a zero-cost column changes nothing when fixed, while a variable at 0.9 on a tight constraint can end the subtree.
For each candidate $j$, the bound improvements $\Delta_j^-$ and $\Delta_j^+$ of the two children, combined with the product score
$$\text{score}_j = \max(\Delta_j^-, \varepsilon) \cdot \max(\Delta_j^+, \varepsilon)$$A product rewards moving both children, and a zero on one side isn't fatal because of the $\varepsilon$.
07 · branching
Compute $\Delta^\pm_j$ exactly: solve both child LPs for each candidate, then branch on the best. The best node counts known, and the most LP solves by far.
Lab totals, best-first: 1 271 nodes but 4 641 LP solves, 3.2 s, against most-fractional's 1 923 nodes and 1.3 s.
Estimate $\Delta^\pm_j$ from history: the average gain per unit of fractionality seen when branching on $x_j$ before, times the current fractionality. Costs nothing per node.
Weak at the top of the tree, where there's no history yet. Lab totals: 1 581 nodes, barely better than most-fractional.
Use strong branching on a variable until its pseudocosts are reliable (a few observations each way), then trust the pseudocosts. This is the default rule in SCIP, and the node count of strong branching at roughly the cost of pseudocosts.
07 · branching · example
At node $y \le 2$ (bound $-12.29$) we branched on $x = 2.29$:
$\Delta^- = -12 - (-12.29) = 0.29$, $\quad \Delta^+ = -4.67 - (-12.29) = 7.62$
score $= 0.29 \times 7.62 = 2.18$
| candidate | $\Delta^-$ | $\Delta^+$ | sum | product |
|---|---|---|---|---|
| A | 0.1 | 8 | 8.1 | 0.8 |
| B | 2 | 3 | 5 | 6 |
A sum prefers A; the product prefers B, which moves both children.
| branch | gain | ÷ fractional part | per unit |
|---|---|---|---|
| $y$ down, at root | 3.05 | $\div\ 0.67$ | 4.57 |
| $x$ down | 0.29 | $\div\ 0.29$ | 1.00 |
| $x$ up | 7.62 | $\div\ 0.71$ | 10.67 |
Next time $x$ is fractional elsewhere in the tree, with fractional part 0.5, the pseudocost predicts $\Delta^- \approx 0.5 \times 1.00$ and $\Delta^+ \approx 0.5 \times 10.67$, without solving an LP.
$y$ up was infeasible, so no gain was recorded for it.
section 4
Finding good solutions early, and what else solvers do.
07 · primal side
A good incumbent early lets bound pruning start early. Solvers spend a share of their time on primal heuristics that find one:
No heuristics: incumbents only come from integral LP leaves. That is one reason the DFS runs in then are so much larger than SCIP's. SCIP typically has a near-optimal solution before its tree has a dozen nodes.
On the running example, rounding the root LP $(2, 2.67)$ down gives $(2, 2)$, the optimum, before any branching.
Unit 26's local search and unit 19's LNS are the same idea used as stand-alone methods.
07 · the gap
| instance | yours, best | SCIP bare* | SCIP | HiGHS |
|---|---|---|---|---|
| knapsack 22×3 #0 | 157 | 87 | 8 | 9 |
| knapsack 22×3 #1 | 1 039 | 203 | 161 | 208 |
| UFL 8×20, M = 10C | 75 | 41 | 1 | 1 |
"Yours, best": the fewest nodes among your six variants. * SCIP bare: presolve and cuts off. The ratio of your column to SCIP's is the factor of shame.
07 · lab
uv run co test 07
uv run co then 07 # the 3x2 matrix, SCIP bare, SCIP and HiGHS default, factor of shame
07 · checkpoint
Wolsey, ch. 7.
Achterberg, Koch & Martin, "Branching rules revisited", ORL 33 (2005).
Achterberg (2007), thesis, ch. 5–6 (optional).
08 cutting planes, which test the first hypothesis · 16 DP, where a third exact method meets this one · 31 learns a branching rule for this tree.