Skip to content

cbcgo performance: pivot-budgeted cuts, hypersparse btran, warm heuristic solves#13

Merged
andig merged 3 commits into
mainfrom
mip-cbc-heuristics
Jul 6, 2026
Merged

cbcgo performance: pivot-budgeted cuts, hypersparse btran, warm heuristic solves#13
andig merged 3 commits into
mainfrom
mip-cbc-heuristics

Conversation

@andig

@andig andig commented Jul 5, 2026

Copy link
Copy Markdown
Member

Summary

Three-commit performance push on the target MILP workload: 020 goes 9.0s → 6.9s end to end (22.6s at the start of this effort; PuLP/real-CBC reference 1.0s), 016 2.3s → 0.5s, 018 2.8s → 1.2s, 021 5.6s → 4.7s. All 13 golden cases green throughout.

simplex — hypersparse BTRAN (activation-graph guarded, bitwise-identical to the dense path, A/B property-tested), in-place sparse-LU row elimination (−31% time / −74% bytes on ColdSolve microbench), fused pivot passes, and int32/arena-consolidated factors with per-LP shared solve scratch (020 end-to-end allocations 1.54GB → 1.21GB).

mip — the structural centerpiece plus cut/heuristic work:

  • Pivot-budgeted cut rounds: every large instance was ending the cut loop by the wall clock (a round's ColdSolve dying at the MaxTime/5 deadline reads as poison), so the cut set was a wall-clock artifact that reshuffled with every engine speedup. Rounds now stop after 10×rows pivots — speed-invariant, deterministic — with the budget exit retracting the final never-revalidated batch to match the old semantics. The wall box remains as a safety cap.
  • Single-row MIR cuts seed the first two root rounds on large instances (root bound 0.334 → 0.355 on 020); later rounds measured-negative.
  • Warm heuristic child solves: RINS and completePoint were the only cold child solves; the trivial MIP start is no longer polished (instrumented rc-fix fixes 0 columns at any cutoff that far above the optimum — 45k pivots fed nothing).
  • Root-pipeline dedups (mipstart solve doubles as round 0, ledger-compensated; slack-drop reuses the loop's final state) and SOLVER_DEBUG phase/pivot attribution.

Measured dead ends documented along the way: event-terminated cut rounds, maxEtas 64/128, fixed-column pricing skips, DSE re-test — all regress or fail 020.

Test plan

  • Golden cases: 13/13 (multiple full sweeps; 020 stable at 6.8–7.0s across repeated runs)
  • go test ./... green; gofmt clean; every commit builds standalone
  • Cut trajectory on 020 verified bitwise round-by-round across the dedup changes

🤖 Generated with Claude Code

andig and others added 3 commits July 6, 2026 09:42
…ed factors

Sparse-LU kernel elimination rewrites rows in place (write index never
passes the read index, fill-in appended after the scan, O(1) mark-array
membership): ColdSolve 300x300 -31% time, -74% bytes, -64% allocs.

btran gathered every singleton pivot's column regardless of rhs
sparsity while ftran already skipped zero pivots naturally. A
factorize-time row->readers graph now activates only reachable pivots
for mostly-zero rhs (dualRun's unit vectors); skipped pivots would
compute exactly 0, so results match the dense path bitwise (A/B
property test included). Activation uses epoch stamps and a
written-rows restore, so a sparse solve does no O(m) clears.

pivot's three O(m) alpha passes (value update, nnz count, eta
extraction) fuse into one with exact-size copies. Per-factor data is
int32-compacted (triPivot 24B->16B, rowKIdx) and arena-consolidated,
and all solve scratch lives once on the per-LP factorWS: a
refactorization allocates a handful of buffers instead of a dozen
(020 end-to-end: 1.54GB -> 1.21GB allocated).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…solves

Single-row MIR cuts (CglMixedIntegerRounding2-style, VUB/bound
substitution with a divisor search) seed the first two root rounds on
large instances; later rounds are measured-negative (row bloat, face
drift). Root bound on the target case: 0.334 -> 0.355.

Cut rounds are budgeted in pivots (10x rows) instead of wall clock:
every large instance was ending the loop by the MaxTime/5 deadline (a
round's ColdSolve dying there reads as poison), making the cut set a
wall-clock artifact that reshuffled with every engine speed change. A
budget exit retracts the final never-revalidated batch, matching the
poison path's semantics. The wall box remains as a safety cap. 016
2.3s -> 0.5s and 018 2.8s -> 1.2s shed rounds that were pure waste.

Heuristic child solves warm-start like ordinary nodes: RINS and
completePoint were the only cold ones (a full phase-1 ColdSolve per
attempt). The trivial MIP start is no longer polished — instrumented
reducedCostFix fixes 0 columns at any cutoff that far above the
optimum, so the 45k-pivot polish fed nothing downstream; tree
heuristics still polish real incumbents. Root-pipeline dedups: the
mipstart block's root solve doubles as cut round 0 when rc-fixing
left the LP untouched (ledger-compensated so the cut set is
unchanged), and the slack-drop pass reuses the loop's final state.

SOLVER_DEBUG now reports probe productivity, per-round cut pivots,
phase wall-clock marks, rc-fix counts, and per-heuristic pivot
attribution. Case 020 end to end: 22.6s at session start, 9.0s after
the tabu fix, 6.9s now (PuLP/CBC reference: 1.0s).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sparse btran, factor arenas

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@andig andig force-pushed the mip-cbc-heuristics branch from d4337b8 to e0137d3 Compare July 6, 2026 07:43
@andig andig changed the title mip: single-row MIR root cuts — 020 proves in a third of the time cbcgo performance: pivot-budgeted cuts, hypersparse btran, warm heuristic solves Jul 6, 2026
@andig andig merged commit 229d4f1 into main Jul 6, 2026
2 checks passed
@andig andig deleted the mip-cbc-heuristics branch July 6, 2026 08:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant