diff --git a/src/presolve/HPresolve.cpp b/src/presolve/HPresolve.cpp index a6f0776410..ed97105e4c 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,21 +2872,16 @@ 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 // 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 +2893,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]); @@ -3771,15 +3674,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; } } @@ -3864,98 +3769,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 @@ -4039,6 +3855,129 @@ HPresolve::Result HPresolve::colPresolve(HighsPostsolveStack& postsolve_stack, return Result::kOk; } +HPresolve::Result HPresolve::detectDominatedCol( + HighsPostsolveStack& postsolve_stack, HighsInt col, + bool handleSingletonRows) { + assert(!colDeleted[col]); + + // get bounds on column dual + 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) { + // 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); + 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); + // handle row singletons (if requested) + if (handleSingletonRows) + HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); + return checkLimits(postsolve_stack); + }; + + 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); + 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); + // 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, + 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 @@ -4846,18 +4785,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 +4824,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..45847b4fe3 100644 --- a/src/presolve/HPresolve.h +++ b/src/presolve/HPresolve.h @@ -315,10 +315,16 @@ 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); + Result detectDominatedCol(HighsPostsolveStack& postsolve_stack, HighsInt col, + bool handleSingletonRows = true); + Result initialRowAndColPresolve(HighsPostsolveStack& postsolve_stack); HighsModelStatus run(HighsPostsolveStack& postsolve_stack);