From 981a2de166045ae60a95c63c5179b10dc23e8d8b Mon Sep 17 00:00:00 2001 From: fwesselm Date: Tue, 4 Nov 2025 11:38:06 +0100 Subject: [PATCH 1/5] WIP --- highs/presolve/HPresolve.cpp | 55 +++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index 8f3f7fc01d..28baf27057 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1157,7 +1157,11 @@ HPresolve::Result HPresolve::dominatedColumns( // count number of fixed columns and modified bounds HighsInt numFixedCols = 0; - HighsInt numModifiedBounds = 0; + HighsInt numFixedColsPredBndAnalysis = 0; + HighsInt numModifiedBoundsPredBndAnalysis = 0; + + // perform predictive bound analysis? + bool allowPredBndAnalysis = false; for (HighsInt j = 0; j < model->num_col_; ++j) { // skip deleted columns @@ -1254,20 +1258,22 @@ HPresolve::Result HPresolve::dominatedColumns( if (lowerBound > model->col_lower_[col] + primal_feastol) { if (model->integrality_[col] != HighsVarType::kContinuous) lowerBound = std::ceil(lowerBound - primal_feastol); - if (lowerBound == model->col_upper_[col]) + if (lowerBound == model->col_upper_[col]) { + numFixedColsPredBndAnalysis++; HPRESOLVE_CHECKED_CALL(fixCol(col, HighsInt{1})); - else if (model->integrality_[col] != HighsVarType::kContinuous) { - numModifiedBounds++; + } else if (model->integrality_[col] != HighsVarType::kContinuous) { + numModifiedBoundsPredBndAnalysis++; changeColLower(col, lowerBound); } } if (upperBound < model->col_upper_[col] - primal_feastol) { if (model->integrality_[col] != HighsVarType::kContinuous) upperBound = std::floor(upperBound + primal_feastol); - if (upperBound == model->col_lower_[col]) + if (upperBound == model->col_lower_[col]) { + numFixedColsPredBndAnalysis++; HPRESOLVE_CHECKED_CALL(fixCol(col, HighsInt{-1})); - else if (model->integrality_[col] != HighsVarType::kContinuous) { - numModifiedBounds++; + } else if (model->integrality_[col] != HighsVarType::kContinuous) { + numModifiedBoundsPredBndAnalysis++; changeColUpper(col, upperBound); } } @@ -1346,6 +1352,7 @@ HPresolve::Result HPresolve::dominatedColumns( auto checkRow = [&](HighsInt row, HighsInt col, HighsInt direction, double bestVal, bool boundImplied, bool hasCliques) { storeRow(row); + bool onlyPredBndAnalysis = !boundImplied && !hasCliques; for (const HighsSliceNonzero& nonz : getStoredRow()) { // get column index HighsInt k = nonz.index(); @@ -1360,7 +1367,7 @@ HPresolve::Result HPresolve::dominatedColumns( bool sameVarType = varsHaveSameType(col, k); // skip checks if nothing to do - if (!boundImplied && !hasCliques && !sameVarType) continue; + if (onlyPredBndAnalysis && !sameVarType) continue; // try to fix variables or strengthen bounds // check already known non-zeros in respective columns in advance to @@ -1383,26 +1390,34 @@ HPresolve::Result HPresolve::dominatedColumns( return Result::kOk; }; + // check if bounds are implied or there are cliques + bool lowerImplied = isLowerImplied(j); + bool upperImplied = isUpperImplied(j); + bool hasNegCliques = + isBinary(j) && mipsolver->mipdata_->cliquetable.numCliques(j, 0) > 0; + bool hasPosCliques = + isBinary(j) && mipsolver->mipdata_->cliquetable.numCliques(j, 1) > 0; + // use row 'bestRowMinus' - if (bestRowMinus != -1) - HPRESOLVE_CHECKED_CALL(checkRow( - bestRowMinus, j, HighsInt{-1}, ajBestRowMinus, isLowerImplied(j), - isBinary(j) && - mipsolver->mipdata_->cliquetable.numCliques(j, 0) > 0)); + if (bestRowMinus != -1 && + (allowPredBndAnalysis || lowerImplied || hasNegCliques)) + HPRESOLVE_CHECKED_CALL(checkRow(bestRowMinus, j, HighsInt{-1}, + ajBestRowMinus, lowerImplied, + hasNegCliques)); // use row 'bestRowPlus' - if (!colDeleted[j] && bestRowPlus != -1) - HPRESOLVE_CHECKED_CALL(checkRow( - bestRowPlus, j, HighsInt{1}, ajBestRowPlus, isUpperImplied(j), - isBinary(j) && - mipsolver->mipdata_->cliquetable.numCliques(j, 1) > 0)); + if (!colDeleted[j] && bestRowPlus != -1 && + (allowPredBndAnalysis || upperImplied || hasPosCliques)) + HPRESOLVE_CHECKED_CALL(checkRow(bestRowPlus, j, HighsInt{1}, + ajBestRowPlus, upperImplied, + hasPosCliques)); } - if (numFixedCols > 0 || numModifiedBounds > 0) + if (numFixedCols > 0 || numModifiedBoundsPredBndAnalysis > 0) highsLogDev(options->log_options, HighsLogType::kInfo, "Fixed %d dominated columns and strengthened %d bounds\n", static_cast(numFixedCols), - static_cast(numModifiedBounds)); + static_cast(numModifiedBoundsPredBndAnalysis)); return Result::kOk; } From b2aa2a7e0ec368ee00f808e2557b1ba8cd10902d Mon Sep 17 00:00:00 2001 From: fwesselm Date: Tue, 4 Nov 2025 11:40:41 +0100 Subject: [PATCH 2/5] Rename --- highs/presolve/HPresolve.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index 28baf27057..7d1aada9ec 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1158,10 +1158,10 @@ HPresolve::Result HPresolve::dominatedColumns( // count number of fixed columns and modified bounds HighsInt numFixedCols = 0; HighsInt numFixedColsPredBndAnalysis = 0; - HighsInt numModifiedBoundsPredBndAnalysis = 0; + HighsInt numModifiedBndsPredBndAnalysis = 0; // perform predictive bound analysis? - bool allowPredBndAnalysis = false; + bool allowPredBndAnalysis = true; for (HighsInt j = 0; j < model->num_col_; ++j) { // skip deleted columns @@ -1262,7 +1262,7 @@ HPresolve::Result HPresolve::dominatedColumns( numFixedColsPredBndAnalysis++; HPRESOLVE_CHECKED_CALL(fixCol(col, HighsInt{1})); } else if (model->integrality_[col] != HighsVarType::kContinuous) { - numModifiedBoundsPredBndAnalysis++; + numModifiedBndsPredBndAnalysis++; changeColLower(col, lowerBound); } } @@ -1273,7 +1273,7 @@ HPresolve::Result HPresolve::dominatedColumns( numFixedColsPredBndAnalysis++; HPRESOLVE_CHECKED_CALL(fixCol(col, HighsInt{-1})); } else if (model->integrality_[col] != HighsVarType::kContinuous) { - numModifiedBoundsPredBndAnalysis++; + numModifiedBndsPredBndAnalysis++; changeColUpper(col, upperBound); } } @@ -1413,11 +1413,11 @@ HPresolve::Result HPresolve::dominatedColumns( hasPosCliques)); } - if (numFixedCols > 0 || numModifiedBoundsPredBndAnalysis > 0) + if (numFixedCols > 0 || numModifiedBndsPredBndAnalysis > 0) highsLogDev(options->log_options, HighsLogType::kInfo, "Fixed %d dominated columns and strengthened %d bounds\n", static_cast(numFixedCols), - static_cast(numModifiedBoundsPredBndAnalysis)); + static_cast(numModifiedBndsPredBndAnalysis)); return Result::kOk; } From 95028369190a839e6006cb24f398644d9ecc924b Mon Sep 17 00:00:00 2001 From: fwesselm Date: Tue, 4 Nov 2025 15:27:18 +0100 Subject: [PATCH 3/5] WIP --- highs/presolve/HPresolve.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index 7d1aada9ec..ed781aada2 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1156,6 +1156,7 @@ HPresolve::Result HPresolve::dominatedColumns( } // count number of fixed columns and modified bounds + HighsInt numCols = 0; HighsInt numFixedCols = 0; HighsInt numFixedColsPredBndAnalysis = 0; HighsInt numModifiedBndsPredBndAnalysis = 0; @@ -1167,6 +1168,9 @@ HPresolve::Result HPresolve::dominatedColumns( // skip deleted columns if (colDeleted[j]) continue; + // increment counter for number of columns + numCols++; + // initialise HighsInt bestRowPlus = -1; HighsInt bestRowPlusLen = kHighsIInf; @@ -1411,6 +1415,16 @@ HPresolve::Result HPresolve::dominatedColumns( HPRESOLVE_CHECKED_CALL(checkRow(bestRowPlus, j, HighsInt{1}, ajBestRowPlus, upperImplied, hasPosCliques)); + + // do not use predictive bound analysis if it requires many domination + // checks and does not yield fixings or improved bounds + allowPredBndAnalysis = + allowPredBndAnalysis && + (numDomChecksPredBndAnalysis == 0 || + (numDomChecksPredBndAnalysis / static_cast(numCols) <= 1e4 && + (numFixedColsPredBndAnalysis + numModifiedBndsPredBndAnalysis) / + static_cast(numDomChecksPredBndAnalysis) >= + 1e-5)); } if (numFixedCols > 0 || numModifiedBndsPredBndAnalysis > 0) From dc7c517e7083f4d5c16b9efb89f615ea0dc4be3f Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 5 Nov 2025 11:12:55 +0100 Subject: [PATCH 4/5] Slightly change stopping criteria --- highs/presolve/HPresolve.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index ed781aada2..53032adc73 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1161,6 +1161,10 @@ HPresolve::Result HPresolve::dominatedColumns( HighsInt numFixedColsPredBndAnalysis = 0; HighsInt numModifiedBndsPredBndAnalysis = 0; + // parameters for predictive bound analysis + const double maxAverageNumDomChecksPredBndAnalysis = 1e4; + const double minAverageNumRedsPredBndAnalysis = 1e-2; + // perform predictive bound analysis? bool allowPredBndAnalysis = true; @@ -1417,14 +1421,19 @@ HPresolve::Result HPresolve::dominatedColumns( hasPosCliques)); // do not use predictive bound analysis if it requires many domination - // checks and does not yield fixings or improved bounds + // checks and only yields few fixings or improved bounds on average + double averageNumDomChecksPredBndAnalysis = + numDomChecksPredBndAnalysis / static_cast(numCols); + double averageNumRedsPredBndAnalysis = + (numFixedColsPredBndAnalysis + numModifiedBndsPredBndAnalysis) / + static_cast(numCols); allowPredBndAnalysis = allowPredBndAnalysis && - (numDomChecksPredBndAnalysis == 0 || - (numDomChecksPredBndAnalysis / static_cast(numCols) <= 1e4 && - (numFixedColsPredBndAnalysis + numModifiedBndsPredBndAnalysis) / - static_cast(numDomChecksPredBndAnalysis) >= - 1e-5)); + (numDomChecksPredBndAnalysis <= + 30.0 * maxAverageNumDomChecksPredBndAnalysis || + (averageNumDomChecksPredBndAnalysis <= + maxAverageNumDomChecksPredBndAnalysis && + averageNumRedsPredBndAnalysis >= minAverageNumRedsPredBndAnalysis)); } if (numFixedCols > 0 || numModifiedBndsPredBndAnalysis > 0) From 384b9af21eb9e9f6d9289f35fa17a36e520852f8 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 5 Nov 2025 11:30:46 +0100 Subject: [PATCH 5/5] Change data type --- highs/presolve/HPresolve.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index 53032adc73..a0e183672a 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1162,7 +1162,7 @@ HPresolve::Result HPresolve::dominatedColumns( HighsInt numModifiedBndsPredBndAnalysis = 0; // parameters for predictive bound analysis - const double maxAverageNumDomChecksPredBndAnalysis = 1e4; + const size_t maxAverageNumDomChecksPredBndAnalysis = 10000; const double minAverageNumRedsPredBndAnalysis = 1e-2; // perform predictive bound analysis? @@ -1422,15 +1422,15 @@ HPresolve::Result HPresolve::dominatedColumns( // do not use predictive bound analysis if it requires many domination // checks and only yields few fixings or improved bounds on average - double averageNumDomChecksPredBndAnalysis = - numDomChecksPredBndAnalysis / static_cast(numCols); + size_t averageNumDomChecksPredBndAnalysis = + numDomChecksPredBndAnalysis / static_cast(numCols); double averageNumRedsPredBndAnalysis = (numFixedColsPredBndAnalysis + numModifiedBndsPredBndAnalysis) / static_cast(numCols); allowPredBndAnalysis = allowPredBndAnalysis && (numDomChecksPredBndAnalysis <= - 30.0 * maxAverageNumDomChecksPredBndAnalysis || + 30 * maxAverageNumDomChecksPredBndAnalysis || (averageNumDomChecksPredBndAnalysis <= maxAverageNumDomChecksPredBndAnalysis && averageNumRedsPredBndAnalysis >= minAverageNumRedsPredBndAnalysis));