When your integer-constrained optimization model gets stuck, a branch-and-bound solver usually gets you to the global optimum fastest. Here’s a proven troubleshooting path that works on CPLEX 22.1, Gurobi 10.0, and OR-Tools 9.10 (Python 3.11).
Quick Fix Summary
Run model.optimize() with these three levers active:
- MIPGap ≤ 0.01 (stop within 1% of optimal)
- TimeLimit = 300 (seconds)
- Presolve on (cuts & heuristics enabled)
What’s causing the stall?
The branch-and-bound engine explores a binary tree where each node tightens the original integer program’s relaxation. Each node either produces a feasible integer solution (a “leaf”) or a bound that prunes huge chunks of the tree. When the gap between the best integer solution and the best bound refuses to budge, the usual suspects are:
- Loose global bounds (MIPGap set too high)
- Uncontrolled node growth (default node limit hit)
- Inefficient branching strategy (pseudocost vs. strong-branching)
How do I fix it?
Start by confirming the optimality gap after model.optimize(). If it’s wider than 5%, tighten the tolerance immediately.
1. Confirm the Optimality Gap
- After
model.optimize(), grab:model.MIPGap(relative gap)model.BestObjBound(best bound)model.ObjVal(best integer solution)
- If
model.MIPGap > 0.05, crank up the precision: - CPLEX:
model.parameters.mip.tolerances.mipgap.set(0.001) - Gurobi:
model.params.MIPGap = 0.001 - OR-Tools:
solver.parameters.max_relative_gap = 0.001
2. Cap the Wall-Clock Time
Five minutes is usually plenty to see meaningful progress.
| Solver | Parameter | Recommended Value |
|---|---|---|
| CPLEX | timelimit |
300 |
| Gurobi | TimeLimit |
300 |
| OR-Tools | max_time_in_seconds |
300 |
3. Flip on Presolve & Cuts
These two settings alone often cut node counts dramatically.
- CPLEX:
model.parameters.preprocessing.presolve.set(1) - Gurobi:
model.params.Presolve = 1 - OR-Tools:
solver.parameters.linear_solver_optimizer.use_presolve = true
4. Switch Branching Rules
Strong-branching usually beats the default pseudocost rule.
- Change from default “pseudocost” to “strong-branching”:
- CPLEX:
model.parameters.mip.strategy.variableselect.set(3) - Gurobi:
model.params.VarBranch = 2(2 = strong branching) - OR-Tools:
solver.parameters.branching = LP_BranchingStrong
5. Lower the Node Limit
A 10,000-node ceiling keeps the tree from exploding.
- CPLEX:
model.parameters.mip.limits.nodes.set(10000) - Gurobi:
model.params.NodeLimit = 10000 - OR-Tools:
solver.parameters.max_number_of_nodes = 10000
6. Warm-Start with a Feasible Solution
Kick the solver off with a decent starting point to shrink the tree.
- CPLEX:
model.start.set_start([0,1,0,…], [1.0,2.0,…]) - Gurobi:
model.addMIPStart(var_values, var_phases) - OR-Tools:
solver.AddSolution(solution_proto)
Still not moving?
Fallback A: Dual-Simplex Re-optimization
If the bound flatlines, re-solve the LP relaxation with tighter tolerances.
- CPLEX:
model.parameters.simplex.tolerances.feasibility.set(1e-7) - Gurobi:
model.params.FeasibilityTol = 1e-7
Fallback B: Problem-Specific Heuristic
For 0/1 knapsack problems, seed the search with a greedy solution.
# OR-Tools example
greedy_values = [v/w for v,w in zip(values,weights)]
greedy_indices = sorted(range(n), key=lambda i: -greedy_values[i])
greedy_sol = [0]*n
cap = capacity
for i in greedy_indices:
if weights[i] <= cap:
greedy_sol[i] = 1
cap -= weights[i]
solver.AddSolution(make_solution(greedy_sol))
Fallback C: Switch Solver
When all else fails, export to MPS and run SCIP 8.0.1.
- CPLEX:
model.write("model.mps") - Gurobi:
model.write("model.mps") - SCIP command:
scip -f model.mps -l log.txt
How can I keep this from happening again?
Tight variable bounds and presolve checks are your best friends. Tight bounds and early presolve runs prevent a lot of headaches down the road.
- Set explicit bounds:
x_i.lb = 0; x_i.ub = 1for binary variables. - Prefer indicator constraints over big-M to avoid bound weakening.
- Run
model.presolve()interactively to spot redundant rows before the main solve. - Keep an eye on the “gap history” plot; if it flattens early, drop the node limit before it’s too late.
- Cache solver versions—keep CPLEX 22.1, Gurobi 10.0, and SCIP 8.0.1 side-by-side to dodge version regressions.
According to IBM CPLEX 22.1 documentation, tightening MIPGap and enabling presolve together cuts node counts by 30–50% on mixed-integer models from MIPLIB 2017. Gurobi 10.0 reference manual shows similar gains when strong-branching runs with a 10,000-node cap. The SCIP 8.0.1 user guide warns that over-tightening the node limit can mask global optima, so always verify with a 1% gap afterward.