Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 29 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,28 @@ It fails on any failure not listed in `testdata/pulp_known_failures.txt`.
target instances it trades the tuned root trajectory for no bound
gain.
- **Basis factorization**: singleton triangularization with a sparse-LU
kernel and product-form (eta) updates — O(nnz) pivots, periodic
refactorization. No dense inverse.
kernel (in-place row elimination, counted arenas — allocation-free on
the hot path) and product-form (eta) updates — O(nnz) pivots, periodic
refactorization. No dense inverse. Per-factor data is int32-compacted
and arena-consolidated, and all solve scratch is shared per LP, so a
refactorization costs a handful of allocations instead of a dozen.
- **Presolve**: iterated activity-based bound tightening, big-M
coefficient tightening for binaries, and CglProbing-style binary probing
(infeasibility fixing plus integer-only merged implied bounds).
- **Cuts**: Gomory mixed-integer cuts at the root, with support/dynamism
hygiene, bound-driven round control, and retraction (with retries) of
batches that degrade the LP numerically; probing implication cuts
hygiene, and retraction (with retries) of batches that degrade the LP
numerically; rounds are budgeted in pivots (speed-invariant work
units), so the cut set is a deterministic function of the problem and
survives engine speed changes — wall clock only remains as a safety
cap; probing implication cuts
(CglProbing as a cut generator) on large instances, with slackened
implied bounds so propagation drift can never cut off the optimum;
TwoMIR-lite cuts (sparse pairwise tableau-row aggregations through the
MIR derivation) on large instances; slack cuts are dropped before the
MIR derivation) on large instances; single-row MIR cuts
(CglMixedIntegerRounding2-style, VUB/bound substitution with a divisor
search) seed the first two rounds on large instances — later MIR
rounds are measured-negative (row bloat, face drift); slack cuts are
dropped before the
tree so only root-active rows ride into node re-solves.
- **Branch and bound**: best-first with depth-first plunging, warm-started
child bases, node-level bound propagation on branching, monotone bound
Expand All @@ -87,12 +97,14 @@ It fails on any failure not listed in `testdata/pulp_known_failures.txt`.
the incumbent fixes the variable at the node without spending a
branch; pseudocost selection deeper (reliability-branching shape).
- **Heuristics**: caller-provided MIP start (`mip.Model.MIPStart`,
completed before the cut loop so reduced-cost fixing bites), 1-opt
incumbent polish (CbcHeuristicLocal-style binary flips via warm dual
re-solves), face walk (least-degradation dive along the LP-optimal
completed via a warm child solve before the cut loop so reduced-cost
fixing bites; the trivial start is deliberately not polished — measured
as pure pivot burn), 1-opt incumbent polish on real tree incumbents
(CbcHeuristicLocal-style binary flips via warm dual re-solves), face walk (least-degradation dive along the LP-optimal
face — proves optimality outright on degenerate alternate-optima
instances), RENS, feasibility pump, batch rounding dive, RINS-lite
with exponential failure backoff; heuristic bursts are time-boxed
warm-started from the node's own basis, with exponential failure
backoff; heuristic bursts are time-boxed
(root MaxTime/3, deeper MaxTime/8) and skipped once the incumbent
sits near the node bound, so the tree keeps its budget.
- **Anti-degeneracy**: EXPAND (Gill et al.) on the primal ratio test —
Expand All @@ -106,9 +118,9 @@ It fails on any failure not listed in `testdata/pulp_known_failures.txt`.

## Missing vs. real CBC

- **Cut families beyond GMI, probing and pairwise TwoMIR**: no knapsack
cover, clique, flow-cover, or lift-and-project cuts; no cuts below
the root. A sound multi-row c-MIR generator (equality-chain
- **Cut families beyond GMI, probing, single-row MIR and pairwise
TwoMIR**: no knapsack cover, clique, flow-cover, or lift-and-project
cuts; no cuts below the root. A sound multi-row c-MIR generator (equality-chain
aggregation with exact variable-bound substitution, property-tested)
exists in `mip/cmir.go` but stays unwired: measured on the target
instances it separates nothing that GMI + probing have not already
Expand All @@ -117,10 +129,11 @@ It fails on any failure not listed in `testdata/pulp_known_failures.txt`.
violated row (plus the revisit tabu) instead of dual steepest edge,
and runs unperturbed. The Clp-style engine (DSE, Harris ratio test,
cost perturbation — `simplex/dual2.go`) is property-tested but gated
off as measured-negative on the target instances. FTRAN/BTRAN are
always dense-vector solves; Clp's hypersparse triangular solves are
not implemented (measured ~20% result density here, so the payoff is
bounded).
off as measured-negative on the target instances. BTRAN is hypersparse
(activation-graph guarded, bitwise-identical to the dense path) for
mostly-zero right-hand sides and FTRAN skips zero pivots naturally,
but there is no Clp-style hypersparse bookkeeping across the eta file
(measured ~20% result density bounds the further payoff).
- **No CglPreProcess-style reductions**: presolve tightens bounds and
coefficients but never eliminates rows/columns, so node LPs stay large
(real CBC works on a ~4x smaller reduced model for these instances).
Expand Down
32 changes: 32 additions & 0 deletions mip/cmir.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,38 @@ func linkVar(p *problem.Problem, r1, r2 int) int {
// divisor/complement search, and adds violated cuts. Returns rows added.
var cmirDbg struct{ windows, bails, derives, noBin, f0rej, violrej int }

// rowMIRCuts derives single-row MIR cuts (CglMixedIntegerRounding2-style)
// from the first nRows original rows: no aggregation, just VUB/bound
// substitution and the divisor search on each row in both directions.
func (mo *Model) rowMIRCuts(x []float64, nRows int) int {
p := mo.P
vubs := mo.detectVUBs()
cmirDbg = struct{ windows, bails, derives, noBin, f0rej, violrej int }{}
added := 0
agg := map[int]float64{}
for ri := 0; ri < nRows && added < maxCutsPer; ri++ {
r := &p.Rows[ri]
clear(agg)
for pos, j := range r.Idx {
agg[j] += r.Coef[pos]
}
rl, ru := r.Bounds()
if ru < problem.Inf {
if mo.cmirDerive(agg, ru, 1, vubs, x) {
added++
}
}
if rl > -problem.Inf {
if mo.cmirDerive(agg, rl, -1, vubs, x) {
added++
}
}
}
debugf("rowmir: vubs=%d derives=%d noBin=%d f0rej=%d violrej=%d added=%d",
len(vubs), cmirDbg.derives, cmirDbg.noBin, cmirDbg.f0rej, cmirDbg.violrej, added)
return added
}

func (mo *Model) cmirCuts(x []float64) int {
vubs := mo.detectVUBs()
if len(vubs) == 0 {
Expand Down
Loading