From 3ea858a49532ce22d11d1755c3b4eb39fe8a9a2a Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 13 Nov 2024 16:53:26 +0100 Subject: [PATCH 1/4] Add util for free column substitution --- src/presolve/HPresolve.cpp | 51 ++++++++++++++++++-------------------- src/presolve/HPresolve.h | 3 +++ 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/presolve/HPresolve.cpp b/src/presolve/HPresolve.cpp index a6f0776410..67e88613d9 100644 --- a/src/presolve/HPresolve.cpp +++ b/src/presolve/HPresolve.cpp @@ -2986,19 +2986,12 @@ HPresolve::Result HPresolve::singletonCol(HighsPostsolveStack& postsolve_stack, if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleFreeColSubstitution); + // todo, store which side of an implied free dual variable needs to be used // for substitution storeRow(row); - HighsPostsolveStack::RowType rowType; - double rhs; - dualImpliedFreeGetRhsAndRowType(row, rhs, rowType); - - postsolve_stack.freeColSubstitution(row, col, rhs, model->col_cost_[col], - rowType, getStoredRow(), - getColumnVector(col)); - // todo, check integrality of coefficients and allow this - substitute(row, col, rhs); + substituteFreeCol(postsolve_stack, row, col); analysis_.logging_on_ = logging_on; if (logging_on) @@ -3010,6 +3003,26 @@ HPresolve::Result HPresolve::singletonCol(HighsPostsolveStack& postsolve_stack, return Result::kOk; } +void HPresolve::substituteFreeCol(HighsPostsolveStack& postsolve_stack, + HighsInt row, HighsInt col, + bool relaxRowDualBounds) { + assert(!rowDeleted[row]); + assert(!colDeleted[col]); + assert(isDualImpliedFree(row)); + assert(isImpliedFree(col)); + + HighsPostsolveStack::RowType rowType; + double rhs; + dualImpliedFreeGetRhsAndRowType(row, rhs, rowType, relaxRowDualBounds); + + postsolve_stack.freeColSubstitution(row, col, rhs, model->col_cost_[col], + rowType, getStoredRow(), + getColumnVector(col)); + + // todo, check integrality of coefficients and allow this + substitute(row, col, rhs); +} + HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, HighsInt row) { assert(!rowDeleted[row]); @@ -4846,18 +4859,9 @@ HPresolve::Result HPresolve::aggregator(HighsPostsolveStack& postsolve_stack) { // in the case where the row has length two or the column has length two // we always do the substitution since the fillin can never be problematic if (rowsize[row] == 2 || colsize[col] == 2) { - double rhs; - HighsPostsolveStack::RowType rowType; - dualImpliedFreeGetRhsAndRowType(row, rhs, rowType, true); - storeRow(row); - - postsolve_stack.freeColSubstitution(row, col, rhs, model->col_cost_[col], - rowType, getStoredRow(), - getColumnVector(col)); + substituteFreeCol(postsolve_stack, row, col, true); substitutionOpportunities[i].first = -1; - - substitute(row, col, rhs); HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); HPRESOLVE_CHECKED_CALL(checkLimits(postsolve_stack)); continue; @@ -4894,15 +4898,8 @@ HPresolve::Result HPresolve::aggregator(HighsPostsolveStack& postsolve_stack) { } nfail = 0; - double rhs; - HighsPostsolveStack::RowType rowType; - dualImpliedFreeGetRhsAndRowType(row, rhs, rowType, true); - - postsolve_stack.freeColSubstitution(row, col, rhs, model->col_cost_[col], - rowType, getStoredRow(), - getColumnVector(col)); + substituteFreeCol(postsolve_stack, row, col, true); substitutionOpportunities[i].first = -1; - substitute(row, col, rhs); HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); HPRESOLVE_CHECKED_CALL(checkLimits(postsolve_stack)); } diff --git a/src/presolve/HPresolve.h b/src/presolve/HPresolve.h index 75625e22a2..ce0bfb2bcb 100644 --- a/src/presolve/HPresolve.h +++ b/src/presolve/HPresolve.h @@ -315,6 +315,9 @@ class HPresolve { Result singletonCol(HighsPostsolveStack& postsolve_stack, HighsInt col); + void substituteFreeCol(HighsPostsolveStack& postsolve_stack, HighsInt row, + HighsInt col, bool relaxRowDualBounds = false); + Result rowPresolve(HighsPostsolveStack& postsolve_stack, HighsInt row); Result colPresolve(HighsPostsolveStack& postsolve_stack, HighsInt col); From a2a25d0c08a5f1700baa567dafbb1881271b624d Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 13 Nov 2024 20:57:36 +0100 Subject: [PATCH 2/4] Use HPRESOLVE_CHECKED_CALL --- src/presolve/HPresolve.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/presolve/HPresolve.cpp b/src/presolve/HPresolve.cpp index 67e88613d9..874033b3a2 100644 --- a/src/presolve/HPresolve.cpp +++ b/src/presolve/HPresolve.cpp @@ -3784,15 +3784,17 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, // in reverse, it will first restore the column primal and dual values // as the dual values are required to find the proper dual multiplier for // the row and the column that we put in the basis. - Result res = checkForcingRow(row, HighsInt{1}, model->row_lower_[row], - HighsPostsolveStack::RowType::kGeq); - if (rowDeleted[row]) return res; + HPRESOLVE_CHECKED_CALL( + checkForcingRow(row, HighsInt{1}, model->row_lower_[row], + HighsPostsolveStack::RowType::kGeq)); + if (rowDeleted[row]) return Result::kOk; } else if (impliedRowLower >= model->row_upper_[row] - primal_feastol) { // forcing row in the other direction - Result res = checkForcingRow(row, HighsInt{-1}, model->row_upper_[row], - HighsPostsolveStack::RowType::kLeq); - if (rowDeleted[row]) return res; + HPRESOLVE_CHECKED_CALL( + checkForcingRow(row, HighsInt{-1}, model->row_upper_[row], + HighsPostsolveStack::RowType::kLeq)); + if (rowDeleted[row]) return Result::kOk; } } From 416f3d78298428f263d5fbb891ac828c7e0e58ba Mon Sep 17 00:00:00 2001 From: fwesselm Date: Thu, 14 Nov 2024 09:17:46 +0100 Subject: [PATCH 3/4] Add utility for detecting dominated columns --- src/presolve/HPresolve.cpp | 107 +++++++++++++++++++++++++++++++++++++ src/presolve/HPresolve.h | 3 ++ 2 files changed, 110 insertions(+) diff --git a/src/presolve/HPresolve.cpp b/src/presolve/HPresolve.cpp index a6f0776410..67d44175b2 100644 --- a/src/presolve/HPresolve.cpp +++ b/src/presolve/HPresolve.cpp @@ -4039,6 +4039,113 @@ HPresolve::Result HPresolve::colPresolve(HighsPostsolveStack& postsolve_stack, return Result::kOk; } +HPresolve::Result HPresolve::detectDominatedCol( + HighsPostsolveStack& postsolve_stack, HighsInt col, + bool handleSingletonRows) { + assert(!colDeleted[col]); + + double colDualUpper = + -impliedDualRowBounds.getSumLower(col, -model->col_cost_[col]); + double colDualLower = + -impliedDualRowBounds.getSumUpper(col, -model->col_cost_[col]); + + const bool logging_on = analysis_.logging_on_; + + auto dominatedCol = [&](HighsInt col, double dualBound, double bound, + HighsInt direction) { + if (direction * dualBound <= options->dual_feasibility_tolerance) + return Result::kOk; + if (direction * bound == -kHighsInf) return Result::kDualInfeasible; + if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleDominatedCol); + bool unbounded = false; + if (direction > 0) + unbounded = fixColToLowerOrUnbounded(postsolve_stack, col); + else + unbounded = fixColToUpperOrUnbounded(postsolve_stack, col); + if (unbounded) { + // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; + } + analysis_.logging_on_ = logging_on; + if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleDominatedCol); + if (handleSingletonRows) + HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); + return checkLimits(postsolve_stack); + }; + + auto weaklyDominatedCol = [&](HighsInt col, double dualBound, double bound, + double otherBound, HighsInt direction) { + if (direction * dualBound < -options->dual_feasibility_tolerance) + return Result::kOk; + if (direction * bound != -kHighsInf) { + if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleDominatedCol); + bool unbounded = false; + if (direction > 0) + unbounded = fixColToLowerOrUnbounded(postsolve_stack, col); + else + unbounded = fixColToUpperOrUnbounded(postsolve_stack, col); + if (unbounded) { + // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; + } + analysis_.logging_on_ = logging_on; + if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleDominatedCol); + return checkLimits(postsolve_stack); + } else if (analysis_.allow_rule_[kPresolveRuleForcingCol]) { + HighsCDouble sum = 0; + if (direction > 0) + sum = impliedDualRowBounds.getSumUpperOrig(col); + else + sum = impliedDualRowBounds.getSumLowerOrig(col); + if (sum == 0.0) { + if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleForcingCol); + postsolve_stack.forcingColumn( + col, getColumnVector(col), model->col_cost_[col], otherBound, + direction < 0, model->integrality_[col] == HighsVarType::kInteger); + markColDeleted(col); + HighsInt coliter = colhead[col]; + while (coliter != -1) { + HighsInt row = Arow[coliter]; + double rhs = direction * Avalue[coliter] > 0.0 + ? model->row_upper_[row] + : model->row_lower_[row]; + coliter = Anext[coliter]; + + postsolve_stack.forcingColumnRemovedRow(col, row, rhs, + getRowVector(row)); + removeRow(row); + } + analysis_.logging_on_ = logging_on; + if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleForcingCol); + return checkLimits(postsolve_stack); + } + } + return Result::kOk; + }; + + // check for dominated column + HPRESOLVE_CHECKED_CALL( + dominatedCol(col, colDualLower, model->col_lower_[col], HighsInt{1})); + if (colDeleted[col]) return Result::kOk; + + HPRESOLVE_CHECKED_CALL( + dominatedCol(col, colDualUpper, model->col_upper_[col], HighsInt{-1})); + if (colDeleted[col]) return Result::kOk; + + // check for weakly dominated column + HPRESOLVE_CHECKED_CALL( + weaklyDominatedCol(col, colDualLower, model->col_lower_[col], + model->col_upper_[col], HighsInt{1})); + if (colDeleted[col]) return Result::kOk; + + HPRESOLVE_CHECKED_CALL( + weaklyDominatedCol(col, colDualUpper, model->col_upper_[col], + model->col_lower_[col], HighsInt{-1})); + return Result::kOk; +} + HPresolve::Result HPresolve::initialRowAndColPresolve( HighsPostsolveStack& postsolve_stack) { // do a full scan over the rows as the singleton arrays and the changed row diff --git a/src/presolve/HPresolve.h b/src/presolve/HPresolve.h index 75625e22a2..b49555597c 100644 --- a/src/presolve/HPresolve.h +++ b/src/presolve/HPresolve.h @@ -319,6 +319,9 @@ class HPresolve { Result colPresolve(HighsPostsolveStack& postsolve_stack, HighsInt col); + Result detectDominatedCol(HighsPostsolveStack& postsolve_stack, HighsInt col, + bool handleSingletonRows = true); + Result initialRowAndColPresolve(HighsPostsolveStack& postsolve_stack); HighsModelStatus run(HighsPostsolveStack& postsolve_stack); From c1a2a279ff68f38dc36552ffa84f0f3a102da455 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Thu, 14 Nov 2024 10:36:35 +0100 Subject: [PATCH 4/4] Use new utility and add some comments --- src/presolve/HPresolve.cpp | 231 ++++--------------------------------- 1 file changed, 24 insertions(+), 207 deletions(-) diff --git a/src/presolve/HPresolve.cpp b/src/presolve/HPresolve.cpp index 67d44175b2..f60c1fe843 100644 --- a/src/presolve/HPresolve.cpp +++ b/src/presolve/HPresolve.cpp @@ -2853,121 +2853,9 @@ HPresolve::Result HPresolve::singletonCol(HighsPostsolveStack& postsolve_stack, return Result::kOk; } - double colDualUpper = - -impliedDualRowBounds.getSumLower(col, -model->col_cost_[col]); - double colDualLower = - -impliedDualRowBounds.getSumUpper(col, -model->col_cost_[col]); - - const bool logging_on = analysis_.logging_on_; - // check for dominated column - if (colDualLower > options->dual_feasibility_tolerance) { - if (model->col_lower_[col] == -kHighsInf) return Result::kDualInfeasible; - if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleDominatedCol); - if (fixColToLowerOrUnbounded(postsolve_stack, col)) { - // Handle unboundedness - presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; - return Result::kDualInfeasible; - } - analysis_.logging_on_ = logging_on; - if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleDominatedCol); - return checkLimits(postsolve_stack); - } - - if (colDualUpper < -options->dual_feasibility_tolerance) { - if (model->col_upper_[col] == kHighsInf) return Result::kDualInfeasible; - if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleDominatedCol); - if (fixColToUpperOrUnbounded(postsolve_stack, col)) { - // Handle unboundedness - presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; - return Result::kDualInfeasible; - } - analysis_.logging_on_ = logging_on; - if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleDominatedCol); - return checkLimits(postsolve_stack); - } - - // check for weakly dominated column - if (colDualUpper <= options->dual_feasibility_tolerance) { - if (model->col_upper_[col] != kHighsInf) { - if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleDominatedCol); - if (fixColToUpperOrUnbounded(postsolve_stack, col)) { - // Handle unboundedness - presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; - return Result::kDualInfeasible; - } - analysis_.logging_on_ = logging_on; - if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleDominatedCol); - } else if (impliedDualRowBounds.getSumLowerOrig(col) == 0.0 && - analysis_.allow_rule_[kPresolveRuleForcingCol]) { - // todo: forcing column, since this implies colDual >= 0 and we - // already checked that colDual <= 0 and since the cost are 0.0 - // all the rows are at a dual multiplier of zero and we can - // determine one nonbasic row in postsolve, and make the other - // rows and the column basic. The columns primal value is - // computed from the nonbasic row which is chosen such that the - // values of all rows are primal feasible printf("removing - // forcing column of size %" HIGHSINT_FORMAT "\n", - // colsize[col]); - if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleForcingCol); - postsolve_stack.forcingColumn( - col, getColumnVector(col), model->col_cost_[col], - model->col_lower_[col], true, - model->integrality_[col] == HighsVarType::kInteger); - markColDeleted(col); - HighsInt coliter = colhead[col]; - while (coliter != -1) { - HighsInt row = Arow[coliter]; - double rhs = Avalue[coliter] > 0.0 ? model->row_lower_[row] - : model->row_upper_[row]; - coliter = Anext[coliter]; - - postsolve_stack.forcingColumnRemovedRow(col, row, rhs, - getRowVector(row)); - removeRow(row); - } - analysis_.logging_on_ = logging_on; - if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleForcingCol); - } - return checkLimits(postsolve_stack); - } - if (colDualLower >= -options->dual_feasibility_tolerance) { - if (model->col_lower_[col] != -kHighsInf) { - if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleDominatedCol); - if (fixColToLowerOrUnbounded(postsolve_stack, col)) { - // Handle unboundedness - presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; - return Result::kDualInfeasible; - } - analysis_.logging_on_ = logging_on; - if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleDominatedCol); - } else if (impliedDualRowBounds.getSumUpperOrig(col) == 0.0 && - analysis_.allow_rule_[kPresolveRuleForcingCol]) { - // forcing column, since this implies colDual <= 0 and we already checked - // that colDual >= 0 - // printf("removing forcing column of size %" HIGHSINT_FORMAT "\n", - // colsize[col]); - if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleForcingCol); - postsolve_stack.forcingColumn( - col, getColumnVector(col), model->col_cost_[col], - model->col_upper_[col], false, - model->integrality_[col] == HighsVarType::kInteger); - markColDeleted(col); - HighsInt coliter = colhead[col]; - while (coliter != -1) { - HighsInt row = Arow[coliter]; - double rhs = Avalue[coliter] > 0.0 ? model->row_upper_[row] - : model->row_lower_[row]; - coliter = Anext[coliter]; - - postsolve_stack.forcingColumnRemovedRow(col, row, rhs, - getRowVector(row)); - removeRow(row); - } - analysis_.logging_on_ = logging_on; - if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleForcingCol); - } - return checkLimits(postsolve_stack); - } + // detect strong / weak domination + HPRESOLVE_CHECKED_CALL(detectDominatedCol(postsolve_stack, col, false)); + if (colDeleted[col]) return Result::kOk; if (mipsolver != nullptr) convertImpliedInteger(col, row); @@ -2984,6 +2872,8 @@ HPresolve::Result HPresolve::singletonCol(HighsPostsolveStack& postsolve_stack, !isImpliedIntegral(col)) return Result::kOk; + const bool logging_on = analysis_.logging_on_; + if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleFreeColSubstitution); // todo, store which side of an implied free dual variable needs to be used @@ -3864,98 +3754,9 @@ HPresolve::Result HPresolve::colPresolve(HighsPostsolveStack& postsolve_stack, break; } - double colDualUpper = - -impliedDualRowBounds.getSumLower(col, -model->col_cost_[col]); - double colDualLower = - -impliedDualRowBounds.getSumUpper(col, -model->col_cost_[col]); - - // check for dominated column - if (colDualLower > options->dual_feasibility_tolerance) { - if (model->col_lower_[col] == -kHighsInf) - return Result::kDualInfeasible; - else { - if (fixColToLowerOrUnbounded(postsolve_stack, col)) { - // Handle unboundedness - presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; - return Result::kDualInfeasible; - } - HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); - } - return checkLimits(postsolve_stack); - } - - if (colDualUpper < -options->dual_feasibility_tolerance) { - if (model->col_upper_[col] == kHighsInf) - return Result::kDualInfeasible; - else { - if (fixColToUpperOrUnbounded(postsolve_stack, col)) { - // Handle unboundedness - presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; - return Result::kDualInfeasible; - } - HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); - } - return checkLimits(postsolve_stack); - } - - // check for weakly dominated column - if (colDualUpper <= options->dual_feasibility_tolerance) { - if (model->col_upper_[col] != kHighsInf) { - if (fixColToUpperOrUnbounded(postsolve_stack, col)) { - // Handle unboundedness - presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; - return Result::kDualInfeasible; - } - HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); - return checkLimits(postsolve_stack); - } else if (impliedDualRowBounds.getSumLowerOrig(col) == 0.0) { - if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleForcingCol); - postsolve_stack.forcingColumn( - col, getColumnVector(col), model->col_cost_[col], - model->col_lower_[col], true, - model->integrality_[col] == HighsVarType::kInteger); - markColDeleted(col); - HighsInt coliter = colhead[col]; - while (coliter != -1) { - HighsInt row = Arow[coliter]; - double rhs = Avalue[coliter] > 0.0 ? model->row_lower_[row] - : model->row_upper_[row]; - coliter = Anext[coliter]; - postsolve_stack.forcingColumnRemovedRow(col, row, rhs, - getRowVector(row)); - removeRow(row); - } - analysis_.logging_on_ = logging_on; - if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleForcingCol); - } - } else if (colDualLower >= -options->dual_feasibility_tolerance) { - // symmetric case for fixing to the lower bound - if (model->col_lower_[col] != -kHighsInf) { - if (fixColToLowerOrUnbounded(postsolve_stack, col)) { - // Handle unboundedness - presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; - return Result::kDualInfeasible; - } - HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); - return checkLimits(postsolve_stack); - } else if (impliedDualRowBounds.getSumUpperOrig(col) == 0.0) { - postsolve_stack.forcingColumn( - col, getColumnVector(col), model->col_cost_[col], - model->col_upper_[col], false, - model->integrality_[col] == HighsVarType::kInteger); - markColDeleted(col); - HighsInt coliter = colhead[col]; - while (coliter != -1) { - HighsInt row = Arow[coliter]; - double rhs = Avalue[coliter] > 0.0 ? model->row_upper_[row] - : model->row_lower_[row]; - coliter = Anext[coliter]; - postsolve_stack.forcingColumnRemovedRow(col, row, rhs, - getRowVector(row)); - removeRow(row); - } - } - } + // detect strong / weak domination + HPRESOLVE_CHECKED_CALL(detectDominatedCol(postsolve_stack, col)); + if (colDeleted[col]) return Result::kOk; // column is not (weakly) dominated @@ -4044,6 +3845,7 @@ HPresolve::Result HPresolve::detectDominatedCol( bool handleSingletonRows) { assert(!colDeleted[col]); + // get bounds on column dual double colDualUpper = -impliedDualRowBounds.getSumLower(col, -model->col_cost_[col]); double colDualLower = @@ -4053,10 +3855,15 @@ HPresolve::Result HPresolve::detectDominatedCol( auto dominatedCol = [&](HighsInt col, double dualBound, double bound, HighsInt direction) { + // column is (strongly) dominated if the bounds on the column dual satisfy: + // 1. lower bound > dual feasibility tolerance (direction = 1) or + // 2. upper bound < -dual feasibility tolerance (direction = -1). if (direction * dualBound <= options->dual_feasibility_tolerance) return Result::kOk; + // cannot fix to +-infinity -> infeasible if (direction * bound == -kHighsInf) return Result::kDualInfeasible; if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleDominatedCol); + // fix variable bool unbounded = false; if (direction > 0) unbounded = fixColToLowerOrUnbounded(postsolve_stack, col); @@ -4069,6 +3876,7 @@ HPresolve::Result HPresolve::detectDominatedCol( } analysis_.logging_on_ = logging_on; if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleDominatedCol); + // handle row singletons (if requested) if (handleSingletonRows) HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); return checkLimits(postsolve_stack); @@ -4076,10 +3884,14 @@ HPresolve::Result HPresolve::detectDominatedCol( auto weaklyDominatedCol = [&](HighsInt col, double dualBound, double bound, double otherBound, HighsInt direction) { + // column is weakly dominated if the bounds on the column dual satisfy: + // 1. lower bound >= -dual feasibility tolerance (direction = 1) or + // 2. upper bound <= dual feasibility tolerance (direction = -1). if (direction * dualBound < -options->dual_feasibility_tolerance) return Result::kOk; if (direction * bound != -kHighsInf) { if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleDominatedCol); + // fix variable bool unbounded = false; if (direction > 0) unbounded = fixColToLowerOrUnbounded(postsolve_stack, col); @@ -4092,14 +3904,19 @@ HPresolve::Result HPresolve::detectDominatedCol( } analysis_.logging_on_ = logging_on; if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleDominatedCol); + // handle row singletons (if requested) + if (handleSingletonRows) + HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); return checkLimits(postsolve_stack); } else if (analysis_.allow_rule_[kPresolveRuleForcingCol]) { + // get bound on dual (column) activity HighsCDouble sum = 0; if (direction > 0) sum = impliedDualRowBounds.getSumUpperOrig(col); else sum = impliedDualRowBounds.getSumLowerOrig(col); if (sum == 0.0) { + // remove column and rows if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleForcingCol); postsolve_stack.forcingColumn( col, getColumnVector(col), model->col_cost_[col], otherBound,