Skip to content

perf: bound symmetry work, incremental affine, and cap unproductive affine fill-in#954

Merged
rasros merged 4 commits into
mainfrom
perf/presolve-pass-incremental
Jul 3, 2026
Merged

perf: bound symmetry work, incremental affine, and cap unproductive affine fill-in#954
rasros merged 4 commits into
mainfrom
perf/presolve-pass-incremental

Conversation

@rasros

@rasros rasros commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #950 for #937's presolve outliers (part of #951).

What changed (4 commits)

  1. Symmetry generator-search budget charges the per-round O(nInt+nBool) variable work (signature-array build + assignColours re-grouping), not just arc count. On a sparse model the arc-only charge under-counted enormously, letting colour refinement run thousands of unbounded rounds.
  2. Affine singleton elimination runs over an incrementally maintained working set + occurrence index, so each elimination is O(occurrences of the pivot) rather than rebuilding the whole factor list/occurrence index per elimination.
  3. Pass-input reuse: the session returns the same passInput view across passes that change nothing, so a round-to-fixpoint sweep no longer rebuilds the live-factor list + domain snapshot for every pass that fires an empty delta.
  4. Underdetermined-model affine cap: on a model with far more integer variables than factors (int-vars/factors > 8), a fold whose fill-in (degree-1)·|termVars| exceeds 64 is deferred (the variable stays, solved directly). Such models fold their many equality-defined variables into a few constraints, inflating them without simplifying — dense, unproductive fill-in. The ratio is taken once from the original problem (PresolveContext.affineUnderdetermined) so it stays stable as later rounds shrink the factor set.

Why

  • hundred_doors_unoptimized presolved in 22.9s at budget=0, entirely in symmetry colour refinement that finds no symmetry → 89ms.
  • gardner_dinner rebuilt the affine occurrence index every elimination → 2.9s to 0.68s.
  • bus_scheduling presolved in 2.09s at budget=0, entirely dense affine fill-in (164 unit-eliminations folding into a few wide constraints) that is unproductive — the solve is UNKNOWN with or without it → 386ms.

Testing

  • Byte-identical presolve oracle over the 130 mzn-bench instances (budget=0 dry-run-presolve): commits 2 and 3 are byte-identical. Commits 1 and 4 change presolve output on a small, bench-parity-validated set: symmetry on cargo/challenge01, carpet-cutting/mzn_rnd_test.01, latin-squares/03; the affine cap on bus_scheduling only (165→164, i.e. affine deferred).
  • Bench-parity on the changed instances (-t, -s): identical status/optimum/no regression. bus_scheduling is UNKNOWN with solutions=0 both with and without the cap (the deferred affine was dead weight — it added 99 constraints). The three symmetry instances solve identically (breaking fewer symmetries only removes symmetric duplicates).
  • ./gradlew check lintDocs: BUILD SUCCESSFUL.

Notes

  • Commits 1 and 4 are sound behaviour changes validated by bench-parity (not the byte-identical oracle), since they alter presolve output; commits 2–3 are byte-identical.
  • The incremental affine (commit 2) is byte-identical but not a uniform speedup: on a very dense model (numbrix) the per-elimination removeValue scans can exceed the whole-list rebuild they replace (2.4s→3.1s); still >0.5s either way. gardner_dinner is the clear win.
  • Remaining >0.5s outliers (ma-path, liner-sf, oocsp, 2DBinPacking) are distributed/inherent costs (subsume over 245k factors, Element-GAC over sparse domains, 16-round iteration) tracked under Make presolve passes incremental to clear the last 7 over-0.5s instances #951 — no clean structural lever like bus's.

@rasros rasros changed the title perf: bound symmetry refinement work and make affine elimination incremental perf: bound symmetry work, incremental affine, and cap unproductive affine fill-in Jul 3, 2026
@rasros rasros merged commit 2d6d16e into main Jul 3, 2026
2 checks passed
@rasros rasros deleted the perf/presolve-pass-incremental branch July 3, 2026 19:50
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