Skip to content

mip presolve: worklist-fixpoint propagation, singleton-column elimination#14

Merged
andig merged 2 commits into
mainfrom
mip-presolve-reductions
Jul 6, 2026
Merged

mip presolve: worklist-fixpoint propagation, singleton-column elimination#14
andig merged 2 commits into
mainfrom
mip-presolve-reductions

Conversation

@andig

@andig andig commented Jul 6, 2026

Copy link
Copy Markdown
Member

Summary

Two presolve-side reductions, follow-up to #13:

  • Bound propagation runs to fixpoint via a row worklist (mip/presolve.go). The previous 4-full-pass cap both overworked (rescanning untouched rows) and underworked (stopping short of the fixpoint on long implication chains). The worklist seeds all rows and re-enqueues only rows touching a tightened column; the fixpoint is unique since bounds only shrink, with a generous pivot-count safety cap for degenerate chains. Node-level propagation gets cheaper and tighter: golden case 020 6.9s → 6.5s, 016 0.5s → 0.2s, 018 1.2s → 0.9s.

  • Singleton-column elimination with exact postsolve (mip/eliminate.go). Costed continuous columns appearing in exactly one row are substituted out before the LP is built: a column whose cost is capped only by its row pins that row at any optimum (x = (b - rest)/a, cost folded onto the row's remaining columns); one whose row never resists its cost is fixed at its bound. Folding can re-classify same-row siblings, so the pass iterates. Solutions, duals, reduced costs and row activities are reconstructed exactly in the caller's column space, including across the warm-restart path.

An honest note on the second one: on the evcc golden instances it finds nothing — their ~616 continuous singletons are penalty slacks whose cost fights the row (a max(0, ·) term no exact linear presolve can remove). An earlier census misread them as eliminable by ignoring ObjSense. The transform does fire on minimize-form models with revenue-style singletons, e.g. anything fed through the cbc CLI, and comes with unit tests covering tight-pin, bound-fix, and partial same-row chains plus a dual-identity check.

Testing

  • go test ./... green; three new unit tests for the elimination (structure, values, duals).
  • Full 13-case golden gate green; case 020 stable at ~6.4s, trajectories bit-identical where elimination finds nothing.

🤖 Generated with Claude Code

andig and others added 2 commits July 6, 2026 11:01
Replaces the 4-full-pass cap with a worklist seeded with all rows that
re-enqueues only rows touching a tightened column. Reaches the unique
fixpoint (bounds only shrink) while doing less work per call: untouched
rows are never rescanned. Gate: 13/13 green; 020 6.9s -> 6.5s, 016
0.5s -> 0.2s, 018 1.2s -> 0.9s.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Costed continuous columns appearing in exactly one row are substituted
out before the LP is built: a column whose cost is capped only by its
row pins that row at any optimum (x = (b - rest)/a, cost folded onto
the row's remaining columns), and one whose row never resists its cost
sits at its bound. Folding can re-classify same-row siblings, so the
pass iterates to fixpoint. Solutions, duals, reduced costs and row
activities are reconstructed exactly in the caller's original space.

On the evcc golden instances this finds nothing — their 616 singletons
are penalty slacks (cost fights the row: a max(0, .) term no linear
presolve can remove); an earlier census misread them as eliminable by
ignoring ObjSense. The transform fires on minimize-form models with
revenue-style singletons, e.g. via the cbc CLI. Gate: 13/13 green,
trajectories bit-identical.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@andig andig merged commit cc2147d into main Jul 6, 2026
3 checks passed
@andig andig deleted the mip-presolve-reductions branch July 6, 2026 11:32
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