Skip to content

Commit 2af03e5

Browse files
andigclaude
andauthored
mip: extend big-M coefficient strengthening to >= rows (large models) (#17)
presolve already tightened binary big-M coefficients CglProbing-style, but only on pure <= rows. This adds the exact >= mirror (both sub-cases preserve the integer feasible set), matching CBC's Cgl0003 strengthening. Gated to len(p.Rows) > 3000: on large root-heavy models the shrink lets faceWalk reach the incumbent in far fewer pivots, but on smaller proof-tree cases the perturbed cut set regresses the branch tree. The row-count gate mirrors the existing NumRows() > 1500 sbDepth gate. Golden suite (../optimizer): case 021 faceWalk pivots 16908 -> 6732 (-60%), 3.09s -> 2.30s (-26%); case 020 unchanged (185 nodes, proven); suite -12%, all cases proven optimal, no regressions. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent a8890ba commit 2af03e5

1 file changed

Lines changed: 43 additions & 20 deletions

File tree

mip/presolve.go

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,12 @@ func presolvePass(p *problem.Problem) (changed bool) {
126126
}
127127
}
128128

129-
// coefficient tightening: binaries in pure <= rows
130-
if !(rub < inf && rlb == -inf) {
129+
// coefficient tightening: binaries in pure one-sided big-M rows.
130+
// The >= mirror is gated to large models: on small proof-tree
131+
// cases it perturbs the cut set for no gain (see perf notes).
132+
pureLE := rub < inf && rlb == -inf
133+
pureGE := rlb > -inf && rub == inf && len(p.Rows) > 3000
134+
if !pureLE && !pureGE {
131135
continue
132136
}
133137
for k, j := range r.Idx {
@@ -136,24 +140,43 @@ func presolvePass(p *problem.Problem) (changed bool) {
136140
if !c.Integer || c.LB != 0 || c.UB != 1 {
137141
continue
138142
}
139-
_, omax := others(k)
140-
if math.IsInf(omax, 1) {
141-
continue
142-
}
143-
switch {
144-
case a < 0 && omax > rub && omax+a < rub:
145-
// y=1 side is slack: shrink |a| so it just reaches
146-
setCoef(p, ri, k, rub-omax)
147-
changed = true
148-
recompute()
149-
case a > 0 && omax < rub && omax+a > rub:
150-
// y=0 side is slack: shift a and the rhs down together
151-
delta := rub - omax
152-
setCoef(p, ri, k, a-delta)
153-
r.RHS -= delta
154-
rub = r.RHS
155-
changed = true
156-
recompute()
143+
omin, omax := others(k)
144+
if pureLE {
145+
if math.IsInf(omax, 1) {
146+
continue
147+
}
148+
switch {
149+
case a < 0 && omax > rub && omax+a < rub:
150+
// y=1 side is slack: shrink |a| so it just reaches
151+
setCoef(p, ri, k, rub-omax)
152+
changed = true
153+
recompute()
154+
case a > 0 && omax < rub && omax+a > rub:
155+
// y=0 side is slack: shift a and the rhs down together
156+
delta := rub - omax
157+
setCoef(p, ri, k, a-delta)
158+
r.RHS -= delta
159+
rub = r.RHS
160+
changed = true
161+
recompute()
162+
}
163+
} else { // pureGE: mirror of pureLE under negation
164+
if math.IsInf(omin, -1) {
165+
continue
166+
}
167+
switch {
168+
case a > 0 && omin < rlb && omin+a > rlb:
169+
setCoef(p, ri, k, rlb-omin)
170+
changed = true
171+
recompute()
172+
case a < 0 && omin > rlb && omin+a < rlb:
173+
delta := omin - rlb
174+
setCoef(p, ri, k, a+delta)
175+
r.RHS += delta
176+
rlb = r.RHS
177+
changed = true
178+
recompute()
179+
}
157180
}
158181
}
159182
}

0 commit comments

Comments
 (0)