diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index 8f3f7fc01d..a0e183672a 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1156,13 +1156,25 @@ HPresolve::Result HPresolve::dominatedColumns( } // count number of fixed columns and modified bounds + HighsInt numCols = 0; HighsInt numFixedCols = 0; - HighsInt numModifiedBounds = 0; + HighsInt numFixedColsPredBndAnalysis = 0; + HighsInt numModifiedBndsPredBndAnalysis = 0; + + // parameters for predictive bound analysis + const size_t maxAverageNumDomChecksPredBndAnalysis = 10000; + const double minAverageNumRedsPredBndAnalysis = 1e-2; + + // perform predictive bound analysis? + bool allowPredBndAnalysis = true; for (HighsInt j = 0; j < model->num_col_; ++j) { // skip deleted columns if (colDeleted[j]) continue; + // increment counter for number of columns + numCols++; + // initialise HighsInt bestRowPlus = -1; HighsInt bestRowPlusLen = kHighsIInf; @@ -1254,20 +1266,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) { + numModifiedBndsPredBndAnalysis++; 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) { + numModifiedBndsPredBndAnalysis++; changeColUpper(col, upperBound); } } @@ -1346,6 +1360,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 +1375,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 +1398,49 @@ 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 (numFixedCols > 0 || numModifiedBounds > 0) + if (!colDeleted[j] && bestRowPlus != -1 && + (allowPredBndAnalysis || upperImplied || hasPosCliques)) + HPRESOLVE_CHECKED_CALL(checkRow(bestRowPlus, j, HighsInt{1}, + ajBestRowPlus, upperImplied, + hasPosCliques)); + + // do not use predictive bound analysis if it requires many domination + // checks and only yields few fixings or improved bounds on average + size_t averageNumDomChecksPredBndAnalysis = + numDomChecksPredBndAnalysis / static_cast(numCols); + double averageNumRedsPredBndAnalysis = + (numFixedColsPredBndAnalysis + numModifiedBndsPredBndAnalysis) / + static_cast(numCols); + allowPredBndAnalysis = + allowPredBndAnalysis && + (numDomChecksPredBndAnalysis <= + 30 * maxAverageNumDomChecksPredBndAnalysis || + (averageNumDomChecksPredBndAnalysis <= + maxAverageNumDomChecksPredBndAnalysis && + averageNumRedsPredBndAnalysis >= minAverageNumRedsPredBndAnalysis)); + } + + 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(numModifiedBounds)); + static_cast(numModifiedBndsPredBndAnalysis)); return Result::kOk; }