Repository navigation
Description
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
fwesselm commentedon Nov 10, 2026
Closed by #3596.