From 5829a0f8d954e5c465d55b8b587f9971bec9eef3 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Mon, 18 Aug 2025 15:26:32 +0200 Subject: [PATCH 01/25] Remove some duplicate code --- highs/presolve/HPresolve.cpp | 45 ++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index b676e5ea180..51ee2a947e0 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1115,37 +1115,32 @@ HPresolve::Result HPresolve::dominatedColumns( HighsInt scale = model->row_upper_[row] != kHighsInf ? 1 : -1; if (colIsBinary) { - if (model->row_upper_[row] != kHighsInf) { - if (model->col_cost_[j] >= 0.0 && nonz.value() < 0.0) { - double maxresact = - impliedRowBounds.getResidualSumUpper(row, j, nonz.value()); - double wcBound = - (model->row_upper_[row] - maxresact) / nonz.value(); - worstCaseLb = std::max(wcBound, worstCaseLb); - } else if (model->col_cost_[j] <= 0.0 && nonz.value() > 0.0) { - double maxresact = - impliedRowBounds.getResidualSumUpper(row, j, nonz.value()); - double wcBound = - (model->row_upper_[row] - maxresact) / nonz.value(); - worstCaseUb = std::min(wcBound, worstCaseUb); - } - } + auto getResidual = [&](HighsInt row, HighsInt col, HighsInt val, + HighsInt direction) { + if (direction > 0) + return impliedRowBounds.getResidualSumUpper(row, col, val); + else + return impliedRowBounds.getResidualSumLower(row, col, val); + }; - if (model->row_lower_[row] != -kHighsInf) { - if (model->col_cost_[j] >= 0.0 && nonz.value() > 0.0) { - double minresact = - impliedRowBounds.getResidualSumLower(row, j, nonz.value()); + auto updateWorstCaseBounds = [&](HighsInt row, HighsInt col, double val, + HighsInt direction, double rhs) { + if (direction * rhs == kHighsInf) return; + if (model->col_cost_[col] >= 0.0 && direction * val < 0.0) { double wcBound = - (model->row_lower_[row] - minresact) / nonz.value(); + (rhs - getResidual(row, col, val, direction)) / val; worstCaseLb = std::max(wcBound, worstCaseLb); - } else if (model->col_cost_[j] <= 0.0 && nonz.value() < 0.0) { - double minresact = - impliedRowBounds.getResidualSumLower(row, j, nonz.value()); + } else if (model->col_cost_[col] <= 0.0 && direction * val > 0.0) { double wcBound = - (model->row_lower_[row] - minresact) / nonz.value(); + (rhs - getResidual(row, col, val, direction)) / val; worstCaseUb = std::min(wcBound, worstCaseUb); } - } + }; + + updateWorstCaseBounds(row, j, nonz.value(), HighsInt{1}, + model->row_upper_[row]); + updateWorstCaseBounds(row, j, nonz.value(), HighsInt{-1}, + model->row_lower_[row]); } double val = scale * nonz.value(); From 756a9ba79ff13cd2d4a8d67acea1d76af9e33616 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Mon, 18 Aug 2025 15:29:02 +0200 Subject: [PATCH 02/25] Fix issue --- highs/presolve/HPresolve.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index 51ee2a947e0..e5aa93f18c8 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1115,7 +1115,7 @@ HPresolve::Result HPresolve::dominatedColumns( HighsInt scale = model->row_upper_[row] != kHighsInf ? 1 : -1; if (colIsBinary) { - auto getResidual = [&](HighsInt row, HighsInt col, HighsInt val, + auto getResidual = [&](HighsInt row, HighsInt col, double val, HighsInt direction) { if (direction > 0) return impliedRowBounds.getResidualSumUpper(row, col, val); From fce0428a9c69638fea698806c8e5e1547d8f5241 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Mon, 18 Aug 2025 15:36:10 +0200 Subject: [PATCH 03/25] Clean up --- 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 e5aa93f18c8..719037407b0 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1127,13 +1127,13 @@ HPresolve::Result HPresolve::dominatedColumns( HighsInt direction, double rhs) { if (direction * rhs == kHighsInf) return; if (model->col_cost_[col] >= 0.0 && direction * val < 0.0) { - double wcBound = - (rhs - getResidual(row, col, val, direction)) / val; - worstCaseLb = std::max(wcBound, worstCaseLb); + worstCaseLb = + std::max((rhs - getResidual(row, col, val, direction)) / val, + worstCaseLb); } else if (model->col_cost_[col] <= 0.0 && direction * val > 0.0) { - double wcBound = - (rhs - getResidual(row, col, val, direction)) / val; - worstCaseUb = std::min(wcBound, worstCaseUb); + worstCaseUb = + std::min((rhs - getResidual(row, col, val, direction)) / val, + worstCaseUb); } }; From 22eebe05d7f0b70ae6aabe62d68a864fa19e1e8e Mon Sep 17 00:00:00 2001 From: fwesselm Date: Tue, 19 Aug 2025 10:36:48 +0200 Subject: [PATCH 04/25] Add lambda --- highs/presolve/HPresolve.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index e5aa93f18c8..f0d803463e7 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1123,17 +1123,20 @@ HPresolve::Result HPresolve::dominatedColumns( return impliedRowBounds.getResidualSumLower(row, col, val); }; + auto calcBound = [&](HighsInt row, HighsInt col, double val, + HighsInt direction, double rhs) { + return (rhs - getResidual(row, col, val, direction)) / val; + }; + auto updateWorstCaseBounds = [&](HighsInt row, HighsInt col, double val, HighsInt direction, double rhs) { if (direction * rhs == kHighsInf) return; if (model->col_cost_[col] >= 0.0 && direction * val < 0.0) { - double wcBound = - (rhs - getResidual(row, col, val, direction)) / val; - worstCaseLb = std::max(wcBound, worstCaseLb); + worstCaseLb = + std::max(calcBound(row, col, val, direction, rhs), worstCaseLb); } else if (model->col_cost_[col] <= 0.0 && direction * val > 0.0) { - double wcBound = - (rhs - getResidual(row, col, val, direction)) / val; - worstCaseUb = std::min(wcBound, worstCaseUb); + worstCaseUb = + std::min(calcBound(row, col, val, direction, rhs), worstCaseUb); } }; From 0e1af04507e44c76ee66c9d969b7a05a919cab99 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Tue, 19 Aug 2025 10:49:56 +0200 Subject: [PATCH 05/25] Remove lambda --- highs/presolve/HPresolve.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index f0d803463e7..719037407b0 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1123,20 +1123,17 @@ HPresolve::Result HPresolve::dominatedColumns( return impliedRowBounds.getResidualSumLower(row, col, val); }; - auto calcBound = [&](HighsInt row, HighsInt col, double val, - HighsInt direction, double rhs) { - return (rhs - getResidual(row, col, val, direction)) / val; - }; - auto updateWorstCaseBounds = [&](HighsInt row, HighsInt col, double val, HighsInt direction, double rhs) { if (direction * rhs == kHighsInf) return; if (model->col_cost_[col] >= 0.0 && direction * val < 0.0) { worstCaseLb = - std::max(calcBound(row, col, val, direction, rhs), worstCaseLb); + std::max((rhs - getResidual(row, col, val, direction)) / val, + worstCaseLb); } else if (model->col_cost_[col] <= 0.0 && direction * val > 0.0) { worstCaseUb = - std::min(calcBound(row, col, val, direction, rhs), worstCaseUb); + std::min((rhs - getResidual(row, col, val, direction)) / val, + worstCaseUb); } }; From daa876be652978ecfc072a5d3e85c397f0e38feb Mon Sep 17 00:00:00 2001 From: fwesselm Date: Tue, 19 Aug 2025 14:10:24 +0200 Subject: [PATCH 06/25] WIP --- highs/presolve/HPresolve.cpp | 66 ++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index 719037407b0..e48e656e4bd 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1160,6 +1160,72 @@ HPresolve::Result HPresolve::dominatedColumns( } if (colIsBinary) { + // lambda for fixing variables + auto fixCol = [&](HighsInt col, HighsInt direction) { + if (direction > 0) { + if (fixColToUpperOrUnbounded(postsolve_stack, col)) { + // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; + } + } else { + if (fixColToLowerOrUnbounded(postsolve_stack, col)) { + // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; + } + } + return Result::kOk; + }; + + auto checkFixCol = [&](HighsInt row, HighsInt col, HighsInt direction, + double scale, double bestVal) { + storeRow(row); + bool isEqOrRangedRow = model->row_lower_[row] != -kHighsInf && + model->row_upper_[row] != kHighsInf; + + for (const HighsSliceNonzero& nonz : getStoredRow()) { + HighsInt k = nonz.index(); + if (k == col || colDeleted[k]) continue; + + double ak = nonz.value() * scale; + + if (direction * bestVal <= + direction * ak + options->small_matrix_value && + (!isEqOrRangedRow || + direction * bestVal >= + direction * ak - options->small_matrix_value) && + checkDomination(direction, j, direction, k)) { + // direction = 1: + // case (i) ub(x_j) = inf, x_j > x_k: set x_k = lb(x_k) + // direction = -1: + // case (iii) lb(x_j) = -inf, -x_j > -x_k: set x_k = ub(x_k) + ++numFixedCols; + HPRESOLVE_CHECKED_CALL(fixCol(col, -direction)); + break; + } else if (direction * bestVal <= + -direction * ak + options->small_matrix_value && + (!isEqOrRangedRow || + direction * bestVal >= + -direction * ak - options->small_matrix_value) && + checkDomination(direction, j, -direction, k)) { + // direction = 1: + // case (ii) ub(x_j) = inf, x_j > -x_k: set x_k = ub(x_k) + // direction = -1: + // case (iv) lb(x_j) = -inf, -x_j > x_k: set x_k = lb(x_k) + ++numFixedCols; + HPRESOLVE_CHECKED_CALL(fixCol(col, direction)); + break; + } + } + + if (colDeleted[col]) { + HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); + HPRESOLVE_CHECKED_CALL(removeDoubletonEquations(postsolve_stack)); + } + return Result::kOk; + }; + if (model->col_cost_[j] >= 0.0 && worstCaseLb <= 1 + primal_feastol) { upperImplied = true; if (!lowerImplied && bestRowMinus != -1) { From 72afeb4705f28720fd25f069923c5800293459a4 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Tue, 19 Aug 2025 14:24:40 +0200 Subject: [PATCH 07/25] Make use of new lambda --- highs/presolve/HPresolve.cpp | 98 ++++-------------------------------- 1 file changed, 9 insertions(+), 89 deletions(-) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index e48e656e4bd..a64930370ea 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1195,7 +1195,7 @@ HPresolve::Result HPresolve::dominatedColumns( (!isEqOrRangedRow || direction * bestVal >= direction * ak - options->small_matrix_value) && - checkDomination(direction, j, direction, k)) { + checkDomination(direction, col, direction, k)) { // direction = 1: // case (i) ub(x_j) = inf, x_j > x_k: set x_k = lb(x_k) // direction = -1: @@ -1208,7 +1208,7 @@ HPresolve::Result HPresolve::dominatedColumns( (!isEqOrRangedRow || direction * bestVal >= -direction * ak - options->small_matrix_value) && - checkDomination(direction, j, -direction, k)) { + checkDomination(direction, col, -direction, k)) { // direction = 1: // case (ii) ub(x_j) = inf, x_j > -x_k: set x_k = ub(x_k) // direction = -1: @@ -1229,99 +1229,19 @@ HPresolve::Result HPresolve::dominatedColumns( if (model->col_cost_[j] >= 0.0 && worstCaseLb <= 1 + primal_feastol) { upperImplied = true; if (!lowerImplied && bestRowMinus != -1) { - storeRow(bestRowMinus); - - bool isEqOrRangedRow = - model->row_lower_[bestRowMinus] != -kHighsInf && - model->row_upper_[bestRowMinus] != kHighsInf; - - for (const HighsSliceNonzero& nonz : getStoredRow()) { - HighsInt k = nonz.index(); - if (k == j || colDeleted[k]) continue; - - double ak = nonz.value() * bestRowMinusScale; - - if (-ajBestRowMinus <= -ak + options->small_matrix_value && - (!isEqOrRangedRow || - -ajBestRowMinus >= -ak - options->small_matrix_value) && - checkDomination(-1, j, -1, k)) { - // case (iii) lb(x_j) = -inf, -x_j > -x_k: set x_k = ub(x_k) - ++numFixedCols; - if (fixColToLowerOrUnbounded(postsolve_stack, j)) { - // Handle unboundedness - presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; - return Result::kDualInfeasible; - } - HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); - break; - } else if (-ajBestRowMinus <= ak + options->small_matrix_value && - (!isEqOrRangedRow || - -ajBestRowMinus >= ak - options->small_matrix_value) && - checkDomination(-1, j, 1, k)) { - // case (iv) lb(x_j) = -inf, -x_j > x_k: set x_k = lb(x_k) - ++numFixedCols; - if (fixColToLowerOrUnbounded(postsolve_stack, j)) { - // Handle unboundedness - presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; - return Result::kDualInfeasible; - } - HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); - break; - } - } - - if (colDeleted[j]) { - HPRESOLVE_CHECKED_CALL(removeDoubletonEquations(postsolve_stack)); - continue; - } + HPRESOLVE_CHECKED_CALL(checkFixCol(bestRowMinus, j, HighsInt{-1}, + bestRowMinusScale, + ajBestRowMinus)); + if (colDeleted[j]) continue; } } if (model->col_cost_[j] <= 0.0 && worstCaseUb >= -primal_feastol) { lowerImplied = true; if (!upperImplied && bestRowPlus != -1) { - storeRow(bestRowPlus); - bool isEqOrRangedRow = model->row_lower_[bestRowPlus] != -kHighsInf && - model->row_upper_[bestRowPlus] != kHighsInf; - for (const HighsSliceNonzero& nonz : getStoredRow()) { - HighsInt k = nonz.index(); - if (k == j || colDeleted[k]) continue; - - double ak = nonz.value() * bestRowPlusScale; - - if (ajBestRowPlus <= ak + options->small_matrix_value && - (!isEqOrRangedRow || - ajBestRowPlus >= ak - options->small_matrix_value) && - checkDomination(1, j, 1, k)) { - // case (i) ub(x_j) = inf, x_j > x_k: set x_k = lb(x_k) - ++numFixedCols; - if (fixColToUpperOrUnbounded(postsolve_stack, j)) { - // Handle unboundedness - presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; - return Result::kDualInfeasible; - } - HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); - break; - } else if (ajBestRowPlus <= -ak + options->small_matrix_value && - (!isEqOrRangedRow || - ajBestRowPlus >= -ak - options->small_matrix_value) && - checkDomination(1, j, -1, k)) { - // case (ii) ub(x_j) = inf, x_j > -x_k: set x_k = ub(x_k) - ++numFixedCols; - if (fixColToUpperOrUnbounded(postsolve_stack, j)) { - // Handle unboundedness - presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; - return Result::kDualInfeasible; - } - HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); - break; - } - } - - if (colDeleted[j]) { - HPRESOLVE_CHECKED_CALL(removeDoubletonEquations(postsolve_stack)); - continue; - } + HPRESOLVE_CHECKED_CALL(checkFixCol(bestRowPlus, j, HighsInt{1}, + bestRowPlusScale, ajBestRowPlus)); + if (colDeleted[j]) continue; } } From 363237c2ad8fe2b16050121afb7cf4ea46ac854a Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 20 Aug 2025 10:34:31 +0200 Subject: [PATCH 08/25] WIP --- highs/presolve/HPresolve.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index a64930370ea..bc108e7ebbf 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1085,14 +1085,13 @@ HPresolve::Result HPresolve::dominatedColumns( if (colDeleted[j]) continue; bool upperImplied = isUpperImplied(j); bool lowerImplied = isLowerImplied(j); - bool hasPosCliques = false; - bool hasNegCliques = false; bool colIsBinary = isBinary(j); - if (colIsBinary) { - hasPosCliques = mipsolver->mipdata_->cliquetable.numCliques(j, 1) > 0; - hasNegCliques = mipsolver->mipdata_->cliquetable.numCliques(j, 0) > 0; - } else if (!upperImplied && !lowerImplied) - continue; + bool hasPosCliques = + colIsBinary && mipsolver->mipdata_->cliquetable.numCliques(j, 1) > 0; + bool hasNegCliques = + colIsBinary && mipsolver->mipdata_->cliquetable.numCliques(j, 0) > 0; + + if (!colIsBinary && !upperImplied && !lowerImplied) continue; HighsInt oldNumFixed = numFixedCols; From 5bac50195eb02d71ecd0a1f57ce8fbb9344bc367 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 20 Aug 2025 14:09:54 +0200 Subject: [PATCH 09/25] WIP --- highs/mip/HighsPseudocost.h | 54 ++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/highs/mip/HighsPseudocost.h b/highs/mip/HighsPseudocost.h index dc54596984a..342a4f66e14 100644 --- a/highs/mip/HighsPseudocost.h +++ b/highs/mip/HighsPseudocost.h @@ -24,6 +24,8 @@ class HighsPostsolveStack; class HighsPseudocost; +constexpr double minThreshold = 1e-6; + struct HighsPseudocostInitialization { std::vector pseudocostup; std::vector pseudocostdown; @@ -47,6 +49,7 @@ struct HighsPseudocostInitialization { const HighsPseudocost& pscost, HighsInt maxCount, const presolve::HighsPostsolveStack& postsolveStack); }; + class HighsPseudocost { friend struct HighsPseudocostInitialization; std::vector pseudocostup; @@ -77,9 +80,7 @@ class HighsPseudocost { HighsPseudocost(const HighsMipSolver& mipsolver); void subtractBase(const HighsPseudocost& base) { - HighsInt ncols = pseudocostup.size(); - - for (HighsInt i = 0; i != ncols; ++i) { + for (size_t i = 0; i != pseudocostup.size(); ++i) { pseudocostup[i] -= base.pseudocostup[i]; pseudocostdown[i] -= base.pseudocostdown[i]; nsamplesup[i] -= base.nsamplesup[i]; @@ -95,8 +96,7 @@ class HighsPseudocost { conflict_weight = 1.0; conflict_avg_score *= scale; - HighsInt numCol = conflictscoreup.size(); - for (HighsInt i = 0; i < numCol; ++i) { + for (size_t i = 0; i != conflictscoreup.size(); ++i) { conflictscoreup[i] *= scale; conflictscoredown[i] *= scale; } @@ -248,11 +248,13 @@ class HighsPseudocost { } double getScore(HighsInt col, double upcost, double downcost) const { - double costScore = std::max(upcost, 1e-6) * std::max(downcost, 1e-6) / - std::max(1e-6, cost_total * cost_total); - double inferenceScore = std::max(inferencesup[col], 1e-6) * - std::max(inferencesdown[col], 1e-6) / - std::max(1e-6, inferences_total * inferences_total); + double costScore = std::max(upcost, minThreshold) * + std::max(downcost, minThreshold) / + std::max(minThreshold, cost_total * cost_total); + double inferenceScore = + std::max(inferencesup[col], minThreshold) * + std::max(inferencesdown[col], minThreshold) / + std::max(minThreshold, inferences_total * inferences_total); double cutOffScoreUp = ncutoffsup[col] / @@ -266,18 +268,19 @@ class HighsPseudocost { std::max(1.0, static_cast(ncutoffstotal) + static_cast(nsamplestotal)); - double cutoffScore = std::max(cutOffScoreUp, 1e-6) * - std::max(cutOffScoreDown, 1e-6) / - std::max(1e-6, avgCutoffs * avgCutoffs); + double cutoffScore = std::max(cutOffScoreUp, minThreshold) * + std::max(cutOffScoreDown, minThreshold) / + std::max(minThreshold, avgCutoffs * avgCutoffs); double conflictScoreUp = conflictscoreup[col] / conflict_weight; double conflictScoreDown = conflictscoredown[col] / conflict_weight; double conflictScoreAvg = conflict_avg_score / (conflict_weight * static_cast(conflictscoreup.size())); - double conflictScore = std::max(conflictScoreUp, 1e-6) * - std::max(conflictScoreDown, 1e-6) / - std::max(1e-6, conflictScoreAvg * conflictScoreAvg); + double conflictScore = + std::max(conflictScoreUp, minThreshold) * + std::max(conflictScoreDown, minThreshold) / + std::max(minThreshold, conflictScoreAvg * conflictScoreAvg); auto mapScore = [](double score) { return 1.0 - 1.0 / (1.0 + score); }; return mapScore(costScore) / degeneracyFactor + @@ -294,9 +297,10 @@ class HighsPseudocost { } double getScoreUp(HighsInt col, double frac) const { - double costScore = getPseudocostUp(col, frac) / std::max(1e-6, cost_total); + double costScore = + getPseudocostUp(col, frac) / std::max(minThreshold, cost_total); double inferenceScore = - inferencesup[col] / std::max(1e-6, inferences_total); + inferencesup[col] / std::max(minThreshold, inferences_total); double cutOffScoreUp = ncutoffsup[col] / @@ -306,13 +310,14 @@ class HighsPseudocost { std::max(1.0, static_cast(ncutoffstotal) + static_cast(nsamplestotal)); - double cutoffScore = cutOffScoreUp / std::max(1e-6, avgCutoffs); + double cutoffScore = cutOffScoreUp / std::max(minThreshold, avgCutoffs); double conflictScoreUp = conflictscoreup[col] / conflict_weight; double conflictScoreAvg = conflict_avg_score / (conflict_weight * static_cast(conflictscoreup.size())); - double conflictScore = conflictScoreUp / std::max(1e-6, conflictScoreAvg); + double conflictScore = + conflictScoreUp / std::max(minThreshold, conflictScoreAvg); auto mapScore = [](double score) { return 1.0 - 1.0 / (1.0 + score); }; @@ -323,9 +328,9 @@ class HighsPseudocost { double getScoreDown(HighsInt col, double frac) const { double costScore = - getPseudocostDown(col, frac) / std::max(1e-6, cost_total); + getPseudocostDown(col, frac) / std::max(minThreshold, cost_total); double inferenceScore = - inferencesdown[col] / std::max(1e-6, inferences_total); + inferencesdown[col] / std::max(minThreshold, inferences_total); double cutOffScoreDown = ncutoffsdown[col] / @@ -335,13 +340,14 @@ class HighsPseudocost { std::max(1.0, static_cast(ncutoffstotal) + static_cast(nsamplestotal)); - double cutoffScore = cutOffScoreDown / std::max(1e-6, avgCutoffs); + double cutoffScore = cutOffScoreDown / std::max(minThreshold, avgCutoffs); double conflictScoreDown = conflictscoredown[col] / conflict_weight; double conflictScoreAvg = conflict_avg_score / (conflict_weight * static_cast(conflictscoredown.size())); - double conflictScore = conflictScoreDown / std::max(1e-6, conflictScoreAvg); + double conflictScore = + conflictScoreDown / std::max(minThreshold, conflictScoreAvg); auto mapScore = [](double score) { return 1.0 - 1.0 / (1.0 + score); }; From e171ea23c66fa2a52b068735d46cbb15a9b8587b Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 20 Aug 2025 15:32:50 +0200 Subject: [PATCH 10/25] Still WIP --- highs/presolve/HPresolve.cpp | 189 +++++++++++++++-------------------- 1 file changed, 80 insertions(+), 109 deletions(-) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index 4d12a9ed5f1..d1cfcad90ba 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1221,25 +1221,25 @@ HPresolve::Result HPresolve::dominatedColumns( } } - if (colIsBinary) { - // lambda for fixing variables - auto fixCol = [&](HighsInt col, HighsInt direction) { - if (direction > 0) { - if (fixColToUpperOrUnbounded(postsolve_stack, col)) { - // Handle unboundedness - presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; - return Result::kDualInfeasible; - } - } else { - if (fixColToLowerOrUnbounded(postsolve_stack, col)) { - // Handle unboundedness - presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; - return Result::kDualInfeasible; - } + // lambda for fixing variables + auto fixCol = [&](HighsInt col, HighsInt direction) { + if (direction > 0) { + if (fixColToUpperOrUnbounded(postsolve_stack, col)) { + // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; } - return Result::kOk; - }; + } else { + if (fixColToLowerOrUnbounded(postsolve_stack, col)) { + // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; + } + } + return Result::kOk; + }; + if (colIsBinary) { auto checkFixCol = [&](HighsInt row, HighsInt col, HighsInt direction, double scale, double bestVal) { storeRow(row); @@ -1263,7 +1263,7 @@ HPresolve::Result HPresolve::dominatedColumns( // direction = -1: // case (iii) lb(x_j) = -inf, -x_j > -x_k: set x_k = ub(x_k) ++numFixedCols; - HPRESOLVE_CHECKED_CALL(fixCol(col, -direction)); + HPRESOLVE_CHECKED_CALL(fixCol(col, direction)); break; } else if (direction * bestVal <= -direction * ak + options->small_matrix_value && @@ -1306,109 +1306,80 @@ HPresolve::Result HPresolve::dominatedColumns( if (colDeleted[j]) continue; } } + } + + auto isBoundFinite = [&](HighsInt col, HighsInt scaleOtherCol) { + if (scaleOtherCol < 0) + return model->col_upper_[col] != kHighsInf; + else + return model->col_lower_[col] != -kHighsInf; + }; - if (!upperImplied && !hasPosCliques) bestRowPlus = -1; + auto colCanBeFixed = [&](HighsInt col, HighsInt k, double bestVal, + double val, HighsInt direction, + HighsInt multiplier, bool boundImplied, + bool isEqOrRangedRow) { + return isBoundFinite(k, direction * multiplier) && + direction * bestVal <= + direction * multiplier * val + options->small_matrix_value && + (!isEqOrRangedRow || + direction * bestVal >= + direction * multiplier * val - options->small_matrix_value) && + (boundImplied || + mipsolver->mipdata_->cliquetable.haveCommonClique( + HighsCliqueTable::CliqueVar(col, direction > 0 ? 1 : 0), + HighsCliqueTable::CliqueVar( + k, multiplier * direction > 0 ? 1 : 0))) && + checkDomination(direction, col, multiplier * direction, k); + }; - if (!lowerImplied && !hasNegCliques) bestRowMinus = -1; - } + auto checkFixCol = [&](HighsInt row, HighsInt col, HighsInt direction, + double scale, double bestVal, bool boundImplied) { + storeRow(row); + bool isEqOrRangedRow = model->row_lower_[row] != -kHighsInf && + model->row_upper_[row] != kHighsInf; - if (bestRowPlus != -1) { - assert(upperImplied || hasPosCliques); - storeRow(bestRowPlus); - bool isEqOrRangedRow = model->row_lower_[bestRowPlus] != -kHighsInf && - model->row_upper_[bestRowPlus] != kHighsInf; for (const HighsSliceNonzero& nonz : getStoredRow()) { HighsInt k = nonz.index(); - if (k == j || colDeleted[k]) continue; - - double ak = nonz.value() * bestRowPlusScale; - - if (model->col_lower_[k] != -kHighsInf && - ajBestRowPlus <= ak + options->small_matrix_value && - (!isEqOrRangedRow || - ajBestRowPlus >= ak - options->small_matrix_value) && - (upperImplied || mipsolver->mipdata_->cliquetable.haveCommonClique( - HighsCliqueTable::CliqueVar(j, 1), - HighsCliqueTable::CliqueVar(k, 1))) && - checkDomination(1, j, 1, k)) { - // case (i) ub(x_j) = inf, x_j > x_k: set x_k = lb(x_k) + if (k == col || colDeleted[k]) continue; + + double ak = nonz.value() * scale; + + if (colCanBeFixed(col, k, bestVal, ak, direction, HighsInt{1}, + boundImplied, isEqOrRangedRow)) { + // direction = 1: + // case (i) ub(x_j) = inf, x_j > x_k: set x_k = lb(x_k) + // direction = -1: + // case (iii) lb(x_j) = -inf, -x_j > -x_k: set x_k = ub(x_k) ++numFixedCols; - if (fixColToLowerOrUnbounded(postsolve_stack, k)) { - // Handle unboundedness - presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; - return Result::kDualInfeasible; - } - HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); - } else if (model->col_upper_[k] != kHighsInf && - ajBestRowPlus <= -ak + options->small_matrix_value && - (!isEqOrRangedRow || - ajBestRowPlus >= -ak - options->small_matrix_value) && - (upperImplied || - mipsolver->mipdata_->cliquetable.haveCommonClique( - HighsCliqueTable::CliqueVar(j, 1), - HighsCliqueTable::CliqueVar(k, 0))) && - checkDomination(1, j, -1, k)) { - // case (ii) ub(x_j) = inf, x_j > -x_k: set x_k = ub(x_k) + HPRESOLVE_CHECKED_CALL(fixCol(col, -direction)); + break; + } else if (colCanBeFixed(col, k, bestVal, ak, direction, HighsInt{-1}, + boundImplied, isEqOrRangedRow)) { + // direction = 1: + // case (ii) ub(x_j) = inf, x_j > -x_k: set x_k = ub(x_k) + // direction = -1: + // case (iv) lb(x_j) = -inf, -x_j > x_k: set x_k = lb(x_k) ++numFixedCols; - if (fixColToUpperOrUnbounded(postsolve_stack, k)) { - // Handle unboundedness - presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; - return Result::kDualInfeasible; - } - HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); + HPRESOLVE_CHECKED_CALL(fixCol(col, direction)); + break; } } - } - if (bestRowMinus != -1) { - assert(lowerImplied || hasNegCliques); - storeRow(bestRowMinus); + if (colDeleted[col]) + HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); + return Result::kOk; + }; - bool isEqOrRangedRow = model->row_lower_[bestRowMinus] != -kHighsInf && - model->row_upper_[bestRowMinus] != kHighsInf; + if (bestRowMinus != -1 && (lowerImplied || hasNegCliques)) + HPRESOLVE_CHECKED_CALL(checkFixCol(bestRowMinus, j, HighsInt{-1}, + bestRowMinusScale, ajBestRowMinus, + lowerImplied)); - for (const HighsSliceNonzero& nonz : getStoredRow()) { - HighsInt k = nonz.index(); - if (k == j || colDeleted[k]) continue; - - double ak = nonz.value() * bestRowMinusScale; - - if (model->col_upper_[k] != kHighsInf && - -ajBestRowMinus <= -ak + options->small_matrix_value && - (!isEqOrRangedRow || - -ajBestRowMinus >= -ak - options->small_matrix_value) && - (lowerImplied || mipsolver->mipdata_->cliquetable.haveCommonClique( - HighsCliqueTable::CliqueVar(j, 0), - HighsCliqueTable::CliqueVar(k, 0))) && - checkDomination(-1, j, -1, k)) { - // case (iii) lb(x_j) = -inf, -x_j > -x_k: set x_k = ub(x_k) - ++numFixedCols; - if (fixColToUpperOrUnbounded(postsolve_stack, k)) { - // Handle unboundedness - presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; - return Result::kDualInfeasible; - } - HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); - } else if (model->col_lower_[k] != -kHighsInf && - -ajBestRowMinus <= ak + options->small_matrix_value && - (!isEqOrRangedRow || - -ajBestRowMinus >= ak - options->small_matrix_value) && - (lowerImplied || - mipsolver->mipdata_->cliquetable.haveCommonClique( - HighsCliqueTable::CliqueVar(j, 0), - HighsCliqueTable::CliqueVar(k, 1))) && - checkDomination(-1, j, 1, k)) { - // case (iv) lb(x_j) = -inf, -x_j > x_k: set x_k = lb(x_k) - ++numFixedCols; - if (fixColToLowerOrUnbounded(postsolve_stack, k)) { - // Handle unboundedness - presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; - return Result::kDualInfeasible; - } - HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); - } - } - } + if (bestRowPlus != -1 && (upperImplied || hasPosCliques)) + HPRESOLVE_CHECKED_CALL(checkFixCol(bestRowPlus, j, HighsInt{1}, + bestRowPlusScale, ajBestRowPlus, + upperImplied)); if (numFixedCols != oldNumFixed) HPRESOLVE_CHECKED_CALL(removeDoubletonEquations(postsolve_stack)); From 002a73ca570a4eb1a309a8f1bbd2fba52b294d65 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 20 Aug 2025 15:54:32 +0200 Subject: [PATCH 11/25] Simplify a little --- highs/presolve/HPresolve.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index d1cfcad90ba..b2185780493 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1308,8 +1308,8 @@ HPresolve::Result HPresolve::dominatedColumns( } } - auto isBoundFinite = [&](HighsInt col, HighsInt scaleOtherCol) { - if (scaleOtherCol < 0) + auto isBoundFinite = [&](HighsInt col, HighsInt direction) { + if (direction < 0) return model->col_upper_[col] != kHighsInf; else return model->col_lower_[col] != -kHighsInf; @@ -1319,18 +1319,18 @@ HPresolve::Result HPresolve::dominatedColumns( double val, HighsInt direction, HighsInt multiplier, bool boundImplied, bool isEqOrRangedRow) { - return isBoundFinite(k, direction * multiplier) && + HighsInt mydirection = multiplier * direction; + return isBoundFinite(k, mydirection) && direction * bestVal <= - direction * multiplier * val + options->small_matrix_value && + mydirection * val + options->small_matrix_value && (!isEqOrRangedRow || direction * bestVal >= - direction * multiplier * val - options->small_matrix_value) && + mydirection * val - options->small_matrix_value) && (boundImplied || mipsolver->mipdata_->cliquetable.haveCommonClique( HighsCliqueTable::CliqueVar(col, direction > 0 ? 1 : 0), - HighsCliqueTable::CliqueVar( - k, multiplier * direction > 0 ? 1 : 0))) && - checkDomination(direction, col, multiplier * direction, k); + HighsCliqueTable::CliqueVar(k, mydirection > 0 ? 1 : 0))) && + checkDomination(direction, col, mydirection, k); }; auto checkFixCol = [&](HighsInt row, HighsInt col, HighsInt direction, From 13410b122e8e970c45697fd9cf508e38a5e2e500 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 20 Aug 2025 16:02:40 +0200 Subject: [PATCH 12/25] Fix issue on lseu --- highs/presolve/HPresolve.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index b2185780493..eb20e9b05ed 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1352,8 +1352,7 @@ HPresolve::Result HPresolve::dominatedColumns( // direction = -1: // case (iii) lb(x_j) = -inf, -x_j > -x_k: set x_k = ub(x_k) ++numFixedCols; - HPRESOLVE_CHECKED_CALL(fixCol(col, -direction)); - break; + HPRESOLVE_CHECKED_CALL(fixCol(k, -direction)); } else if (colCanBeFixed(col, k, bestVal, ak, direction, HighsInt{-1}, boundImplied, isEqOrRangedRow)) { // direction = 1: @@ -1361,13 +1360,11 @@ HPresolve::Result HPresolve::dominatedColumns( // direction = -1: // case (iv) lb(x_j) = -inf, -x_j > x_k: set x_k = lb(x_k) ++numFixedCols; - HPRESOLVE_CHECKED_CALL(fixCol(col, direction)); - break; + HPRESOLVE_CHECKED_CALL(fixCol(k, direction)); } + if (colDeleted[k]) + HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); } - - if (colDeleted[col]) - HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); return Result::kOk; }; From 06ab636c8702f9aa16c3763f9fd3b87e1e491975 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 20 Aug 2025 17:01:56 +0200 Subject: [PATCH 13/25] Remove unnecessary else --- highs/presolve/HPresolve.cpp | 38 ++++++++++++++---------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index eb20e9b05ed..0f7809fdddc 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1172,6 +1172,7 @@ HPresolve::Result HPresolve::dominatedColumns( bool checkPosRow = upperImplied || colIsBinary; bool checkNegRow = lowerImplied || colIsBinary; + for (const HighsSliceNonzero& nonz : getColumnVector(j)) { HighsInt row = nonz.index(); HighsInt scale = model->row_upper_[row] != kHighsInf ? 1 : -1; @@ -1252,29 +1253,20 @@ HPresolve::Result HPresolve::dominatedColumns( double ak = nonz.value() * scale; - if (direction * bestVal <= - direction * ak + options->small_matrix_value && - (!isEqOrRangedRow || - direction * bestVal >= - direction * ak - options->small_matrix_value) && - checkDomination(direction, col, direction, k)) { - // direction = 1: - // case (i) ub(x_j) = inf, x_j > x_k: set x_k = lb(x_k) - // direction = -1: - // case (iii) lb(x_j) = -inf, -x_j > -x_k: set x_k = ub(x_k) - ++numFixedCols; - HPRESOLVE_CHECKED_CALL(fixCol(col, direction)); - break; - } else if (direction * bestVal <= - -direction * ak + options->small_matrix_value && - (!isEqOrRangedRow || - direction * bestVal >= - -direction * ak - options->small_matrix_value) && - checkDomination(direction, col, -direction, k)) { - // direction = 1: - // case (ii) ub(x_j) = inf, x_j > -x_k: set x_k = ub(x_k) - // direction = -1: - // case (iv) lb(x_j) = -inf, -x_j > x_k: set x_k = lb(x_k) + if ((direction * bestVal <= + direction * ak + options->small_matrix_value && + (!isEqOrRangedRow || + direction * bestVal >= + direction * ak - options->small_matrix_value) && + checkDomination(direction, col, direction, k)) || + (direction * bestVal <= + -direction * ak + options->small_matrix_value && + (!isEqOrRangedRow || + direction * bestVal >= + -direction * ak - options->small_matrix_value) && + checkDomination(direction, col, -direction, k))) { + // direction = 1: fix binary variable to one + // direction = -1: fix binary variable to zero ++numFixedCols; HPRESOLVE_CHECKED_CALL(fixCol(col, direction)); break; From 774ef56101b0aef9430c20832cc56bb28beb3b43 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 20 Aug 2025 20:13:08 +0200 Subject: [PATCH 14/25] Add lambda --- highs/presolve/HPresolve.cpp | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index 0f7809fdddc..9dc96ea8cb8 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1241,6 +1241,18 @@ HPresolve::Result HPresolve::dominatedColumns( }; if (colIsBinary) { + auto binaryCanBeFixed = [&](HighsInt col, HighsInt k, double bestVal, + double val, HighsInt direction, + HighsInt multiplier, bool isEqOrRangedRow) { + HighsInt mydirection = multiplier * direction; + return direction * bestVal <= + mydirection * val + options->small_matrix_value && + (!isEqOrRangedRow || + direction * bestVal >= + mydirection * val - options->small_matrix_value) && + checkDomination(direction, col, mydirection, k); + }; + auto checkFixCol = [&](HighsInt row, HighsInt col, HighsInt direction, double scale, double bestVal) { storeRow(row); @@ -1253,18 +1265,10 @@ HPresolve::Result HPresolve::dominatedColumns( double ak = nonz.value() * scale; - if ((direction * bestVal <= - direction * ak + options->small_matrix_value && - (!isEqOrRangedRow || - direction * bestVal >= - direction * ak - options->small_matrix_value) && - checkDomination(direction, col, direction, k)) || - (direction * bestVal <= - -direction * ak + options->small_matrix_value && - (!isEqOrRangedRow || - direction * bestVal >= - -direction * ak - options->small_matrix_value) && - checkDomination(direction, col, -direction, k))) { + if (binaryCanBeFixed(col, k, bestVal, ak, direction, HighsInt{1}, + isEqOrRangedRow) || + binaryCanBeFixed(col, k, bestVal, ak, direction, HighsInt{-1}, + isEqOrRangedRow)) { // direction = 1: fix binary variable to one // direction = -1: fix binary variable to zero ++numFixedCols; From 53bce529a905f0a5f59effff01f4e324d22c416e Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 20 Aug 2025 20:35:33 +0200 Subject: [PATCH 15/25] Rename --- highs/presolve/HPresolve.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index 9dc96ea8cb8..7d09bbce854 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1253,8 +1253,8 @@ HPresolve::Result HPresolve::dominatedColumns( checkDomination(direction, col, mydirection, k); }; - auto checkFixCol = [&](HighsInt row, HighsInt col, HighsInt direction, - double scale, double bestVal) { + auto checkFixBinary = [&](HighsInt row, HighsInt col, HighsInt direction, + double scale, double bestVal) { storeRow(row); bool isEqOrRangedRow = model->row_lower_[row] != -kHighsInf && model->row_upper_[row] != kHighsInf; @@ -1287,9 +1287,9 @@ HPresolve::Result HPresolve::dominatedColumns( if (model->col_cost_[j] >= 0.0 && worstCaseLb <= 1 + primal_feastol) { upperImplied = true; if (!lowerImplied && bestRowMinus != -1) { - HPRESOLVE_CHECKED_CALL(checkFixCol(bestRowMinus, j, HighsInt{-1}, - bestRowMinusScale, - ajBestRowMinus)); + HPRESOLVE_CHECKED_CALL(checkFixBinary(bestRowMinus, j, HighsInt{-1}, + bestRowMinusScale, + ajBestRowMinus)); if (colDeleted[j]) continue; } } @@ -1297,8 +1297,8 @@ HPresolve::Result HPresolve::dominatedColumns( if (model->col_cost_[j] <= 0.0 && worstCaseUb >= -primal_feastol) { lowerImplied = true; if (!upperImplied && bestRowPlus != -1) { - HPRESOLVE_CHECKED_CALL(checkFixCol(bestRowPlus, j, HighsInt{1}, - bestRowPlusScale, ajBestRowPlus)); + HPRESOLVE_CHECKED_CALL(checkFixBinary( + bestRowPlus, j, HighsInt{1}, bestRowPlusScale, ajBestRowPlus)); if (colDeleted[j]) continue; } } From b027dbe398937ba81627d8e870eaaabd72f580d9 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Thu, 21 Aug 2025 09:21:01 +0200 Subject: [PATCH 16/25] Fix warning --- highs/presolve/HPresolve.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index 7d09bbce854..8642106f924 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1128,8 +1128,7 @@ HPresolve::Result HPresolve::dominatedColumns( return true; }; - HighsInt numNz = Avalue.size(); - for (HighsInt i = 0; i < numNz; ++i) { + for (size_t i = 0; i != Avalue.size(); ++i) { if (Avalue[i] == 0) continue; HighsInt row = Arow[i]; From 90b85a9d0d3ba673a58c0684084db8f69cd73816 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Thu, 21 Aug 2025 09:42:41 +0200 Subject: [PATCH 17/25] Fix some more warnings --- highs/presolve/HPresolve.cpp | 55 ++++++++++++++++--------------- highs/util/HighsSparseVectorSum.h | 2 +- 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index 8642106f924..298bec48172 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -936,8 +936,7 @@ void HPresolve::shrinkProblem(HighsPostsolveStack& postsolve_stack) { impliedRowBounds.shrink(newRowIndex, model->num_row_); impliedDualRowBounds.shrink(newColIndex, model->num_col_); - HighsInt numNnz = Avalue.size(); - for (HighsInt i = 0; i != numNnz; ++i) { + for (size_t i = 0; i != Avalue.size(); ++i) { if (Avalue[i] == 0) continue; assert(newColIndex[Acol[i]] != -1); assert(newRowIndex[Arow[i]] != -1); @@ -1502,9 +1501,14 @@ HPresolve::Result HPresolve::runProbing(HighsPostsolveStack& postsolve_stack) { HighsInt numImplicsStart = implications.getNumImplications(); HighsInt numDelStart = probingNumDelCol; - HighsInt numDel = probingNumDelCol - numDelStart + - implications.substitutions.size() + - cliquetable.getSubstitutions().size(); + auto calcNumDel = [&]() { + return probingNumDelCol - numDelStart + + static_cast(implications.substitutions.size() + + cliquetable.getSubstitutions().size()); + }; + + HighsInt numDel = calcNumDel(); + int64_t splayContingent = cliquetable.numNeighbourhoodQueries + std::max(mipsolver->submip ? HighsInt{0} : HighsInt{100000}, @@ -1590,9 +1594,7 @@ HPresolve::Result HPresolve::runProbing(HighsPostsolveStack& postsolve_stack) { if (domain.isFixed(domain.getChangedCols()[numChangedCols++])) ++probingNumDelCol; } - HighsInt newNumDel = probingNumDelCol - numDelStart + - implications.substitutions.size() + - cliquetable.getSubstitutions().size(); + HighsInt newNumDel = calcNumDel(); if (newNumDel > numDel) { probingContingent += numDel; @@ -1641,7 +1643,7 @@ HPresolve::Result HPresolve::runProbing(HighsPostsolveStack& postsolve_stack) { // add nonzeros from clique lifting before removing fixed variables, since // this might lead to stronger constraint sides auto& extensionvars = cliquetable.getCliqueExtensions(); - HighsInt addednnz = extensionvars.size(); + HighsInt addednnz = static_cast(extensionvars.size()); for (const auto& cliqueextension : extensionvars) { if (rowDeleted[cliqueextension.first]) { --addednnz; @@ -1902,7 +1904,7 @@ void HPresolve::addToMatrix(const HighsInt row, const HighsInt col, if (pos == -1) { if (freeslots.empty()) { - pos = Avalue.size(); + pos = static_cast(Avalue.size()); Avalue.push_back(val); Arow.push_back(row); Acol.push_back(col); @@ -2374,9 +2376,9 @@ bool HPresolve::okFromCSC(const std::vector& Aval, rowDualUpperSource.data()); impliedDualRowBounds.setNumSums(model->num_col_); - HighsInt ncol = Astart.size() - 1; - assert(ncol == int(colhead.size())); - HighsInt nnz = Aval.size(); + HighsInt ncol = static_cast(Astart.size()) - 1; + assert(static_cast(ncol) == colhead.size()); + HighsInt nnz = static_cast(Aval.size()); Avalue = Aval; if (!okReserve(Acol, nnz)) return false; @@ -2437,9 +2439,9 @@ bool HPresolve::okFromCSR(const std::vector& ARval, rowDualUpperSource.data()); impliedDualRowBounds.setNumSums(model->num_col_); - HighsInt nrow = ARstart.size() - 1; - assert(nrow == int(rowroot.size())); - HighsInt nnz = ARval.size(); + HighsInt nrow = static_cast(ARstart.size()) - 1; + assert(static_cast(nrow) == rowroot.size()); + HighsInt nnz = static_cast(ARval.size()); Avalue = ARval; if (!okReserve(Acol, nnz)) return false; @@ -2768,10 +2770,10 @@ void HPresolve::substitute(HighsInt row, HighsInt col, double rhs) { void HPresolve::toCSC(std::vector& Aval, std::vector& Aindex, std::vector& Astart) { // set up the column starts using the column size array - HighsInt numcol = colsize.size(); + size_t numcol = colsize.size(); Astart.resize(numcol + 1); HighsInt nnz = 0; - for (HighsInt i = 0; i != numcol; ++i) { + for (size_t i = 0; i != numcol; ++i) { Astart[i] = nnz; nnz += colsize[i]; } @@ -2782,9 +2784,9 @@ void HPresolve::toCSC(std::vector& Aval, std::vector& Aindex, // for determining the position of each nonzero Aval.resize(nnz); Aindex.resize(nnz); - HighsInt numslots = Avalue.size(); - assert(numslots - int(freeslots.size()) == nnz); - for (HighsInt i = 0; i != numslots; ++i) { + size_t numslots = Avalue.size(); + assert(numslots - freeslots.size() == static_cast(nnz)); + for (size_t i = 0; i != numslots; ++i) { if (Avalue[i] == 0.0) continue; assert(Acol[i] >= 0 && Acol[i] < model->num_col_); HighsInt pos = Astart[Acol[i] + 1] - colsize[Acol[i]]; @@ -2799,10 +2801,10 @@ void HPresolve::toCSR(std::vector& ARval, std::vector& ARindex, std::vector& ARstart) { // set up the row starts using the row size array - HighsInt numrow = rowsize.size(); + size_t numrow = rowsize.size(); ARstart.resize(numrow + 1); HighsInt nnz = 0; - for (HighsInt i = 0; i != numrow; ++i) { + for (size_t i = 0; i != numrow; ++i) { ARstart[i] = nnz; nnz += rowsize[i]; } @@ -3549,11 +3551,11 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, rhs = std::round(rhs); - HighsInt rowlen = rowpositions.size(); HighsInt x1Cand = -1; int64_t d = 0; - for (HighsInt i = 0; i < rowlen; ++i) { + for (HighsInt i = 0; i < static_cast(rowpositions.size()); + ++i) { int64_t newgcd = d == 0 ? int64_t(std::abs( std::round(intScale * Avalue[rowpositions[i]]))) @@ -4401,7 +4403,8 @@ HPresolve::Result HPresolve::presolve(HighsPostsolveStack& postsolve_stack) { if (!mipsolver || mipsolver->mipdata_->numRestarts == 0) { HighsInt numCol = model->num_col_ - numDeletedCols; HighsInt numRow = model->num_row_ - numDeletedRows; - HighsInt numNonz = Avalue.size() - freeslots.size(); + HighsInt numNonz = + static_cast(Avalue.size() - freeslots.size()); // Only read the run time if it's to be printed const double run_time = options->output_flag ? this->timer->read() : 0; #ifndef NDEBUG diff --git a/highs/util/HighsSparseVectorSum.h b/highs/util/HighsSparseVectorSum.h index b4553e5f9b4..5f1090834f5 100644 --- a/highs/util/HighsSparseVectorSum.h +++ b/highs/util/HighsSparseVectorSum.h @@ -75,7 +75,7 @@ class HighsSparseVectorSum { template void cleanup(IsZero&& isZero) { - HighsInt numNz = nonzeroinds.size(); + HighsInt numNz = static_cast(nonzeroinds.size()); for (HighsInt i = numNz - 1; i >= 0; --i) { HighsInt pos = nonzeroinds[i]; From eebb63eb840ad400faee28a729eeca8d18663a97 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Thu, 21 Aug 2025 10:09:00 +0200 Subject: [PATCH 18/25] Add comments --- highs/presolve/HPresolve.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index 298bec48172..27233dffa50 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1019,6 +1019,9 @@ void HPresolve::shrinkProblem(HighsPostsolveStack& postsolve_stack) { HPresolve::Result HPresolve::dominatedColumns( HighsPostsolveStack& postsolve_stack) { + // See section 6.4 "Dominated columns", Achterberg et al., Presolve Reductions + // in Mixed Integer Programming, INFORMS Journal on Computing 32(2):473-506. + std::vector> signatures(model->num_col_); auto isBinary = [&](HighsInt i) { @@ -1176,6 +1179,7 @@ HPresolve::Result HPresolve::dominatedColumns( HighsInt scale = model->row_upper_[row] != kHighsInf ? 1 : -1; if (colIsBinary) { + // lambda for calculating residual minimum / maximum row activity auto getResidual = [&](HighsInt row, HighsInt col, double val, HighsInt direction) { if (direction > 0) @@ -1184,6 +1188,7 @@ HPresolve::Result HPresolve::dominatedColumns( return impliedRowBounds.getResidualSumLower(row, col, val); }; + // lambda for updating worst-case bound on binary variables auto updateWorstCaseBounds = [&](HighsInt row, HighsInt col, double val, HighsInt direction, double rhs) { if (direction * rhs == kHighsInf) return; @@ -1198,6 +1203,7 @@ HPresolve::Result HPresolve::dominatedColumns( } }; + // compute worst-case bounds for binary variables updateWorstCaseBounds(row, j, nonz.value(), HighsInt{1}, model->row_upper_[row]); updateWorstCaseBounds(row, j, nonz.value(), HighsInt{-1}, @@ -1239,6 +1245,7 @@ HPresolve::Result HPresolve::dominatedColumns( }; if (colIsBinary) { + // lambda for checking whether a binary variable can be fixed auto binaryCanBeFixed = [&](HighsInt col, HighsInt k, double bestVal, double val, HighsInt direction, HighsInt multiplier, bool isEqOrRangedRow) { @@ -1251,6 +1258,7 @@ HPresolve::Result HPresolve::dominatedColumns( checkDomination(direction, col, mydirection, k); }; + // lambda for fixing binary variables auto checkFixBinary = [&](HighsInt row, HighsInt col, HighsInt direction, double scale, double bestVal) { storeRow(row); @@ -1275,6 +1283,7 @@ HPresolve::Result HPresolve::dominatedColumns( } } + // remove row singletons and doubleton equations if binary was fixed if (colDeleted[col]) { HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); HPRESOLVE_CHECKED_CALL(removeDoubletonEquations(postsolve_stack)); @@ -1302,6 +1311,8 @@ HPresolve::Result HPresolve::dominatedColumns( } } + // lambda for determining whether column bound is finite (in given + // direction) auto isBoundFinite = [&](HighsInt col, HighsInt direction) { if (direction < 0) return model->col_upper_[col] != kHighsInf; @@ -1309,6 +1320,7 @@ HPresolve::Result HPresolve::dominatedColumns( return model->col_lower_[col] != -kHighsInf; }; + // lambda for checking whether a variable can be fixed auto colCanBeFixed = [&](HighsInt col, HighsInt k, double bestVal, double val, HighsInt direction, HighsInt multiplier, bool boundImplied, @@ -1327,6 +1339,7 @@ HPresolve::Result HPresolve::dominatedColumns( checkDomination(direction, col, mydirection, k); }; + // lambda for fixing variables auto checkFixCol = [&](HighsInt row, HighsInt col, HighsInt direction, double scale, double bestVal, bool boundImplied) { storeRow(row); @@ -1362,16 +1375,19 @@ HPresolve::Result HPresolve::dominatedColumns( return Result::kOk; }; + // try to fix variables using row 'bestRowMinus' if (bestRowMinus != -1 && (lowerImplied || hasNegCliques)) HPRESOLVE_CHECKED_CALL(checkFixCol(bestRowMinus, j, HighsInt{-1}, bestRowMinusScale, ajBestRowMinus, lowerImplied)); + // try to fix variables using row 'bestRowPlus' if (bestRowPlus != -1 && (upperImplied || hasPosCliques)) HPRESOLVE_CHECKED_CALL(checkFixCol(bestRowPlus, j, HighsInt{1}, bestRowPlusScale, ajBestRowPlus, upperImplied)); + // remove doubleton equations if (numFixedCols != oldNumFixed) HPRESOLVE_CHECKED_CALL(removeDoubletonEquations(postsolve_stack)); } From 6d447933bfb470e1f4212795219341e41031fb48 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Tue, 26 Aug 2025 15:13:45 +0200 Subject: [PATCH 19/25] Add another lambda --- highs/presolve/HPresolve.cpp | 78 ++++++++++++++---------------------- 1 file changed, 29 insertions(+), 49 deletions(-) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index 27233dffa50..36aeca136ce 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1037,6 +1037,28 @@ HPresolve::Result HPresolve::dominatedColumns( signatures[col].second |= rowUpperFinite << rowHashedPos; }; + auto checkDominationNonZero = [&](HighsInt row, double aj, double ak) { + if (model->row_lower_[row] != -kHighsInf && + model->row_upper_[row] != kHighsInf) { + // the row is an equality or ranged row, therefore the coefficients must + // be parallel, otherwise one of the inequalities given by the row rules + // out domination + if (std::abs(aj - ak) > options->small_matrix_value) return false; + return true; + } + + // normalize row to a <= constraint + if (model->row_upper_[row] == kHighsInf) { + aj = -aj; + ak = -ak; + } + + // the coefficient of the dominating column needs to be smaller than or + // equal to the coefficient of the dominated column + if (aj > ak + options->small_matrix_value) return false; + return true; + }; + auto checkDomination = [&](HighsInt scalj, HighsInt j, HighsInt scalk, HighsInt k) { // rule out domination from integers to continuous variables @@ -1061,70 +1083,28 @@ HPresolve::Result HPresolve::dominatedColumns( // dominated columns set of rows with a positive coefficient if ((sjPlus & ~skPlus) != 0) return false; - // next check if the columns cost allows for domination - double cj = scalj * model->col_cost_[j]; - double ck = scalk * model->col_cost_[k]; - // the dominating columns cost must be smaller or equal to the dominated // columns cost - if (cj > ck + options->small_matrix_value) return false; + if (scalj * model->col_cost_[j] > + scalk * model->col_cost_[k] + options->small_matrix_value) + return false; // finally check the column vectors for (const HighsSliceNonzero& nonz : getColumnVector(j)) { HighsInt row = nonz.index(); - double aj = scalj * nonz.value(); - HighsInt akPos = findNonzero(row, k); - double ak = scalk * (akPos == -1 ? 0.0 : Avalue[akPos]); - - if (model->row_lower_[row] != -kHighsInf && - model->row_upper_[row] != kHighsInf) { - // the row is an equality or ranged row, therefore the coefficients must - // be parallel, otherwise one of the inequalities given by the row rules - // out domination - if (std::abs(aj - ak) > options->small_matrix_value) return false; - continue; - } - - // normalize row to a <= constraint - if (model->row_upper_[row] == kHighsInf) { - aj = -aj; - ak = -ak; - } - - // the coefficient of the dominating column needs to be smaller than or - // equal to the coefficient of the dominated column - if (aj > ak + options->small_matrix_value) return false; + if (!checkDominationNonZero(row, scalj * nonz.value(), + scalk * (akPos == -1 ? 0.0 : Avalue[akPos]))) + return false; } // check row only occurring in the column vector of k for (const HighsSliceNonzero& nonz : getColumnVector(k)) { HighsInt row = nonz.index(); - double ak = scalk * nonz.value(); - HighsInt ajPos = findNonzero(row, j); // only rows in which aj does not occur are left to check if (ajPos != -1) continue; - double aj = 0.0; - - if (model->row_lower_[row] != -kHighsInf && - model->row_upper_[row] != kHighsInf) { - // the row is an equality or ranged row, therefore the coefficients must - // be parallel, otherwise one of the inequalities given by the row rules - // out domination - if (std::abs(aj - ak) > options->small_matrix_value) return false; - continue; - } - - // normalize row to a <= constraint - if (model->row_upper_[row] == kHighsInf) { - aj = -aj; - ak = -ak; - } - - // the coefficient of the dominating column needs to be smaller than or - // equal to the coefficient of the dominated column - if (aj > ak + options->small_matrix_value) return false; + if (!checkDominationNonZero(row, 0.0, scalk * nonz.value())) return false; } return true; From 59a6f061489ed78b6a702021f5f87ecb303db052 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Fri, 29 Aug 2025 10:12:14 +0200 Subject: [PATCH 20/25] Add comments --- highs/presolve/HPresolve.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index 5f1e191e57e..5e535af1cab 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1173,10 +1173,12 @@ HPresolve::Result HPresolve::dominatedColumns( HighsInt direction, double rhs) { if (direction * rhs == kHighsInf) return; if (model->col_cost_[col] >= 0.0 && direction * val < 0.0) { + // compute worst-case lower bound worstCaseLb = std::max((rhs - getResidual(row, col, val, direction)) / val, worstCaseLb); } else if (model->col_cost_[col] <= 0.0 && direction * val > 0.0) { + // compute worst-case upper bound worstCaseUb = std::min((rhs - getResidual(row, col, val, direction)) / val, worstCaseUb); @@ -1268,9 +1270,12 @@ HPresolve::Result HPresolve::dominatedColumns( HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); HPRESOLVE_CHECKED_CALL(removeDoubletonEquations(postsolve_stack)); } - return Result::kOk; + return Result::kOk;‚ }; + // try to fix binary variables; see Gamrath, G., Koch, T., Martin, A. et + // al. Progress in presolving for mixed integer programming. Math. Prog. + // Comp. 7, 367–398 (2015). if (model->col_cost_[j] >= 0.0 && worstCaseLb <= 1 + primal_feastol) { upperImplied = true; if (!lowerImplied && bestRowMinus != -1) { From 97ba645b3b6c989a0377e925a953f896ea6ad0e8 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Fri, 29 Aug 2025 10:17:28 +0200 Subject: [PATCH 21/25] Fix typo --- highs/presolve/HPresolve.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index 5e535af1cab..30f7716a07a 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1270,7 +1270,7 @@ HPresolve::Result HPresolve::dominatedColumns( HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); HPRESOLVE_CHECKED_CALL(removeDoubletonEquations(postsolve_stack)); } - return Result::kOk;‚ + return Result::kOk; }; // try to fix binary variables; see Gamrath, G., Koch, T., Martin, A. et From ee20dd3a8be0510b0e6e47f62bc43cea51a06117 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Fri, 29 Aug 2025 14:04:45 +0200 Subject: [PATCH 22/25] More comments --- highs/presolve/HPresolve.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index 30f7716a07a..59c28d82058 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1171,14 +1171,22 @@ HPresolve::Result HPresolve::dominatedColumns( // lambda for updating worst-case bound on binary variables auto updateWorstCaseBounds = [&](HighsInt row, HighsInt col, double val, HighsInt direction, double rhs) { + // direction = 1 (<= row): use upper bound on row's activity to + // compute worst-case implied column bounds. + // direction = -1 (>= row): use lower bound on row's activity to + // compute worst-case implied column bounds. if (direction * rhs == kHighsInf) return; if (model->col_cost_[col] >= 0.0 && direction * val < 0.0) { - // compute worst-case lower bound + // worst-case lower bound is non-negative: + // direction = 1 (<= row): rhs - getResidual(...) <= 0 and val < 0. + // direction = -1 (>= row): rhs - getResidual(...) >= 0 and val > 0. worstCaseLb = std::max((rhs - getResidual(row, col, val, direction)) / val, worstCaseLb); } else if (model->col_cost_[col] <= 0.0 && direction * val > 0.0) { - // compute worst-case upper bound + // worst-case upper bound is non-positive: + // direction = 1 (<= row): rhs - getResidual(...) <= 0 and val > 0. + // direction = -1 (>= row): rhs - getResidual(...) >= 0 and val < 0. worstCaseUb = std::min((rhs - getResidual(row, col, val, direction)) / val, worstCaseUb); @@ -1277,6 +1285,7 @@ HPresolve::Result HPresolve::dominatedColumns( // al. Progress in presolving for mixed integer programming. Math. Prog. // Comp. 7, 367–398 (2015). if (model->col_cost_[j] >= 0.0 && worstCaseLb <= 1 + primal_feastol) { + // try to fix binary variable to zero upperImplied = true; if (!lowerImplied && bestRowMinus != -1) { HPRESOLVE_CHECKED_CALL(checkFixBinary(bestRowMinus, j, HighsInt{-1}, @@ -1287,6 +1296,7 @@ HPresolve::Result HPresolve::dominatedColumns( } if (model->col_cost_[j] <= 0.0 && worstCaseUb >= -primal_feastol) { + // try to fix variable to one lowerImplied = true; if (!upperImplied && bestRowPlus != -1) { HPRESOLVE_CHECKED_CALL(checkFixBinary( From ff94d97a7255ac0fc94ab0bd5f278d5bbfc5eaff Mon Sep 17 00:00:00 2001 From: fwesselm Date: Fri, 29 Aug 2025 15:08:11 +0200 Subject: [PATCH 23/25] More comments --- highs/presolve/HPresolve.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index 59c28d82058..bc47eeefa9a 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1281,11 +1281,11 @@ HPresolve::Result HPresolve::dominatedColumns( return Result::kOk; }; - // try to fix binary variables; see Gamrath, G., Koch, T., Martin, A. et - // al. Progress in presolving for mixed integer programming. Math. Prog. - // Comp. 7, 367–398 (2015). + // see Gamrath, G., Koch, T., Martin, A. et al. Progress in presolving for + // mixed integer programming. Math. Prog. Comp. 7, 367–398 (2015). if (model->col_cost_[j] >= 0.0 && worstCaseLb <= 1 + primal_feastol) { - // try to fix binary variable to zero + // cost is positive and 1 + primal_feastol >= worstCaseLb >= 0: try to + // fix binary variable to zero upperImplied = true; if (!lowerImplied && bestRowMinus != -1) { HPRESOLVE_CHECKED_CALL(checkFixBinary(bestRowMinus, j, HighsInt{-1}, @@ -1296,7 +1296,8 @@ HPresolve::Result HPresolve::dominatedColumns( } if (model->col_cost_[j] <= 0.0 && worstCaseUb >= -primal_feastol) { - // try to fix variable to one + // cost is negative and 0 >= worstCaseUb >= -primal_feastol: try to fix + // variable to one lowerImplied = true; if (!upperImplied && bestRowPlus != -1) { HPRESOLVE_CHECKED_CALL(checkFixBinary( From 3629ebf9d491715dde1aeeacc1681e3c0ce6415e Mon Sep 17 00:00:00 2001 From: fwesselm Date: Mon, 1 Sep 2025 11:06:12 +0200 Subject: [PATCH 24/25] Try to improve some comments --- highs/presolve/HPresolve.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index bc47eeefa9a..b0e555af6b0 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1284,8 +1284,9 @@ HPresolve::Result HPresolve::dominatedColumns( // see Gamrath, G., Koch, T., Martin, A. et al. Progress in presolving for // mixed integer programming. Math. Prog. Comp. 7, 367–398 (2015). if (model->col_cost_[j] >= 0.0 && worstCaseLb <= 1 + primal_feastol) { - // cost is positive and 1 + primal_feastol >= worstCaseLb >= 0: try to - // fix binary variable to zero + // cost is positive and 1 + primal_feastol >= worstCaseLb >= 0, i.e. + // worstCaseLb agrees with the bounds: try to find dominated variable + // that allows for fixing binary variable to zero upperImplied = true; if (!lowerImplied && bestRowMinus != -1) { HPRESOLVE_CHECKED_CALL(checkFixBinary(bestRowMinus, j, HighsInt{-1}, @@ -1296,8 +1297,9 @@ HPresolve::Result HPresolve::dominatedColumns( } if (model->col_cost_[j] <= 0.0 && worstCaseUb >= -primal_feastol) { - // cost is negative and 0 >= worstCaseUb >= -primal_feastol: try to fix - // variable to one + // cost is negative and 0 >= worstCaseUb >= -primal_feastol, i.e. + // worstCaseUb agrees with the bounds: try to find dominated variable + // that allows for fixing binary variable to one lowerImplied = true; if (!upperImplied && bestRowPlus != -1) { HPRESOLVE_CHECKED_CALL(checkFixBinary( From aa79316c51cbcc8cd1d4da9cd31e32637c15917f Mon Sep 17 00:00:00 2001 From: fwesselm Date: Thu, 4 Sep 2025 13:48:00 +0200 Subject: [PATCH 25/25] Use new utility --- highs/presolve/HPresolve.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index 6ff72e664df..917b1bbece5 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1043,8 +1043,7 @@ HPresolve::Result HPresolve::dominatedColumns( }; auto checkDominationNonZero = [&](HighsInt row, double aj, double ak) { - if (model->row_lower_[row] != -kHighsInf && - model->row_upper_[row] != kHighsInf) { + if (isRanged(row)) { // the row is an equality or ranged row, therefore the coefficients must // be parallel, otherwise one of the inequalities given by the row rules // out domination @@ -1257,8 +1256,7 @@ HPresolve::Result HPresolve::dominatedColumns( auto checkFixBinary = [&](HighsInt row, HighsInt col, HighsInt direction, double scale, double bestVal) { storeRow(row); - bool isEqOrRangedRow = model->row_lower_[row] != -kHighsInf && - model->row_upper_[row] != kHighsInf; + bool isEqOrRangedRow = isRanged(row); for (const HighsSliceNonzero& nonz : getStoredRow()) { HighsInt k = nonz.index(); @@ -1346,8 +1344,7 @@ HPresolve::Result HPresolve::dominatedColumns( auto checkFixCol = [&](HighsInt row, HighsInt col, HighsInt direction, double scale, double bestVal, bool boundImplied) { storeRow(row); - bool isEqOrRangedRow = model->row_lower_[row] != -kHighsInf && - model->row_upper_[row] != kHighsInf; + bool isEqOrRangedRow = isRanged(row); for (const HighsSliceNonzero& nonz : getStoredRow()) { HighsInt k = nonz.index();