Skip to content

Presolve: skip the clique cover in variable bound aggregation when no row can be merged #3594

Closed

Description

@DoumarCharles

This mitigates the cost of aggregateVarBounds, separate from #3588.

On MIPLIB models piperout-08 and piperout-27, presolve on latest (98391ab, threads = 1)
takes 56 s and 97 s. About 40% and 25% of that is cliqueCover, called from
aggregateVarBounds.

aggregateVarBounds runs after every probing call, 9 times on piperout-08 and 10 times on
piperout-27. Each time it covers the variable bounds of every non-binary column again, with sets
of up to 1,752 binaries. Most of the cover time is in the loop that adds earlier binaries to
each clique, which is quadratic in the set size.

The problem is that almost none of these covers can change the model. mergeCliques only
rewrites the origin row of one of the clique's bounds. That origin is only set for size-2
one-sided rows, and it is cleared when the row changes. In most sets no bound has an origin row,
so every clique is skipped with row == -1.

piperout-08 piperout-27
Covered sets with / without an origin row 4 / 1,259 11 / 1,387
Cover time in sets without an origin row 23.6 s of 23.6 s 23.3 s of 23.3 s
Rows modified over all calls 1 2

Proposed fix

While setting up the cliques, note whether any bound has an origin row, separately for the VLBs
and the VUBs. If none has, skip the cover for that side. Nothing is lost: without an origin row
the merge loop cannot rewrite or remove any row.

Skipping the cover only changes numNeighbourhoodQueries, and the clique table is rebuilt with
that counter at 0 at the end of presolve.

On the 21 MIPLIB 2017 benchmark models where the aggregation modifies rows, the presolved models
are identical.

Results: latest vs. latest + patch

Model Presolve before Presolve after Reduction
piperout-08 55.6 s 35.6 s 36%
piperout-27 97.2 s 76.8 s 21%
dws008-01 6.5 s 3.3 s 49%

(One run each, both versions started together.)

Suggested diff (against 98391ab):

--- a/highs/presolve/HPresolveVarBndAggregation.cpp
+++ b/highs/presolve/HPresolveVarBndAggregation.cpp
@@ -232,15 +232,24 @@
     }
 
     // set up cliques
+    bool vlbOrigin = false;
+    bool vubOrigin = false;
     vlbs.for_each([&](HighsInt binaryCol, colImpliedBounds& bounds) {
       HighsInt val = bounds.originalBound.coef > 0 ? 1 : 0;
       vlbsClique.emplace_back(binaryCol, val);
+      if (bounds.originalBound.origin >= 0) vlbOrigin = true;
     });
     vubs.for_each([&](HighsInt binaryCol, colImpliedBounds& bounds) {
       HighsInt val = bounds.originalBound.coef < 0 ? 1 : 0;
       vubsClique.emplace_back(binaryCol, val);
+      if (bounds.originalBound.origin >= 0) vubOrigin = true;
     });
 
+    // a clique can only be merged into the origin row of one of its bounds,
+    // so skip the clique cover if there is no such row
+    if (!vlbOrigin) vlbsClique.clear();
+    if (!vubOrigin) vubsClique.clear();
+
     // clique cover
     cliquetable.cliqueCover(vlbsClique, vlbsCover);
     cliquetable.cliqueCover(vubsClique, vubsCover);

Activity

  1. self-assigned this
    on Nov 9, 2026
  2. fwesselm commented on Nov 10, 2026

    @fwesselm
    Collaborator

    Closed by #3596.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions