From b4f0b20c4d70fdc66f586944d5000a437179bf49 Mon Sep 17 00:00:00 2001 From: zwang Date: Tue, 21 Jul 2026 00:13:20 +0800 Subject: [PATCH 01/25] add the dual fixing probing propagator --- highs/mip/HighsDomain.cpp | 73 +++++++++++++++++++++++++++++++++++++++ highs/mip/HighsDomain.h | 51 +++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) diff --git a/highs/mip/HighsDomain.cpp b/highs/mip/HighsDomain.cpp index f09d0f5b817..4998cbabf3b 100644 --- a/highs/mip/HighsDomain.cpp +++ b/highs/mip/HighsDomain.cpp @@ -75,6 +75,7 @@ HighsDomain::HighsDomain(HighsMipSolver& mipsolver) : mipsolver(&mipsolver) { changedcols_.reserve(mipsolver.numCol()); infeasible_reason = Reason::unspecified(); infeasible_ = false; + dfprobingPropagation.domain = this; } void HighsDomain::addCutpool(HighsCutPool& cutpool) { @@ -637,6 +638,78 @@ void HighsDomain::CutpoolPropagation::updateActivityUbChange( } } +HighsDomain::DualfixingProbingPropagation::DualfixingProbingPropagation(const DualfixingProbingPropagation& other) + : zeroCostVarsDirection_(other.zeroCostVarsDirection_), + colLowerLockNum_(other.colLowerLockNum_), + colUpperLockNum_(other.colUpperLockNum_), + redundantPropagateflags_(other.redundantPropagateflags_), + redundantPropagateinds_(other.redundantPropagateinds_), + zeroCostFixedVariables_(other.zeroCostFixedVariables_), + tmpColLoLock_(other.tmpColLoLock_), + tmpColUpLock_(other.tmpColUpLock_), + involvedVars(other.involvedVars), + indsVars(other.indsVars) {;} + + +void HighsDomain::DualfixingProbingPropagation::recomputeLocks() { + redundantPropagateflags_.assign(2 * mipsolver->numRow(), false); + redundantPropagateinds_.clear(); + redundantPropagateinds_.reserve(2 * mipsolver->numRow()); + zeroCostFixedVariables_.clear(); + zeroCostFixedVariables_.reserve(2 * mipsolver->numCol()); + + tmpColLoLock_.assign(mipsolver->numCol(), 0); + tmpColUpLock_.assign(mipsolver->numCol(), 0); + + involvedVars.clear(); + involvedVars.reserve(mipsolver->numCol()); + indsVars.assign(mipsolver->numCol(), false); +} + +bool HighsDomain::DualfixingProbingPropagation::isUpperRedundant(HighsInt row) { + bool upperRedundant; + + upperRedundant = (mipsolver->model_->row_upper_[row] != kHighsInf) && + (domain->getMaxActivity(row) <= mipsolver->model_->row_upper_[row] + mipsolver->mipdata_->feastol); + return upperRedundant; +} + +bool HighsDomain::DualfixingProbingPropagation::isLowerRedundant(HighsInt row) { + bool lowerRedundant; + + lowerRedundant = (mipsolver->model_->row_lower_[row] != -kHighsInf) && + (domain->getMinActivity(row) >= mipsolver->model_->row_lower_[row] - mipsolver->mipdata_->feastol); + return lowerRedundant; +} + +void HighsDomain::DualfixingProbingPropagation::markRedundantPropagate(HighsInt row, bool isUpper) { + assert(row < (int)mipsolver->numRow()); + if (mipsolver->submip) + return; + const HighsInt pos = 2 * row + isUpper; + if (!redundantPropagateflags_[pos]) { + if (isUpper) { + const bool upperRedundant = isUpperRedundant(row); + if (upperRedundant) { + redundantPropagateinds_.push_back(pos); + redundantPropagateflags_[pos] = 1; + } + } + else { + const bool lowerRedundant = isLowerRedundant(row); + if (lowerRedundant) { + redundantPropagateinds_.push_back(pos); + redundantPropagateflags_[pos] = 1; + } + } + } +} + +void HighsDomain::DualfixingProbingPropagation::propagate() { + mipsolver = domain->mipsolver; + +} + namespace highs { template <> struct RbTreeTraits< diff --git a/highs/mip/HighsDomain.h b/highs/mip/HighsDomain.h index e69ab43c8c9..3e20aee1363 100644 --- a/highs/mip/HighsDomain.h +++ b/highs/mip/HighsDomain.h @@ -235,6 +235,53 @@ class HighsDomain { void propagateConflict(HighsInt conflict); }; + struct DualfixingProbingPropagation { + HighsDomain* domain; + HighsMipSolver* mipsolver; + std::vector zeroCostVarsDirection_; + vector colLowerLockNum_; + vector colUpperLockNum_; + // row lower and upper, length = 2 * rownum + std::vector redundantPropagateflags_; + std::vector redundantPropagateinds_; + std::vector> zeroCostFixedVariables_; + HighsInt probingStatusSide = 0; + bool startZeroCostFixing; + + std::vector tmpColLoLock_; + std::vector tmpColUpLock_; + std::vector involvedVars; + std::vector indsVars; + + void clearInvolved(HighsInt start) { + for (const auto x : involvedVars) + indsVars[x] = false; + involvedVars.clear(); + } + + void clearRedundant(); + + + + DualfixingProbingPropagation() {}; + + DualfixingProbingPropagation(HighsDomain* domain) : domain(domain) {}; + + DualfixingProbingPropagation(const DualfixingProbingPropagation& other); + + DualfixingProbingPropagation& operator=(const DualfixingProbingPropagation& other); + + ~DualfixingProbingPropagation(); + + void recomputeLocks(); + bool isUpperRedundant(HighsInt row); + bool isLowerRedundant(HighsInt row); + void markRedundantPropagate(HighsInt row, bool isUpper); + + void propagate(); + + }; + private: struct ObjectivePropagation { HighsDomain* domain = nullptr; @@ -320,6 +367,7 @@ class HighsDomain { private: std::deque cutpoolpropagation; std::deque conflictPoolPropagation; + DualfixingProbingPropagation dfprobingPropagation; bool infeasible_ = false; Reason infeasible_reason; @@ -370,6 +418,7 @@ class HighsDomain { mipsolver(other.mipsolver), cutpoolpropagation(other.cutpoolpropagation), conflictPoolPropagation(other.conflictPoolPropagation), + dfprobingPropagation(other.dfprobingPropagation), infeasible_(other.infeasible_), infeasible_reason(other.infeasible_reason), infeasible_pos(other.infeasible_pos), @@ -383,6 +432,7 @@ class HighsDomain { for (ConflictPoolPropagation& conflictprop : conflictPoolPropagation) conflictprop.domain = this; if (objProp_.domain) objProp_.domain = this; + dfprobingPropagation.domain = this; } HighsDomain& operator=(const HighsDomain& other) { @@ -414,6 +464,7 @@ class HighsDomain { for (ConflictPoolPropagation& conflictprop : conflictPoolPropagation) conflictprop.domain = this; if (objProp_.domain) objProp_.domain = this; + dfprobingPropagation.domain = this; return *this; } From 7ee938f19129b25b82896c5b990a3305b47b0038 Mon Sep 17 00:00:00 2001 From: zwang Date: Thu, 23 Jul 2026 16:07:31 +0800 Subject: [PATCH 02/25] add main logic of dual fixing augmented probing --- highs/mip/HighsDomain.cpp | 369 +++++++++++++++++++++++++++++++++----- highs/mip/HighsDomain.h | 91 +++++++--- 2 files changed, 396 insertions(+), 64 deletions(-) diff --git a/highs/mip/HighsDomain.cpp b/highs/mip/HighsDomain.cpp index 4998cbabf3b..26c155deefb 100644 --- a/highs/mip/HighsDomain.cpp +++ b/highs/mip/HighsDomain.cpp @@ -639,77 +639,342 @@ void HighsDomain::CutpoolPropagation::updateActivityUbChange( } HighsDomain::DualfixingProbingPropagation::DualfixingProbingPropagation(const DualfixingProbingPropagation& other) - : zeroCostVarsDirection_(other.zeroCostVarsDirection_), - colLowerLockNum_(other.colLowerLockNum_), - colUpperLockNum_(other.colUpperLockNum_), - redundantPropagateflags_(other.redundantPropagateflags_), + : redundantPropagateflags_(other.redundantPropagateflags_), redundantPropagateinds_(other.redundantPropagateinds_), + zeroCostVarsDirection_(other.zeroCostVarsDirection_), zeroCostFixedVariables_(other.zeroCostFixedVariables_), - tmpColLoLock_(other.tmpColLoLock_), - tmpColUpLock_(other.tmpColUpLock_), - involvedVars(other.involvedVars), - indsVars(other.indsVars) {;} - + colLowerLockOriginal_(other.colLowerLockOriginal_), + colUpperLockOriginal_(other.colUpperLockOriginal_), + colLowerLockReduced_(other.colLowerLockReduced_), + colUpperLockReduced_(other.colUpperLockReduced_), + candidatesVec_(other.candidatesVec_), + candidatesFlag_(other.candidatesFlag_) {;} void HighsDomain::DualfixingProbingPropagation::recomputeLocks() { + if (!isEnabled()) + return; + + mipsolver = domain->mipsolver; redundantPropagateflags_.assign(2 * mipsolver->numRow(), false); redundantPropagateinds_.clear(); redundantPropagateinds_.reserve(2 * mipsolver->numRow()); + zeroCostVarsDirection_.assign(2 * mipsolver->numCol(), FIXDIRECTION_NOT_DECIDED); zeroCostFixedVariables_.clear(); zeroCostFixedVariables_.reserve(2 * mipsolver->numCol()); - tmpColLoLock_.assign(mipsolver->numCol(), 0); - tmpColUpLock_.assign(mipsolver->numCol(), 0); + startZeroCostFixing_ = false; + previousSize_ = 0; - involvedVars.clear(); - involvedVars.reserve(mipsolver->numCol()); - indsVars.assign(mipsolver->numCol(), false); + colLowerLockOriginal_.assign(mipsolver->numCol(), 0); + colUpperLockOriginal_.assign(mipsolver->numCol(), 0); + colLowerLockReduced_.assign(mipsolver->numCol(), 0); + colUpperLockReduced_.assign(mipsolver->numCol(), 0); + + candidatesVec_.clear(); + candidatesVec_.reserve(mipsolver->numCol()); + candidatesFlag_.assign(mipsolver->numCol(), false); } -bool HighsDomain::DualfixingProbingPropagation::isUpperRedundant(HighsInt row) { - bool upperRedundant; +void HighsDomain::DualfixingProbingPropagation::updateRhsRedundant(HighsInt row) { + if (!isEnabled()) + return; - upperRedundant = (mipsolver->model_->row_upper_[row] != kHighsInf) && - (domain->getMaxActivity(row) <= mipsolver->model_->row_upper_[row] + mipsolver->mipdata_->feastol); - return upperRedundant; + if (domain->activitymaxinf_[row] != 0 || redundantPropagateflags_[2 * row + 1] || mipsolver->model_->row_upper_[row] == kHighsInf) + return; + + if (domain->getMaxActivity(row) <= mipsolver->model_->row_upper_[row] + mipsolver->mipdata_->feastol) { + redundantPropagateinds_.push_back(2 * row + 1); + redundantPropagateflags_[2 * row + 1] = 1; + } } -bool HighsDomain::DualfixingProbingPropagation::isLowerRedundant(HighsInt row) { - bool lowerRedundant; +void HighsDomain::DualfixingProbingPropagation::updateLhsRedundant(HighsInt row) { + if (!isEnabled()) + return; + + if (domain->activitymininf_[row] != 0 || redundantPropagateflags_[2 * row] || mipsolver->model_->row_lower_[row] == -kHighsInf) + return; - lowerRedundant = (mipsolver->model_->row_lower_[row] != -kHighsInf) && - (domain->getMinActivity(row) >= mipsolver->model_->row_lower_[row] - mipsolver->mipdata_->feastol); - return lowerRedundant; + if (domain->getMinActivity(row) >= mipsolver->model_->row_lower_[row] - mipsolver->mipdata_->feastol) { + redundantPropagateinds_.push_back(2 * row); + redundantPropagateflags_[2 * row] = 1; + } } -void HighsDomain::DualfixingProbingPropagation::markRedundantPropagate(HighsInt row, bool isUpper) { - assert(row < (int)mipsolver->numRow()); - if (mipsolver->submip) + +void HighsDomain::DualfixingProbingPropagation::propagate() { + // The boolean variable ``startZeroCostFixing_'' is used to flag if we allow variables with zero cost objective coefficients can be fixed in domain propagation. + // The process of domain propagtion in probing is executed in two phases: + // Phase 1: Apply classic domain propagation, and additionally fix variables with nonzero objective coefficients using dual fixing + // Phase 2: Apply classic domain propagation, and additionally fix variables (including those with zero objective coefficients) using dual fixing + // In Phase 1, ``startZeroCostFixing_'' is set to be ``false'' to exclude variable with zero objective coefficients. + // In Phase 2, ``startZeroCostFixing_'' is set to be ``true''. + // Note that + // (1) For all the bound changes in Phase 1, reductions deduced from them are valid for all optimal solutions; + // (2) For the bound changes in Phase 2, reductions deduced from them can only be used to derive global valid reductions (i.e., variable fixing, global bound tightening, variable substitution). + if (!isEnabled()) return; - const HighsInt pos = 2 * row + isUpper; - if (!redundantPropagateflags_[pos]) { - if (isUpper) { - const bool upperRedundant = isUpperRedundant(row); - if (upperRedundant) { - redundantPropagateinds_.push_back(pos); - redundantPropagateflags_[pos] = 1; + + assert(candidatesVec_.empty()); + vector domainchangeProbing; + + // tool lambda functions + auto addToCandidate = [&](HighsInt k) { + // std::cout << "k = " << k << std::endl; + if (candidatesFlag_[k]) + return; + else { + candidatesVec_.push_back(k); + candidatesFlag_[k] = true; + } + }; + + auto checkVariableLowerLock = [&](HighsInt iCol) { + auto model = mipsolver->model_; + if (ableToFixToLb(iCol)) { + for (HighsInt k = model->a_matrix_.start_[iCol]; k < model->a_matrix_.start_[iCol + 1]; k ++) { + const HighsInt iRow = model->a_matrix_.index_[k]; + const double iValue = model->a_matrix_.value_[k]; + const double blower = model->row_lower_[iRow], bupper = model->row_upper_[iRow]; + const bool lhsOk = iValue > 0 && domain->getMinActivity(iRow) >= blower - domain->feastol(); + const bool rhsOk = iValue < 0 && domain->getMaxActivity(iRow) <= bupper + domain->feastol(); + if (!lhsOk && !rhsOk) { + std::cout << "Lower lock: variable " << iCol << " at row = " << iRow << " coef = " << iValue + << " not redundant at constraint " << iRow << ", minact = " << domain->getMinActivity(iRow) << ", maxact = " << domain->getMaxActivity(iRow) + << " lhs = " << blower << " rhs = " << bupper << std::endl; + } } } - else { - const bool lowerRedundant = isLowerRedundant(row); - if (lowerRedundant) { - redundantPropagateinds_.push_back(pos); - redundantPropagateflags_[pos] = 1; + }; + + auto checkVariableUpperLock = [&](HighsInt iCol) { + auto model = mipsolver->model_; + if (ableToFixToUb(iCol)) { + for (HighsInt k = model->a_matrix_.start_[iCol]; k < model->a_matrix_.start_[iCol + 1]; k ++) { + const HighsInt iRow = model->a_matrix_.index_[k]; + const double iValue = model->a_matrix_.value_[k]; + const double blower = model->row_lower_[iRow], bupper = model->row_upper_[iRow]; + const bool lhsOk = iValue < 0 && domain->getMinActivity(iRow) >= blower - domain->feastol(); + const bool rhsOk = iValue > 0 && domain->getMaxActivity(iRow) <= bupper + domain->feastol(); + if (!lhsOk && !rhsOk) { + std::cout << "Upper lock: variable " << iCol << " at row = " << iRow << " coef = " << iValue + << " not redundant at constraint " << iRow << ", minact = " << domain->getMinActivity(iRow) << ", maxact = " << domain->getMaxActivity(iRow) + << " lhs = " << blower << " rhs = " << bupper << std::endl; + } + } + } + }; + + auto addFixLower = [&](int iCol) { + HighsDomainChange* thisbchg = new HighsDomainChange; + thisbchg->column = iCol; + thisbchg->boundtype = HighsBoundType::kUpper; + thisbchg->boundval = domain->col_lower_[iCol]; + domainchangeProbing.push_back(thisbchg); + // std::cout << "fixing to lower " << iCol << std::endl; + }; + + auto addFixUpper = [&](int iCol) { + HighsDomainChange* thisbchg = new HighsDomainChange; + thisbchg->column = iCol; + thisbchg->boundtype = HighsBoundType::kLower; + thisbchg->boundval = domain->col_upper_[iCol]; + domainchangeProbing.push_back(thisbchg); + // std::cout << "fixing to upper " << iCol << std::endl; + }; + + auto collectFixLower = [&](int iCol) { + zeroCostFixedVariables_.emplace_back(iCol, FIXDIRECTION_LOWER_BOUND); + }; + + auto collectFixUpper = [&](int iCol) { + zeroCostFixedVariables_.emplace_back(iCol, FIXDIRECTION_UPPER_BOUND); + }; + + + + // get candidate + HighsInt maxLockLeft = redundantPropagateinds_.size() - previousSize_; + if (maxLockLeft == 0) + return; + for (; previousSize_ < redundantPropagateinds_.size(); ++ previousSize_, -- maxLockLeft) { + const HighsInt i = redundantPropagateinds_[previousSize_]; + const HighsInt iRow = i / 2; + assert(iRow < mipsolver->numRow()); + + if (i % 2 == 0) { // lower redundant + HighsInt rstart = mipsolver->mipdata_->ARstart_[iRow]; + HighsInt rend = mipsolver->mipdata_->ARstart_[iRow + 1]; + for (auto k = rstart; k < rend; ++ k) { + const HighsInt iCol = mipsolver->mipdata_->ARindex_[k]; + if (domain->isFixed(iCol)) + continue; + const double iValue = mipsolver->mipdata_->ARvalue_[k]; + const double cost = mipsolver->model_->col_cost_[iCol]; + + bool lowerNoInsert = colLowerLockReduced_[iCol] + maxLockLeft < colLowerLockOriginal_[iCol]; + bool upperNoInsert = colUpperLockReduced_[iCol] + maxLockLeft < colUpperLockOriginal_[iCol]; + + if (iValue > 0 && cost >= mipsolver->options_mip_->dual_feasibility_tolerance) { + colLowerLockReduced_[iCol] ++; + lowerNoInsert = lowerNoInsert && colLowerLockReduced_[iCol] + maxLockLeft < colLowerLockOriginal_[iCol]; + } + else if (iValue < 0 && cost <= mipsolver->options_mip_->dual_feasibility_tolerance) { + colUpperLockReduced_[iCol] ++; + upperNoInsert = upperNoInsert && colUpperLockReduced_[iCol] + maxLockLeft < colUpperLockOriginal_[iCol]; + } + + if (!lowerNoInsert || !upperNoInsert) + addToCandidate(iCol); + } + } + else { // upper redundant + HighsInt rstart = mipsolver->mipdata_->ARstart_[iRow]; + HighsInt rend = mipsolver->mipdata_->ARstart_[iRow + 1]; + for (auto k = rstart; k < rend; k++) { + const HighsInt iCol = mipsolver->mipdata_->ARindex_[k]; + if (domain->isFixed(iCol)) + continue; + const double iValue = mipsolver->mipdata_->ARvalue_[k]; + const double cost = mipsolver->model_->col_cost_[iCol]; + + bool lowerNoInsert = colLowerLockReduced_[iCol] + maxLockLeft < colLowerLockOriginal_[iCol]; + bool upperNoInsert = colUpperLockReduced_[iCol] + maxLockLeft < colUpperLockOriginal_[iCol]; + + if (iValue < 0 && cost >= mipsolver->options_mip_->dual_feasibility_tolerance) { + colLowerLockReduced_[iCol] ++; + lowerNoInsert = lowerNoInsert && colLowerLockReduced_[iCol] + maxLockLeft < colLowerLockOriginal_[iCol]; + } + else if (iValue > 0 && cost <= mipsolver->options_mip_->dual_feasibility_tolerance) { + colUpperLockReduced_[iCol] ++; + upperNoInsert = upperNoInsert && colUpperLockReduced_[iCol] + maxLockLeft < colUpperLockOriginal_[iCol]; + } + + if (!lowerNoInsert || !upperNoInsert) + addToCandidate(iCol); } } } -} -void HighsDomain::DualfixingProbingPropagation::propagate() { - mipsolver = domain->mipsolver; - + for (auto iCol : candidatesVec_) { + if (domain->isFixed(iCol)) + continue; + const bool canBeFixedToLower = colLowerLockReduced_[iCol] == colLowerLockOriginal_[iCol]; + const bool canBeFixedToUpper = colUpperLockReduced_[iCol] == colUpperLockOriginal_[iCol]; + if (!canBeFixedToLower && !canBeFixedToUpper) + continue; + + if (fabs(mipsolver->model_->col_cost_[iCol]) <= mipsolver->options_mip_->dual_feasibility_tolerance) { + if (startZeroCostFixing_) { + // not fixed before + if (zeroCostVarsDirection_[iCol] == FIXDIRECTION_NOT_DECIDED) { + // both directions are ok - depending on cost (no tolerance) + if (canBeFixedToLower && canBeFixedToUpper) { + if (mipsolver->model_->col_cost_[iCol] >= 0) { + addFixLower(iCol); + zeroCostVarsDirection_[iCol] = FIXDIRECTION_LOWER_BOUND; + } + else { + addFixUpper(iCol); + zeroCostVarsDirection_[iCol] = FIXDIRECTION_UPPER_BOUND; + } + } + // fix depending on the direction + else if (canBeFixedToLower) { + addFixLower(iCol); + zeroCostVarsDirection_[iCol] = FIXDIRECTION_LOWER_BOUND; + } + else if (canBeFixedToUpper) { + addFixUpper(iCol); + zeroCostVarsDirection_[iCol] = FIXDIRECTION_UPPER_BOUND; + } + } + // fix to lb + else if (zeroCostVarsDirection_[iCol] == FIXDIRECTION_LOWER_BOUND && canBeFixedToLower) + addFixLower(iCol); + // fix to ub + else if (zeroCostVarsDirection_[iCol] == FIXDIRECTION_UPPER_BOUND && canBeFixedToUpper) + addFixUpper(iCol); + + continue; + } + // do not perfrom zero cost variable fixing, just collect them and choose directions + else { + // not fixed before + if (zeroCostVarsDirection_[iCol] == FIXDIRECTION_NOT_DECIDED) { + // both directions are ok - depending on cost (no tolerance) + if (canBeFixedToLower && canBeFixedToUpper) { + if (mipsolver->model_->col_cost_[iCol] >= 0) { + collectFixLower(iCol); + zeroCostVarsDirection_[iCol] = FIXDIRECTION_LOWER_BOUND; + } + else { + collectFixUpper(iCol); + zeroCostVarsDirection_[iCol] = FIXDIRECTION_UPPER_BOUND; + } + } + else if (canBeFixedToLower) { // fix to lower and set its direction + collectFixLower(iCol); + zeroCostVarsDirection_[iCol] = FIXDIRECTION_LOWER_BOUND; + } + else if (canBeFixedToUpper) { + collectFixUpper(iCol); + zeroCostVarsDirection_[iCol] = FIXDIRECTION_UPPER_BOUND; + } + } + else if (zeroCostVarsDirection_[iCol] == FIXDIRECTION_UPPER_BOUND && canBeFixedToUpper) { // fix to upper + collectFixUpper(iCol); + } + else if (zeroCostVarsDirection_[iCol] == FIXDIRECTION_LOWER_BOUND && canBeFixedToLower) { // fix to lower + collectFixLower(iCol); + } + // we have collected this column + continue; + } + } + + + // if (mipsolver->model_->col_cost_[iCol] >= mipsolver->options_mip_->dual_feasibility_tolerance) { + if (mipsolver->model_->col_cost_[iCol] >= mipsolver->options_mip_->dual_feasibility_tolerance) { + if (canBeFixedToLower) { + checkVariableLowerLock(iCol); + addFixLower(iCol); + continue; + } + } + // if (mipsolver->model_->col_cost_[iCol] <= mipsolver->options_mip_->dual_feasibility_tolerance) { + if (mipsolver->model_->col_cost_[iCol] <= mipsolver->options_mip_->dual_feasibility_tolerance) { + if (canBeFixedToUpper) { + checkVariableUpperLock(iCol); + addFixUpper(iCol); + continue; + } + } + } + + // clear candidate info + for (const auto x : candidatesVec_) + candidatesFlag_[x] = false; + candidatesVec_.clear(); + + // change bound + size_t j = 0; + for (; j != domainchangeProbing.size() && !domain->infeasible_; ++ j) { + domain->changeBound(*domainchangeProbing[j], Reason::unspecified()); + delete domainchangeProbing[j]; + } + + for (j ++; j < domainchangeProbing.size(); ++ j) { + assert(domain->infeasible); + delete domainchangeProbing[j]; + } + + // record the current number of redundant constraints. + previousSize_ = redundantPropagateinds_.size(); } + + namespace highs { template <> struct RbTreeTraits< @@ -1639,6 +1904,9 @@ void HighsDomain::updateActivityLbChange(HighsInt col, double oldbound, mip->row_lower_[mip->a_matrix_.index_[i]] != -kHighsInf && mip->row_upper_[mip->a_matrix_.index_[i]] == kHighsInf) updateRedundantRows(mip->a_matrix_.index_[i]); + + if (newbound >= oldbound + mipsolver->mipdata_->feastol) + dfprobingPropagation.updateLhsRedundant(mip->a_matrix_.index_[i]); if (deltamin <= 0) { updateThresholdLbChange(col, newbound, mip->a_matrix_.value_[i], @@ -1689,6 +1957,9 @@ void HighsDomain::updateActivityLbChange(HighsInt col, double oldbound, mip->row_upper_[mip->a_matrix_.index_[i]] != kHighsInf) updateRedundantRows(mip->a_matrix_.index_[i]); + if (newbound >= oldbound + mipsolver->mipdata_->feastol) + dfprobingPropagation.updateRhsRedundant(mip->a_matrix_.index_[i]); + if (deltamax >= 0) { updateThresholdLbChange(col, newbound, mip->a_matrix_.value_[i], capacityThreshold_[mip->a_matrix_.index_[i]]); @@ -1806,6 +2077,9 @@ void HighsDomain::updateActivityUbChange(HighsInt col, double oldbound, mip->row_lower_[mip->a_matrix_.index_[i]] == -kHighsInf && mip->row_upper_[mip->a_matrix_.index_[i]] != kHighsInf) updateRedundantRows(mip->a_matrix_.index_[i]); + + if (newbound <= oldbound - mipsolver->mipdata_->feastol) + dfprobingPropagation.updateRhsRedundant(mip->a_matrix_.index_[i]); if (deltamax >= 0) { updateThresholdUbChange(col, newbound, mip->a_matrix_.value_[i], @@ -1858,6 +2132,9 @@ void HighsDomain::updateActivityUbChange(HighsInt col, double oldbound, mip->row_lower_[mip->a_matrix_.index_[i]] != -kHighsInf && mip->row_upper_[mip->a_matrix_.index_[i]] == kHighsInf) updateRedundantRows(mip->a_matrix_.index_[i]); + + if (newbound <= oldbound - mipsolver->mipdata_->feastol) + dfprobingPropagation.updateLhsRedundant(mip->a_matrix_.index_[i]); if (deltamin <= 0) { updateThresholdUbChange(col, newbound, mip->a_matrix_.value_[i], @@ -2452,6 +2729,9 @@ bool HighsDomain::propagate() { if (!conflictprop.propagateConflictInds_.empty()) return true; } + if (dfprobingPropagation.isActive()) + return true; + return false; }; @@ -2628,6 +2908,9 @@ bool HighsDomain::propagate() { propagateinds.clear(); } } + + if (dfprobingPropagation.isActive()) + dfprobingPropagation.propagate(); } return true; diff --git a/highs/mip/HighsDomain.h b/highs/mip/HighsDomain.h index 3e20aee1363..3bf3c0efac4 100644 --- a/highs/mip/HighsDomain.h +++ b/highs/mip/HighsDomain.h @@ -238,32 +238,79 @@ class HighsDomain { struct DualfixingProbingPropagation { HighsDomain* domain; HighsMipSolver* mipsolver; - std::vector zeroCostVarsDirection_; - vector colLowerLockNum_; - vector colUpperLockNum_; // row lower and upper, length = 2 * rownum std::vector redundantPropagateflags_; std::vector redundantPropagateinds_; + + enum DFPROBING_FIX_DIRECTION { + FIXDIRECTION_NOT_DECIDED = 0, + FIXDIRECTION_LOWER_BOUND = 1, + FIXDIRECTION_UPPER_BOUND = 2, + }; + std::vector zeroCostVarsDirection_; std::vector> zeroCostFixedVariables_; - HighsInt probingStatusSide = 0; - bool startZeroCostFixing; - - std::vector tmpColLoLock_; - std::vector tmpColUpLock_; - std::vector involvedVars; - std::vector indsVars; - - void clearInvolved(HighsInt start) { - for (const auto x : involvedVars) - indsVars[x] = false; - involvedVars.clear(); + bool startZeroCostFixing_; + + bool enabled_ = false; + size_t previousSize_; + + std::vector colLowerLockOriginal_; + std::vector colUpperLockOriginal_; + std::vector colLowerLockReduced_; + std::vector colUpperLockReduced_; + std::vector candidatesVec_; + std::vector candidatesFlag_; + + void enablePropagator() { + enabled_ = true; } - void clearRedundant(); + void disablePropagator() { + enabled_ = false; + } + bool isEnabled() { + return enabled_; + } + bool isActive() { + return enabled_ && redundantPropagateinds_.size() > previousSize_; + } + + void enableZeroObjFixing() { + startZeroCostFixing_ = true; + } - DualfixingProbingPropagation() {}; + void disableZeroObjFixing() { + startZeroCostFixing_ = false; + } + + bool ableToFixToLb(int col) { + return mipsolver->model_->col_cost_[col] >= -mipsolver->options_mip_->dual_feasibility_tolerance + && mipsolver->model_->col_lower_[col] > -kHighsInf; + } + + bool ableToFixToUb(int col) { + return mipsolver->model_->col_cost_[col] <= mipsolver->options_mip_->dual_feasibility_tolerance + && mipsolver->model_->col_upper_[col] < kHighsInf; + } + + + void clearRedundant() { + if (!redundantPropagateinds_.empty()) { // clear buffers + for (auto x : redundantPropagateinds_) + redundantPropagateflags_[x] = false; + + redundantPropagateinds_.clear(); + } + + for (size_t i = 0; i < redundantPropagateflags_.size(); ++ i) + assert(!redundantPropagateflags_[i]); + + zeroCostFixedVariables_.clear(); + } + + DualfixingProbingPropagation() {;}; DualfixingProbingPropagation(HighsDomain* domain) : domain(domain) {}; @@ -271,14 +318,16 @@ class HighsDomain { DualfixingProbingPropagation& operator=(const DualfixingProbingPropagation& other); - ~DualfixingProbingPropagation(); + ~DualfixingProbingPropagation() {;}; void recomputeLocks(); - bool isUpperRedundant(HighsInt row); - bool isLowerRedundant(HighsInt row); - void markRedundantPropagate(HighsInt row, bool isUpper); + void updateRhsRedundant(HighsInt row); + void updateLhsRedundant(HighsInt row); void propagate(); + + + }; From de053a8c1e1a458b0fbe8593112d550f61f021cb Mon Sep 17 00:00:00 2001 From: zwang Date: Thu, 23 Jul 2026 20:44:14 +0800 Subject: [PATCH 03/25] add main logic in HighsImplications --- highs/mip/HighsDomain.h | 15 ++++ highs/mip/HighsImplications.cpp | 131 +++++++++++++++++++++++++++++++- highs/mip/HighsImplications.h | 120 +++++++++++++++++++++++++++++ 3 files changed, 264 insertions(+), 2 deletions(-) diff --git a/highs/mip/HighsDomain.h b/highs/mip/HighsDomain.h index 3bf3c0efac4..b86cc50dcd3 100644 --- a/highs/mip/HighsDomain.h +++ b/highs/mip/HighsDomain.h @@ -250,6 +250,7 @@ class HighsDomain { std::vector zeroCostVarsDirection_; std::vector> zeroCostFixedVariables_; bool startZeroCostFixing_; + size_t zeroCostStartPos_; bool enabled_ = false; size_t previousSize_; @@ -277,6 +278,14 @@ class HighsDomain { return enabled_ && redundantPropagateinds_.size() > previousSize_; } + void setZeroCostFixingPosition(HighsInt v) { + zeroCostStartPos_ = v; + } + + size_t getZeroCostFixingPosition() { + return zeroCostStartPos_; + } + void enableZeroObjFixing() { startZeroCostFixing_ = true; } @@ -448,6 +457,8 @@ class HighsDomain { std::vector col_lower_; std::vector col_upper_; + bool inProbing_ = false; + HighsDomain(HighsMipSolver& mipsolver); HighsDomain(const HighsDomain& other) @@ -769,6 +780,10 @@ class HighsDomain { void setRecordRedundantRows(bool val) { recordRedundantRows_ = val; }; bool isRedundantRow(HighsInt row) const; + + DualfixingProbingPropagation& getDfProbingPropagation() { + return dfprobingPropagation; + } }; #endif diff --git a/highs/mip/HighsImplications.cpp b/highs/mip/HighsImplications.cpp index 906c2349c4e..4a6884503c3 100644 --- a/highs/mip/HighsImplications.cpp +++ b/highs/mip/HighsImplications.cpp @@ -27,6 +27,8 @@ bool HighsImplications::computeImplications(HighsInt col, bool val) { const auto& domchgreason = globaldomain.getDomainChangeReason(); size_t changedend = globaldomain.getChangedCols().size(); + globaldomain.getDfProbingPropagation().clearRedundant(); + HighsInt stackimplicstart = domchgstack.size() + 1; HighsInt numImplications = -stackimplicstart; if (val) @@ -61,7 +63,11 @@ bool HighsImplications::computeImplications(HighsInt col, bool val) { if (isInfeasible(col, val)) return true; + if (globaldomain.inProbing_) + globaldomain.getDfProbingPropagation().enablePropagator(); globaldomain.propagate(); + if (globaldomain.inProbing_) + globaldomain.getDfProbingPropagation().disablePropagator(); if (isInfeasible(col, val)) return true; @@ -73,13 +79,27 @@ bool HighsImplications::computeImplications(HighsInt col, bool val) { std::vector implics; implics.reserve(numImplications); + // data structure to cache implications for non-binary variables + std::vector implics_tentative; + implics_tentative.reserve(numImplications); + std::vector isTentative(numImplications, false); + HighsInt numEntries = mipsolver.mipdata_->cliquetable.getNumEntries(); HighsInt maxEntries = 100000 + mipsolver.numNonzero(); + const HighsInt tentativeStart = globaldomain.inProbing_ ? globaldomain.getDfProbingPropagation().getZeroCostFixingPosition() : kHighsIInf32; + if (globaldomain.inProbing_) { + implics_tentative.assign(domchgstack.begin() + stackimplicstart, domchgstack.begin() + stackimplicend); + for (int i = 0; i < stackimplicend - stackimplicstart; i ++) + isTentative[i] = (i + stackimplicstart >= tentativeStart); + } for (HighsInt i = stackimplicstart; i < stackimplicend; ++i) { if (domchgreason[i].type == HighsDomain::Reason::kCliqueTable && ((domchgreason[i].index >> 1) == col || numEntries >= maxEntries)) continue; + + if (i >= tentativeStart) // cache tentative implications + continue; implics.push_back(domchgstack[i]); } @@ -90,6 +110,18 @@ bool HighsImplications::computeImplications(HighsInt col, bool val) { // backtrack doBacktrack(changedend); + if (!implics_tentative.empty()) { + // add the implications of binary variables to the clique table + auto binstart_tmp = std::partition(implics_tentative.begin(), implics_tentative.end(), + [&](const HighsDomainChange& a) { + return !globaldomain.isBinary(a.column); + }); + // Store the tentative bound changes (fixing) of binary variables separately + for (auto i = binstart_tmp; i != implics_tentative.end(); ++ i) + cacheTmpCliques(val, *i); + implics_tentative.erase(binstart_tmp, implics_tentative.end()); + } + // add the implications of binary variables to the clique table auto binstart = std::partition(implics.begin(), implics.end(), [&](const HighsDomainChange& a) { @@ -147,6 +179,11 @@ bool HighsImplications::computeImplications(HighsInt col, bool val) { implications[loc].implics = std::move(implics); this->numImplications += implications[loc].implics.size(); } + if (!implics_tentative.empty()) { + pdqsort(implics_tentative.begin(), implics_tentative.end()); + implications[loc].implics_tentative = std::move(implics_tentative); + implications[loc].isTentative = std::move(isTentative); + } return false; } @@ -300,6 +337,11 @@ bool HighsImplications::runProbing(HighsInt col, HighsInt& numReductions) { if (globaldomain.isBinary(col) && !implicationsCached(col, 1) && !implicationsCached(col, 0) && mipsolver.mipdata_->cliquetable.getSubstitution(col) == nullptr) { + + // setup for dfprobingPropagation + clearCacheClique(); + globaldomain.getDfProbingPropagation().setZeroCostFixingPosition(kHighsIInf32); + bool infeasible = computeImplications(col, 1); if (globaldomain.infeasible()) return true; if (infeasible) return true; @@ -312,11 +354,87 @@ bool HighsImplications::runProbing(HighsInt col, HighsInt& numReductions) { if (mipsolver.mipdata_->cliquetable.getSubstitution(col) != nullptr) return true; + if (globaldomain.inProbing_ && !binaryInvolvedInds_.empty()) { + HighsCliqueTable& cliquetable = mipsolver.mipdata_->cliquetable; + HighsCliqueTable::CliqueVar clique[2]; + bool haveReduction; + do + { + haveReduction = false; + // Loop over binary variables that are tighened at least once + for (auto k : binaryInvolvedInds_) { + // Skip non-binary variables (being fixed now) or those can be substituted by other binary variables + if (!globaldomain.isBinary(k) || colsubstituted[k]) + continue; + // Return if the whole problem is infeasible + if (globaldomain.infeasible()) + return true; + // Get the information how x[k] is fixed in probing on x[col] = 0 and x[col] = 1 + // For the meaning of ``data'', please see lines 71-82 in HighsImplications.h + uint8_t data = binaryInvolvedFlags_[k]; + if (data == 0) // flag for no reduction + continue; + + if (data == binaryFixType::kGlobalLower) { // x[k] is fixed at 0 under both x[col] = 0 and x[col] = 1 + // fix x[k] = 0 by adding two cliques (i.e., these two cliques should be added in computeImplications() to derive global reductions) + clique[0] = HighsCliqueTable::CliqueVar(col, 0); + clique[1] = HighsCliqueTable::CliqueVar(k, 1); + cliquetable.addClique(mipsolver, &clique[0], 2); + clique[0] = HighsCliqueTable::CliqueVar(col, 1); + clique[1] = HighsCliqueTable::CliqueVar(k, 1); + cliquetable.addClique(mipsolver, &clique[0], 2); + data = 0; + haveReduction = true; + } + else if (data == binaryFixType::kGlobalUpper) { // x[k] is fixed at 1 under both x[col] = 0 and x[col] = 1 + // fix x[k] = 1 by adding two cliques (i.e., these two cliques should be added in computeImplications() to derive global reductions) + clique[0] = HighsCliqueTable::CliqueVar(col, 0); + clique[1] = HighsCliqueTable::CliqueVar(k, 0); + cliquetable.addClique(mipsolver, &clique[0], 2); + clique[0] = HighsCliqueTable::CliqueVar(col, 1); + clique[1] = HighsCliqueTable::CliqueVar(k, 0); + cliquetable.addClique(mipsolver, &clique[0], 2); + data = 0; + haveReduction = true; + } + else if (data == binaryFixType::kSubstituteComplement) { // x[k] is fixed at 0 under x[col] = 1, and is fixed at 1 under x[col] = 0; this makes x[col] + x[k] = 1 + // Adding two cliques (i.e., these two cliques should be added in computeImplications() to derive global reductions) + clique[0] = HighsCliqueTable::CliqueVar(col, 1); + clique[1] = HighsCliqueTable::CliqueVar(k, 1); + cliquetable.addClique(mipsolver, &clique[0], 2); + clique[0] = HighsCliqueTable::CliqueVar(col, 0); + clique[1] = HighsCliqueTable::CliqueVar(k, 0); + cliquetable.addClique(mipsolver, &clique[0], 2); + data = 0; + haveReduction = true; + } + else if (data == binaryFixType::kSubstituteEqual) { // x[k] is fixed at 0 under x[col] = 0, and is fixed at 1 under x[col] = 1; this makes x[col] = x[k] + // Adding two cliques (i.e., these two cliques should be added in computeImplications() to derive global reductions) + clique[0] = HighsCliqueTable::CliqueVar(col, 1); + clique[1] = HighsCliqueTable::CliqueVar(k, 0); + cliquetable.addClique(mipsolver, &clique[0], 2); + clique[0] = HighsCliqueTable::CliqueVar(col, 0); + clique[1] = HighsCliqueTable::CliqueVar(k, 1); + cliquetable.addClique(mipsolver, &clique[0], 2); + data = 0; + haveReduction = true; + } + } + } while (haveReduction); + + // clear the tentative bound changes for binary variables obtained from probing on x[col] + clearCacheClique(); + } + // analyze implications + // also include the bound changes of non-binary variables here, to derive tighter global bounds and variable substitutions + const bool haveTentativeImplics_zero = !implications[2 * col].implics_tentative.empty(); + const bool haveTentativeImplics_one = !implications[2 * col + 1].implics_tentative.empty(); + const std::vector& implicsdown = - getImplications(col, 0, infeasible); + haveTentativeImplics_zero ? getImplications_tentative(col, 0) : getImplications(col, 0, infeasible); const std::vector& implicsup = - getImplications(col, 1, infeasible); + haveTentativeImplics_one ? getImplications_tentative(col, 1) : getImplications(col, 1, infeasible); HighsInt nimplicsdown = implicsdown.size(); HighsInt nimplicsup = implicsup.size(); HighsInt u = 0; @@ -382,6 +500,15 @@ bool HighsImplications::runProbing(HighsInt col, HighsInt& numReductions) { } } + if (haveTentativeImplics_zero) { + implications[2 * col].implics_tentative.clear(); + implications[2 * col].isTentative.clear(); + } + if (haveTentativeImplics_one) { + implications[2 * col + 1].implics_tentative.clear(); + implications[2 * col + 1].isTentative.clear(); + } + return true; } diff --git a/highs/mip/HighsImplications.h b/highs/mip/HighsImplications.h index a82cc1d1a9b..7e88f431c2e 100644 --- a/highs/mip/HighsImplications.h +++ b/highs/mip/HighsImplications.h @@ -25,6 +25,15 @@ class HighsImplications { struct Implics { std::vector implics; + /* the "tentative" implications. + A implication of type x_j \ge (\ell^1_j - \ell^0_j) x_k + \ell^0_j is called "tentative", if + (1) c_j = 0 + (2) x_j is fixed by applying dual fixing in probing + These implications can only be used to perform globally valid reductions. + Therefore, special treatment is required. + */ + std::vector implics_tentative; + std::vector isTentative; bool computed = false; }; std::vector implications; @@ -57,6 +66,30 @@ class HighsImplications { const HighsMipSolver& mipsolver; std::vector substitutions; std::vector colsubstituted; + + // if a binary variable x_j is: (1) c_j = 0 (2) x_j is fixed by applying dual fixing in probing + std::vector binaryInvolvedInds_; + enum binaryFixType { + kNoReduction = 0b0000, + kGlobalLower = 0b1010, + kGlobalUpper = 0b0101, + kSubstituteComplement = 0b1001, + kSubstituteEqual = 0b0110, + }; + /* + Possible values for binaryInvolvedFlags_ + 0 (0000, kNoReduction): Not involved + 2 (0010): fixed to 0 in second side probing + 1 (0001): fixed to 1 in second side probing + 8 (1000): fixed to 0 in first side probing + 4 (0100): fixed to 1 in first side probing + 10(1010, kGlobalLower): fixed to 0 in both side probing (global fixing!) + 5 (0101, kGlobalUpper): fixed to 1 in both side probing (global fixing!) + 9 (1001, kSubstituteComplement): substitutation type 1 --- x1 + x2 = 1 + 6 (0110, kSubstituteEqual): substitutation type 2 --- x1 = x2 + */ + std::vector binaryInvolvedFlags_; + HighsImplications(const HighsMipSolver& mipsolver) : mipsolver(mipsolver) { HighsInt numcol = mipsolver.numCol(); implications.resize(2 * static_cast(numcol)); @@ -67,6 +100,9 @@ class HighsImplications { numImplications = 0; numVarBounds = 0; maxVarBounds = calcMaxVarBounds(numcol); + + binaryInvolvedInds_.reserve(numcol); + binaryInvolvedFlags_.assign(numcol, 0b0000); } std::function @@ -92,6 +128,9 @@ class HighsImplications { maxVarBounds = calcMaxVarBounds(numcol); nextCleanupCall = mipsolver.numNonzero(); + binaryInvolvedInds_.reserve(numcol); + binaryInvolvedFlags_.assign(numcol, 0b0000); + } constexpr static int64_t calcMaxVarBounds(HighsInt numcol) { @@ -115,6 +154,13 @@ class HighsImplications { return implications[loc].implics; } + // get the "tentative implications" w.r.t non-binary variables + const std::vector& getImplications_tentative(HighsInt col, bool val) { + HighsInt loc = 2 * col + val; + return implications[loc].implics_tentative; + } + + bool implicationsCached(HighsInt col, bool val) { HighsInt loc = 2 * col + val; return implications[loc].computed; @@ -192,6 +238,80 @@ class HighsImplications { bool& infeasible, bool allowBoundChanges = true) const; void applyImplications(HighsDomain& domain, HighsInt col, HighsInt val); + + // collect tentative binary implications + void cacheTmpCliques(bool val, const HighsDomainChange& bchg) { + const int iCol = bchg.column; + if (val == 0) { // probing x_k = 0 + if (bchg.boundtype == HighsBoundType::kLower) { // fixed to 1 + if (!isFixedTo1(val, iCol)) { + if (binaryInvolvedFlags_[iCol] == 0) + binaryInvolvedInds_.push_back(iCol); + binaryInvolvedFlags_[iCol] += 0b0001; // 0001 + } + } + else { // fixed to 0 + if (!isFixedTo0(val, iCol)) { + if (binaryInvolvedFlags_[iCol] == 0) + binaryInvolvedInds_.push_back(iCol); + binaryInvolvedFlags_[iCol] += 0b0010; // 0010 + } + } + } + else { + if (bchg.boundtype == HighsBoundType::kLower) { // fixed to 1 + if (!isFixedTo1(val, iCol)) { + if (binaryInvolvedFlags_[iCol] == 0) + binaryInvolvedInds_.push_back(iCol); + binaryInvolvedFlags_[iCol] += 0b0100; // 0100 + } + } + else { // fixed to 0 + if (!isFixedTo0(val, iCol)) { + if (binaryInvolvedFlags_[iCol] == 0) + binaryInvolvedInds_.push_back(iCol); + binaryInvolvedFlags_[iCol] += 0b1000; // 1000 + } + } + } + } + // clear tentative binary implications + void clearCacheClique() { + for (auto iCol : binaryInvolvedInds_) + binaryInvolvedFlags_[iCol] = binaryFixType::kNoReduction; + binaryInvolvedInds_.clear(); + } + // tools for cacheTmpCliques + bool isFixedTo0(bool val, HighsInt iCol) { + if (binaryInvolvedFlags_[iCol] == 0) + return false; + + uint8_t mask; + if (val == 0) { // x_k = 0, last two digits + mask = 1 << (1); + return (binaryInvolvedFlags_[iCol] & mask) != 0; + } + else { // x_k = 1, first two digits + mask = 1 << (3); + return (binaryInvolvedFlags_[iCol] & mask) != 0; + } + } + // tools for cacheTmpCliques + bool isFixedTo1(bool val, HighsInt iCol) { + if (binaryInvolvedFlags_[iCol] == 0) + return false; + + uint8_t mask; + if (val == 0) { // x_k = 0, last two digits + mask = 1; + return (binaryInvolvedFlags_[iCol] & mask) != 0; + } + else { // x_k = 1, first two digits + mask = 1 << (2); + return (binaryInvolvedFlags_[iCol] & mask) != 0; + } + } + }; #endif From 54d5a49bcbd2ca48070b57241d1bfcfb1ff35ba9 Mon Sep 17 00:00:00 2001 From: zwang Date: Thu, 23 Jul 2026 21:02:00 +0800 Subject: [PATCH 04/25] turn on dfprobing propagator in probing --- highs/mip/HighsDomain.cpp | 18 ++++++++++++++---- highs/mip/HighsImplications.cpp | 12 ++---------- highs/mip/HighsImplications.h | 1 - highs/presolve/HPresolve.cpp | 2 ++ 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/highs/mip/HighsDomain.cpp b/highs/mip/HighsDomain.cpp index 26c155deefb..16392f46cde 100644 --- a/highs/mip/HighsDomain.cpp +++ b/highs/mip/HighsDomain.cpp @@ -651,9 +651,6 @@ HighsDomain::DualfixingProbingPropagation::DualfixingProbingPropagation(const Du candidatesFlag_(other.candidatesFlag_) {;} void HighsDomain::DualfixingProbingPropagation::recomputeLocks() { - if (!isEnabled()) - return; - mipsolver = domain->mipsolver; redundantPropagateflags_.assign(2 * mipsolver->numRow(), false); redundantPropagateinds_.clear(); @@ -673,6 +670,19 @@ void HighsDomain::DualfixingProbingPropagation::recomputeLocks() { candidatesVec_.clear(); candidatesVec_.reserve(mipsolver->numCol()); candidatesFlag_.assign(mipsolver->numCol(), false); + + const auto model = mipsolver->model_; + for (HighsInt iCol = 0; iCol < model->a_matrix_.num_col_; iCol ++) { + for (HighsInt k = model->a_matrix_.start_[iCol]; k < model->a_matrix_.start_[iCol + 1]; k ++) { + const HighsInt iRow = model->a_matrix_.index_[k]; + const double iValue = model->a_matrix_.value_[k]; + const double lhs = model->row_lower_[iRow], rhs = model->row_upper_[iRow]; + if ((iValue > 0 && rhs != kHighsInf) || (iValue < 0 && lhs != -kHighsInf)) + colUpperLockOriginal_[iCol] ++; + if ((iValue > 0 && lhs != -kHighsInf) || (iValue < 0 && rhs != kHighsInf)) + colLowerLockOriginal_[iCol] ++; + } + } } void HighsDomain::DualfixingProbingPropagation::updateRhsRedundant(HighsInt row) { @@ -965,7 +975,7 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { } for (j ++; j < domainchangeProbing.size(); ++ j) { - assert(domain->infeasible); + assert(domain->infeasible_); delete domainchangeProbing[j]; } diff --git a/highs/mip/HighsImplications.cpp b/highs/mip/HighsImplications.cpp index 4a6884503c3..eed1394db96 100644 --- a/highs/mip/HighsImplications.cpp +++ b/highs/mip/HighsImplications.cpp @@ -82,7 +82,6 @@ bool HighsImplications::computeImplications(HighsInt col, bool val) { // data structure to cache implications for non-binary variables std::vector implics_tentative; implics_tentative.reserve(numImplications); - std::vector isTentative(numImplications, false); HighsInt numEntries = mipsolver.mipdata_->cliquetable.getNumEntries(); HighsInt maxEntries = 100000 + mipsolver.numNonzero(); @@ -90,8 +89,6 @@ bool HighsImplications::computeImplications(HighsInt col, bool val) { const HighsInt tentativeStart = globaldomain.inProbing_ ? globaldomain.getDfProbingPropagation().getZeroCostFixingPosition() : kHighsIInf32; if (globaldomain.inProbing_) { implics_tentative.assign(domchgstack.begin() + stackimplicstart, domchgstack.begin() + stackimplicend); - for (int i = 0; i < stackimplicend - stackimplicstart; i ++) - isTentative[i] = (i + stackimplicstart >= tentativeStart); } for (HighsInt i = stackimplicstart; i < stackimplicend; ++i) { if (domchgreason[i].type == HighsDomain::Reason::kCliqueTable && @@ -182,7 +179,6 @@ bool HighsImplications::computeImplications(HighsInt col, bool val) { if (!implics_tentative.empty()) { pdqsort(implics_tentative.begin(), implics_tentative.end()); implications[loc].implics_tentative = std::move(implics_tentative); - implications[loc].isTentative = std::move(isTentative); } return false; @@ -500,14 +496,10 @@ bool HighsImplications::runProbing(HighsInt col, HighsInt& numReductions) { } } - if (haveTentativeImplics_zero) { + if (haveTentativeImplics_zero) implications[2 * col].implics_tentative.clear(); - implications[2 * col].isTentative.clear(); - } - if (haveTentativeImplics_one) { + if (haveTentativeImplics_one) implications[2 * col + 1].implics_tentative.clear(); - implications[2 * col + 1].isTentative.clear(); - } return true; } diff --git a/highs/mip/HighsImplications.h b/highs/mip/HighsImplications.h index 7e88f431c2e..4e66b7b4bb6 100644 --- a/highs/mip/HighsImplications.h +++ b/highs/mip/HighsImplications.h @@ -33,7 +33,6 @@ class HighsImplications { Therefore, special treatment is required. */ std::vector implics_tentative; - std::vector isTentative; bool computed = false; }; std::vector implications; diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index ac5b83a74fa..4f88e85013b 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1799,7 +1799,9 @@ HPresolve::Result HPresolve::runProbing(HighsPostsolveStack& postsolve_stack) { HighsInt numBoundChgs = 0; HighsInt numNewCliques = -cliquetable.numCliques(); + domain.inProbing_ = true; const bool probing_result = implications.runProbing(i, numBoundChgs); + domain.inProbing_ = false; if (!probing_result) continue; probingContingent += numBoundChgs; numNewCliques += cliquetable.numCliques(); From bff422ffbde6dded65524c23ca67888b6e2bb338 Mon Sep 17 00:00:00 2001 From: zwang Date: Thu, 23 Jul 2026 23:23:14 +0800 Subject: [PATCH 05/25] add initialization --- highs/presolve/HPresolve.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index 4f88e85013b..f296ba75925 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1735,6 +1735,8 @@ HPresolve::Result HPresolve::runProbing(HighsPostsolveStack& postsolve_stack) { } }; + domain.getDfProbingPropagation().recomputeLocks(); + for (const auto& binvar : binaries) { // Count the binaries considered iBin++; From e9e0148ee97f7d8010fa478e2b7bc1ea6dea8754 Mon Sep 17 00:00:00 2001 From: zwang Date: Fri, 24 Jul 2026 16:07:33 +0800 Subject: [PATCH 06/25] clear lock number when propagation finishes --- highs/mip/HighsDomain.cpp | 18 ++++++++++++++---- highs/mip/HighsDomain.h | 12 ++++++++++-- highs/mip/HighsImplications.cpp | 4 ++-- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/highs/mip/HighsDomain.cpp b/highs/mip/HighsDomain.cpp index 16392f46cde..9af75dc4b5b 100644 --- a/highs/mip/HighsDomain.cpp +++ b/highs/mip/HighsDomain.cpp @@ -648,7 +648,8 @@ HighsDomain::DualfixingProbingPropagation::DualfixingProbingPropagation(const Du colLowerLockReduced_(other.colLowerLockReduced_), colUpperLockReduced_(other.colUpperLockReduced_), candidatesVec_(other.candidatesVec_), - candidatesFlag_(other.candidatesFlag_) {;} + candidatesFlag_(other.candidatesFlag_), + lockNeedClear_(other.lockNeedClear_) {;} void HighsDomain::DualfixingProbingPropagation::recomputeLocks() { mipsolver = domain->mipsolver; @@ -670,6 +671,7 @@ void HighsDomain::DualfixingProbingPropagation::recomputeLocks() { candidatesVec_.clear(); candidatesVec_.reserve(mipsolver->numCol()); candidatesFlag_.assign(mipsolver->numCol(), false); + lockNeedClear_.reserve(mipsolver->numCol()); const auto model = mipsolver->model_; for (HighsInt iCol = 0; iCol < model->a_matrix_.num_col_; iCol ++) { @@ -963,8 +965,10 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { } // clear candidate info - for (const auto x : candidatesVec_) - candidatesFlag_[x] = false; + for (const auto x : candidatesVec_) { + candidatesFlag_[x] = false; + lockNeedClear_.insert(x); + } candidatesVec_.clear(); // change bound @@ -2919,8 +2923,14 @@ bool HighsDomain::propagate() { } } - if (dfprobingPropagation.isActive()) + if (dfprobingPropagation.isActive()) { dfprobingPropagation.propagate(); + if (!havePropagationRows() && !dfprobingPropagation.isZeroObjFixingEnabled()) { + dfprobingPropagation.enableZeroObjFixing(); + dfprobingPropagation.setZeroCostFixingPosition(domchgstack_.size()); + dfprobingPropagation.propagate(); + } + } } return true; diff --git a/highs/mip/HighsDomain.h b/highs/mip/HighsDomain.h index b86cc50dcd3..288d72e8c3e 100644 --- a/highs/mip/HighsDomain.h +++ b/highs/mip/HighsDomain.h @@ -13,6 +13,7 @@ #include #include #include +#include #include "HighsPseudocost.h" #include "mip/HighsDomainChange.h" @@ -261,6 +262,7 @@ class HighsDomain { std::vector colUpperLockReduced_; std::vector candidatesVec_; std::vector candidatesFlag_; + std::unordered_set lockNeedClear_; void enablePropagator() { enabled_ = true; @@ -294,6 +296,10 @@ class HighsDomain { startZeroCostFixing_ = false; } + bool isZeroObjFixingEnabled() { + return startZeroCostFixing_; + } + bool ableToFixToLb(int col) { return mipsolver->model_->col_cost_[col] >= -mipsolver->options_mip_->dual_feasibility_tolerance && mipsolver->model_->col_lower_[col] > -kHighsInf; @@ -317,6 +323,10 @@ class HighsDomain { assert(!redundantPropagateflags_[i]); zeroCostFixedVariables_.clear(); + + for (const auto x : lockNeedClear_) + colLowerLockReduced_[x] = colUpperLockReduced_[x] = 0; + lockNeedClear_.clear(); } DualfixingProbingPropagation() {;}; @@ -325,8 +335,6 @@ class HighsDomain { DualfixingProbingPropagation(const DualfixingProbingPropagation& other); - DualfixingProbingPropagation& operator=(const DualfixingProbingPropagation& other); - ~DualfixingProbingPropagation() {;}; void recomputeLocks(); diff --git a/highs/mip/HighsImplications.cpp b/highs/mip/HighsImplications.cpp index eed1394db96..3e2ed8d8a74 100644 --- a/highs/mip/HighsImplications.cpp +++ b/highs/mip/HighsImplications.cpp @@ -28,6 +28,8 @@ bool HighsImplications::computeImplications(HighsInt col, bool val) { size_t changedend = globaldomain.getChangedCols().size(); globaldomain.getDfProbingPropagation().clearRedundant(); + if (globaldomain.inProbing_) + globaldomain.getDfProbingPropagation().enablePropagator(); HighsInt stackimplicstart = domchgstack.size() + 1; HighsInt numImplications = -stackimplicstart; @@ -63,8 +65,6 @@ bool HighsImplications::computeImplications(HighsInt col, bool val) { if (isInfeasible(col, val)) return true; - if (globaldomain.inProbing_) - globaldomain.getDfProbingPropagation().enablePropagator(); globaldomain.propagate(); if (globaldomain.inProbing_) globaldomain.getDfProbingPropagation().disablePropagator(); From 5035bf921e3607244bf2637b384c5296e08f09b7 Mon Sep 17 00:00:00 2001 From: zwang Date: Fri, 24 Jul 2026 16:29:43 +0800 Subject: [PATCH 07/25] add debug info --- highs/mip/HighsDomain.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/highs/mip/HighsDomain.cpp b/highs/mip/HighsDomain.cpp index da8f524d9cc..c412d929820 100644 --- a/highs/mip/HighsDomain.cpp +++ b/highs/mip/HighsDomain.cpp @@ -978,6 +978,8 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { delete domainchangeProbing[j]; } + std::cout << "#Bchg = " << j << std::endl; + for (j ++; j < domainchangeProbing.size(); ++ j) { assert(domain->infeasible_); delete domainchangeProbing[j]; @@ -2916,6 +2918,7 @@ bool HighsDomain::propagate() { } if (dfprobingPropagation.isActive()) { + std::cout << "Activated by nRedundantIndices = " << dfprobingPropagation.redundantPropagateinds_.size() << std::endl; dfprobingPropagation.propagate(); if (!havePropagationRows() && !dfprobingPropagation.isZeroObjFixingEnabled()) { dfprobingPropagation.enableZeroObjFixing(); From 2344dbe4a89340ccaa170322500ee8296b36d615 Mon Sep 17 00:00:00 2001 From: zwang Date: Sat, 25 Jul 2026 20:02:49 +0800 Subject: [PATCH 08/25] add debug output --- highs/mip/HighsDomain.cpp | 48 ++++++++++++++++++++++++++------------- highs/mip/HighsDomain.h | 18 +++++++-------- 2 files changed, 41 insertions(+), 25 deletions(-) diff --git a/highs/mip/HighsDomain.cpp b/highs/mip/HighsDomain.cpp index c412d929820..7aa3a4d20b7 100644 --- a/highs/mip/HighsDomain.cpp +++ b/highs/mip/HighsDomain.cpp @@ -639,8 +639,8 @@ void HighsDomain::CutpoolPropagation::updateActivityUbChange( } HighsDomain::DualfixingProbingPropagation::DualfixingProbingPropagation(const DualfixingProbingPropagation& other) - : redundantPropagateflags_(other.redundantPropagateflags_), - redundantPropagateinds_(other.redundantPropagateinds_), + : redundantPropagateFlag_(other.redundantPropagateFlag_), + redundantPropagateVec_(other.redundantPropagateVec_), zeroCostVarsDirection_(other.zeroCostVarsDirection_), zeroCostFixedVariables_(other.zeroCostFixedVariables_), colLowerLockOriginal_(other.colLowerLockOriginal_), @@ -653,9 +653,9 @@ HighsDomain::DualfixingProbingPropagation::DualfixingProbingPropagation(const Du void HighsDomain::DualfixingProbingPropagation::recomputeLocks() { mipsolver = domain->mipsolver; - redundantPropagateflags_.assign(2 * mipsolver->numRow(), false); - redundantPropagateinds_.clear(); - redundantPropagateinds_.reserve(2 * mipsolver->numRow()); + redundantPropagateFlag_.assign(2 * mipsolver->numRow(), false); + redundantPropagateVec_.clear(); + redundantPropagateVec_.reserve(2 * mipsolver->numRow()); zeroCostVarsDirection_.assign(2 * mipsolver->numCol(), FIXDIRECTION_NOT_DECIDED); zeroCostFixedVariables_.clear(); zeroCostFixedVariables_.reserve(2 * mipsolver->numCol()); @@ -691,12 +691,12 @@ void HighsDomain::DualfixingProbingPropagation::updateRhsRedundant(HighsInt row) if (!isEnabled()) return; - if (domain->activitymaxinf_[row] != 0 || redundantPropagateflags_[2 * row + 1] || mipsolver->model_->row_upper_[row] == kHighsInf) + if (domain->activitymaxinf_[row] != 0 || redundantPropagateFlag_[2 * row + 1] || mipsolver->model_->row_upper_[row] == kHighsInf) return; if (domain->getMaxActivity(row) <= mipsolver->model_->row_upper_[row] + mipsolver->mipdata_->feastol) { - redundantPropagateinds_.push_back(2 * row + 1); - redundantPropagateflags_[2 * row + 1] = 1; + redundantPropagateVec_.push_back(2 * row + 1); + redundantPropagateFlag_[2 * row + 1] = 1; } } @@ -704,12 +704,12 @@ void HighsDomain::DualfixingProbingPropagation::updateLhsRedundant(HighsInt row) if (!isEnabled()) return; - if (domain->activitymininf_[row] != 0 || redundantPropagateflags_[2 * row] || mipsolver->model_->row_lower_[row] == -kHighsInf) + if (domain->activitymininf_[row] != 0 || redundantPropagateFlag_[2 * row] || mipsolver->model_->row_lower_[row] == -kHighsInf) return; if (domain->getMinActivity(row) >= mipsolver->model_->row_lower_[row] - mipsolver->mipdata_->feastol) { - redundantPropagateinds_.push_back(2 * row); - redundantPropagateflags_[2 * row] = 1; + redundantPropagateVec_.push_back(2 * row); + redundantPropagateFlag_[2 * row] = 1; } } @@ -727,6 +727,17 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { if (!isEnabled()) return; +// #ifndef NDEBUG + for (const HighsInt x : redundantPropagateVec_) { + HighsInt iRow = x / 2; + bool isUpper = x % 2; + if (isUpper && domain->getMaxActivity(iRow) > mipsolver->model_->row_upper_[iRow] + domain->feastol()) + printf("Row %d not rhs redundant, maxAct = %f, rhs = %f.\n", iRow, domain->getMaxActivity(iRow), mipsolver->model_->row_upper_[iRow]); + if (!isUpper && domain->getMinActivity(iRow) < mipsolver->model_->row_lower_[iRow] - domain->feastol()) + printf("Row %d not lhs redundant, minAct = %f, lhs = %f.\n", iRow, domain->getMinActivity(iRow), mipsolver->model_->row_lower_[iRow]); + } +// #endif + assert(candidatesVec_.empty()); vector domainchangeProbing; @@ -754,6 +765,11 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { std::cout << "Lower lock: variable " << iCol << " at row = " << iRow << " coef = " << iValue << " not redundant at constraint " << iRow << ", minact = " << domain->getMinActivity(iRow) << ", maxact = " << domain->getMaxActivity(iRow) << " lhs = " << blower << " rhs = " << bupper << std::endl; + std::cout << "lock rows:\n"; + for (int kk = model->a_matrix_.start_[iCol]; kk < model->a_matrix_.start_[iCol + 1]; kk ++) { + std::cout << kk << " "; + } + std::cout << std::endl; } } } @@ -806,11 +822,11 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { // get candidate - HighsInt maxLockLeft = redundantPropagateinds_.size() - previousSize_; + HighsInt maxLockLeft = redundantPropagateVec_.size() - previousSize_; if (maxLockLeft == 0) return; - for (; previousSize_ < redundantPropagateinds_.size(); ++ previousSize_, -- maxLockLeft) { - const HighsInt i = redundantPropagateinds_[previousSize_]; + for (; previousSize_ < redundantPropagateVec_.size(); ++ previousSize_, -- maxLockLeft) { + const HighsInt i = redundantPropagateVec_[previousSize_]; const HighsInt iRow = i / 2; assert(iRow < mipsolver->numRow()); @@ -986,7 +1002,7 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { } // record the current number of redundant constraints. - previousSize_ = redundantPropagateinds_.size(); + previousSize_ = redundantPropagateVec_.size(); } @@ -2918,7 +2934,7 @@ bool HighsDomain::propagate() { } if (dfprobingPropagation.isActive()) { - std::cout << "Activated by nRedundantIndices = " << dfprobingPropagation.redundantPropagateinds_.size() << std::endl; + std::cout << "Activated by nRedundantIndices = " << dfprobingPropagation.redundantPropagateVec_.size() << std::endl; dfprobingPropagation.propagate(); if (!havePropagationRows() && !dfprobingPropagation.isZeroObjFixingEnabled()) { dfprobingPropagation.enableZeroObjFixing(); diff --git a/highs/mip/HighsDomain.h b/highs/mip/HighsDomain.h index cd3f517389a..d87b0bbdd36 100644 --- a/highs/mip/HighsDomain.h +++ b/highs/mip/HighsDomain.h @@ -240,8 +240,8 @@ class HighsDomain { HighsDomain* domain; HighsMipSolver* mipsolver; // row lower and upper, length = 2 * rownum - std::vector redundantPropagateflags_; - std::vector redundantPropagateinds_; + std::vector redundantPropagateFlag_; + std::vector redundantPropagateVec_; enum DFPROBING_FIX_DIRECTION { FIXDIRECTION_NOT_DECIDED = 0, @@ -277,7 +277,7 @@ class HighsDomain { } bool isActive() { - return enabled_ && redundantPropagateinds_.size() > previousSize_; + return enabled_ && redundantPropagateVec_.size() > previousSize_; } void setZeroCostFixingPosition(HighsInt v) { @@ -312,15 +312,15 @@ class HighsDomain { void clearRedundant() { - if (!redundantPropagateinds_.empty()) { // clear buffers - for (auto x : redundantPropagateinds_) - redundantPropagateflags_[x] = false; + if (!redundantPropagateVec_.empty()) { // clear buffers + for (auto x : redundantPropagateVec_) + redundantPropagateFlag_[x] = false; - redundantPropagateinds_.clear(); + redundantPropagateVec_.clear(); } - for (size_t i = 0; i < redundantPropagateflags_.size(); ++ i) - assert(!redundantPropagateflags_[i]); + for (size_t i = 0; i < redundantPropagateFlag_.size(); ++ i) + assert(!redundantPropagateFlag_[i]); zeroCostFixedVariables_.clear(); From ac5a926ad01cccab63ea07047d470b0f2cf6c105 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=85=86=E7=BB=B4?= Date: Sat, 25 Jul 2026 22:10:59 +0800 Subject: [PATCH 09/25] add to lockNeedClear --- highs/mip/HighsDomain.cpp | 18 +++++++++++------- highs/mip/HighsDomain.h | 1 + 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/highs/mip/HighsDomain.cpp b/highs/mip/HighsDomain.cpp index 7aa3a4d20b7..4bf9634cdc7 100644 --- a/highs/mip/HighsDomain.cpp +++ b/highs/mip/HighsDomain.cpp @@ -727,6 +727,7 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { if (!isEnabled()) return; + // printf("%f, %f\n", domain->getMaxActivity(1001), domain->getMinActivity(1001)); // #ifndef NDEBUG for (const HighsInt x : redundantPropagateVec_) { HighsInt iRow = x / 2; @@ -765,11 +766,11 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { std::cout << "Lower lock: variable " << iCol << " at row = " << iRow << " coef = " << iValue << " not redundant at constraint " << iRow << ", minact = " << domain->getMinActivity(iRow) << ", maxact = " << domain->getMaxActivity(iRow) << " lhs = " << blower << " rhs = " << bupper << std::endl; - std::cout << "lock rows:\n"; - for (int kk = model->a_matrix_.start_[iCol]; kk < model->a_matrix_.start_[iCol + 1]; kk ++) { - std::cout << kk << " "; - } - std::cout << std::endl; + // std::cout << "lock rows:\n"; + // for (int kk = model->a_matrix_.start_[iCol]; kk < model->a_matrix_.start_[iCol + 1]; kk ++) { + // std::cout << kk << " "; + // } + // std::cout << std::endl; } } } @@ -844,10 +845,12 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { bool upperNoInsert = colUpperLockReduced_[iCol] + maxLockLeft < colUpperLockOriginal_[iCol]; if (iValue > 0 && cost >= mipsolver->options_mip_->dual_feasibility_tolerance) { + lockNeedClear_.insert(iCol); colLowerLockReduced_[iCol] ++; lowerNoInsert = lowerNoInsert && colLowerLockReduced_[iCol] + maxLockLeft < colLowerLockOriginal_[iCol]; } else if (iValue < 0 && cost <= mipsolver->options_mip_->dual_feasibility_tolerance) { + lockNeedClear_.insert(iCol); colUpperLockReduced_[iCol] ++; upperNoInsert = upperNoInsert && colUpperLockReduced_[iCol] + maxLockLeft < colUpperLockOriginal_[iCol]; } @@ -870,10 +873,12 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { bool upperNoInsert = colUpperLockReduced_[iCol] + maxLockLeft < colUpperLockOriginal_[iCol]; if (iValue < 0 && cost >= mipsolver->options_mip_->dual_feasibility_tolerance) { + lockNeedClear_.insert(iCol); colLowerLockReduced_[iCol] ++; lowerNoInsert = lowerNoInsert && colLowerLockReduced_[iCol] + maxLockLeft < colLowerLockOriginal_[iCol]; } else if (iValue > 0 && cost <= mipsolver->options_mip_->dual_feasibility_tolerance) { + lockNeedClear_.insert(iCol); colUpperLockReduced_[iCol] ++; upperNoInsert = upperNoInsert && colUpperLockReduced_[iCol] + maxLockLeft < colUpperLockOriginal_[iCol]; } @@ -983,7 +988,6 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { // clear candidate info for (const auto x : candidatesVec_) { candidatesFlag_[x] = false; - lockNeedClear_.insert(x); } candidatesVec_.clear(); @@ -2933,7 +2937,7 @@ bool HighsDomain::propagate() { } } - if (dfprobingPropagation.isActive()) { + if (!infeasible_ && dfprobingPropagation.isActive()) { std::cout << "Activated by nRedundantIndices = " << dfprobingPropagation.redundantPropagateVec_.size() << std::endl; dfprobingPropagation.propagate(); if (!havePropagationRows() && !dfprobingPropagation.isZeroObjFixingEnabled()) { diff --git a/highs/mip/HighsDomain.h b/highs/mip/HighsDomain.h index d87b0bbdd36..36a2107ea9f 100644 --- a/highs/mip/HighsDomain.h +++ b/highs/mip/HighsDomain.h @@ -312,6 +312,7 @@ class HighsDomain { void clearRedundant() { + previousSize_ = 0; if (!redundantPropagateVec_.empty()) { // clear buffers for (auto x : redundantPropagateVec_) redundantPropagateFlag_[x] = false; From 5a282ccddef4001f85ff5f38938e7195e87c8a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=85=86=E7=BB=B4?= Date: Sun, 26 Jul 2026 12:08:16 +0800 Subject: [PATCH 10/25] disable dfprobing propagator when probing leads to infeasible binary fixing --- highs/mip/HighsImplications.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/highs/mip/HighsImplications.cpp b/highs/mip/HighsImplications.cpp index 3e2ed8d8a74..f7b957d2a75 100644 --- a/highs/mip/HighsImplications.cpp +++ b/highs/mip/HighsImplications.cpp @@ -57,6 +57,8 @@ bool HighsImplications::computeImplications(HighsInt col, bool val) { auto isInfeasible = [&](HighsInt col, bool val) { if (!globaldomain.infeasible()) return false; + if (globaldomain.inProbing_) + globaldomain.getDfProbingPropagation().disablePropagator(); storeLiftingOpportunities(col, val); doBacktrack(changedend); cliquetable.vertexInfeasible(globaldomain, col, val); From b7ca8e26026780273e8c295080fa24d58303fb4c Mon Sep 17 00:00:00 2001 From: zwang Date: Sun, 26 Jul 2026 12:17:13 +0800 Subject: [PATCH 11/25] cleanup --- highs/mip/HighsDomain.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/highs/mip/HighsDomain.cpp b/highs/mip/HighsDomain.cpp index 4bf9634cdc7..ef8b5cfdaba 100644 --- a/highs/mip/HighsDomain.cpp +++ b/highs/mip/HighsDomain.cpp @@ -998,7 +998,7 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { delete domainchangeProbing[j]; } - std::cout << "#Bchg = " << j << std::endl; + // std::cout << "#Bchg = " << j << std::endl; for (j ++; j < domainchangeProbing.size(); ++ j) { assert(domain->infeasible_); @@ -2938,7 +2938,7 @@ bool HighsDomain::propagate() { } if (!infeasible_ && dfprobingPropagation.isActive()) { - std::cout << "Activated by nRedundantIndices = " << dfprobingPropagation.redundantPropagateVec_.size() << std::endl; + // std::cout << "Activated by nRedundantIndices = " << dfprobingPropagation.redundantPropagateVec_.size() << std::endl; dfprobingPropagation.propagate(); if (!havePropagationRows() && !dfprobingPropagation.isZeroObjFixingEnabled()) { dfprobingPropagation.enableZeroObjFixing(); From bae50aa85cf326305396218bd7a24d71a7a35b75 Mon Sep 17 00:00:00 2001 From: zwang Date: Sun, 26 Jul 2026 16:04:55 +0800 Subject: [PATCH 12/25] renaming functions --- highs/mip/HighsDomain.h | 6 +++--- highs/mip/HighsImplications.cpp | 8 ++++---- highs/mip/HighsImplications.h | 16 ++++++++-------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/highs/mip/HighsDomain.h b/highs/mip/HighsDomain.h index 36a2107ea9f..7ad32643fbd 100644 --- a/highs/mip/HighsDomain.h +++ b/highs/mip/HighsDomain.h @@ -245,8 +245,8 @@ class HighsDomain { enum DFPROBING_FIX_DIRECTION { FIXDIRECTION_NOT_DECIDED = 0, - FIXDIRECTION_LOWER_BOUND = 1, - FIXDIRECTION_UPPER_BOUND = 2, + FIXDIRECTION_LOWER_BOUND, + FIXDIRECTION_UPPER_BOUND, }; std::vector zeroCostVarsDirection_; std::vector> zeroCostFixedVariables_; @@ -311,7 +311,7 @@ class HighsDomain { } - void clearRedundant() { + void clearRedundantInfo() { previousSize_ = 0; if (!redundantPropagateVec_.empty()) { // clear buffers for (auto x : redundantPropagateVec_) diff --git a/highs/mip/HighsImplications.cpp b/highs/mip/HighsImplications.cpp index f7b957d2a75..1692327b813 100644 --- a/highs/mip/HighsImplications.cpp +++ b/highs/mip/HighsImplications.cpp @@ -27,7 +27,7 @@ bool HighsImplications::computeImplications(HighsInt col, bool val) { const auto& domchgreason = globaldomain.getDomainChangeReason(); size_t changedend = globaldomain.getChangedCols().size(); - globaldomain.getDfProbingPropagation().clearRedundant(); + globaldomain.getDfProbingPropagation().clearRedundantInfo(); if (globaldomain.inProbing_) globaldomain.getDfProbingPropagation().enablePropagator(); @@ -117,7 +117,7 @@ bool HighsImplications::computeImplications(HighsInt col, bool val) { }); // Store the tentative bound changes (fixing) of binary variables separately for (auto i = binstart_tmp; i != implics_tentative.end(); ++ i) - cacheTmpCliques(val, *i); + recordTentativeCliques(val, *i); implics_tentative.erase(binstart_tmp, implics_tentative.end()); } @@ -337,7 +337,7 @@ bool HighsImplications::runProbing(HighsInt col, HighsInt& numReductions) { mipsolver.mipdata_->cliquetable.getSubstitution(col) == nullptr) { // setup for dfprobingPropagation - clearCacheClique(); + clearTentativeClique(); globaldomain.getDfProbingPropagation().setZeroCostFixingPosition(kHighsIInf32); bool infeasible = computeImplications(col, 1); @@ -421,7 +421,7 @@ bool HighsImplications::runProbing(HighsInt col, HighsInt& numReductions) { } while (haveReduction); // clear the tentative bound changes for binary variables obtained from probing on x[col] - clearCacheClique(); + clearTentativeClique(); } // analyze implications diff --git a/highs/mip/HighsImplications.h b/highs/mip/HighsImplications.h index 4e66b7b4bb6..c041df32255 100644 --- a/highs/mip/HighsImplications.h +++ b/highs/mip/HighsImplications.h @@ -239,7 +239,7 @@ class HighsImplications { void applyImplications(HighsDomain& domain, HighsInt col, HighsInt val); // collect tentative binary implications - void cacheTmpCliques(bool val, const HighsDomainChange& bchg) { + void recordTentativeCliques(bool val, const HighsDomainChange& bchg) { const int iCol = bchg.column; if (val == 0) { // probing x_k = 0 if (bchg.boundtype == HighsBoundType::kLower) { // fixed to 1 @@ -275,37 +275,37 @@ class HighsImplications { } } // clear tentative binary implications - void clearCacheClique() { + void clearTentativeClique() { for (auto iCol : binaryInvolvedInds_) binaryInvolvedFlags_[iCol] = binaryFixType::kNoReduction; binaryInvolvedInds_.clear(); } - // tools for cacheTmpCliques + // tools for recordTentativeCliques bool isFixedTo0(bool val, HighsInt iCol) { if (binaryInvolvedFlags_[iCol] == 0) return false; uint8_t mask; - if (val == 0) { // x_k = 0, last two digits + if (val == 0) { // probing at x = 0, last two digits mask = 1 << (1); return (binaryInvolvedFlags_[iCol] & mask) != 0; } - else { // x_k = 1, first two digits + else { // probing at x = 1, first two digits mask = 1 << (3); return (binaryInvolvedFlags_[iCol] & mask) != 0; } } - // tools for cacheTmpCliques + // tools for recordTentativeCliques bool isFixedTo1(bool val, HighsInt iCol) { if (binaryInvolvedFlags_[iCol] == 0) return false; uint8_t mask; - if (val == 0) { // x_k = 0, last two digits + if (val == 0) { // probing at x = 0, last two digits mask = 1; return (binaryInvolvedFlags_[iCol] & mask) != 0; } - else { // x_k = 1, first two digits + else { // probint at x = 1, first two digits mask = 1 << (2); return (binaryInvolvedFlags_[iCol] & mask) != 0; } From 6fd7e6d92c6425d35376f6bfe32137f4c9402f0b Mon Sep 17 00:00:00 2001 From: zwang Date: Mon, 27 Jul 2026 20:31:41 +0800 Subject: [PATCH 13/25] add parameters for DFProbing and GDF --- highs/lp_data/HighsOptions.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/highs/lp_data/HighsOptions.h b/highs/lp_data/HighsOptions.h index ef09ae76954..55280fc0f86 100644 --- a/highs/lp_data/HighsOptions.h +++ b/highs/lp_data/HighsOptions.h @@ -466,6 +466,8 @@ struct HighsOptionsStruct { bool less_infeasible_DSE_check; bool less_infeasible_DSE_choose_row; bool use_original_HFactor_logic; + bool presolve_dfprobing; + bool presolve_gdf; // bool allow_pdlp_cleanup; bool run_centring; HighsInt max_centring_steps; @@ -1724,6 +1726,19 @@ class HighsOptions : public HighsOptionsStruct { advanced, ¢ring_ratio_tolerance, 0, 100, kHighsInf); records.push_back(record_double); + record_bool = + new OptionRecordBool("presolve_dfprobing", + "Use the dual fixing aumgented probing technique in presolve", advanced, + &presolve_dfprobing, true); + records.push_back(record_bool); + + record_bool = + new OptionRecordBool("presolve_gdf", + "Use the generalized dual fixing technique in presolve", advanced, + &presolve_gdf, true); + records.push_back(record_bool); + + // Set up the log_options aliases log_options.clear(); log_options.log_stream = From 35a08f5fb07c7d96f833acb8d31ef806f7e05c71 Mon Sep 17 00:00:00 2001 From: zwang Date: Mon, 27 Jul 2026 20:32:34 +0800 Subject: [PATCH 14/25] Add the functions of GDF --- highs/mip/HighsDomain.cpp | 235 ++++++++++++++++++++++++++++++-- highs/mip/HighsDomain.h | 19 ++- highs/mip/HighsImplications.cpp | 41 ++++-- highs/presolve/HPresolve.cpp | 5 +- 4 files changed, 271 insertions(+), 29 deletions(-) diff --git a/highs/mip/HighsDomain.cpp b/highs/mip/HighsDomain.cpp index ef8b5cfdaba..1c3ad8cd9ab 100644 --- a/highs/mip/HighsDomain.cpp +++ b/highs/mip/HighsDomain.cpp @@ -649,7 +649,15 @@ HighsDomain::DualfixingProbingPropagation::DualfixingProbingPropagation(const Du colUpperLockReduced_(other.colUpperLockReduced_), candidatesVec_(other.candidatesVec_), candidatesFlag_(other.candidatesFlag_), - lockNeedClear_(other.lockNeedClear_) {;} + lockNeedClear_(other.lockNeedClear_), + gdfCandidatesVec_(other.gdfCandidatesVec_), + gdfCandidatesFlag_(other.gdfCandidatesFlag_), + gdfLbReachable0_(other.gdfLbReachable0_), + gdfLbReachable1_(other.gdfLbReachable1_), + gdfUbReachable0_(other.gdfUbReachable0_), + gdfUbReachable1_(other.gdfUbReachable1_), + gdfLbReachable_(other.gdfLbReachable_), + gdfUbReachable_(other.gdfUbReachable_) {;} void HighsDomain::DualfixingProbingPropagation::recomputeLocks() { mipsolver = domain->mipsolver; @@ -673,6 +681,16 @@ void HighsDomain::DualfixingProbingPropagation::recomputeLocks() { candidatesFlag_.assign(mipsolver->numCol(), false); lockNeedClear_.reserve(mipsolver->numCol()); + gdfCandidatesVec_.reserve(mipsolver->numCol()); + gdfCandidatesFlag_.assign(mipsolver->numCol(), false); + + gdfLbReachable0_.clear(); + gdfLbReachable1_.clear(); + gdfUbReachable0_.clear(); + gdfUbReachable1_.clear(); + gdfLbReachable_.clear(); + gdfUbReachable_.clear(); + const auto model = mipsolver->model_; for (HighsInt iCol = 0; iCol < model->a_matrix_.num_col_; iCol ++) { for (HighsInt k = model->a_matrix_.start_[iCol]; k < model->a_matrix_.start_[iCol + 1]; k ++) { @@ -740,11 +758,10 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { // #endif assert(candidatesVec_.empty()); - vector domainchangeProbing; + vector domainchangeDFProbing; // tool lambda functions auto addToCandidate = [&](HighsInt k) { - // std::cout << "k = " << k << std::endl; if (candidatesFlag_[k]) return; else { @@ -799,8 +816,7 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { thisbchg->column = iCol; thisbchg->boundtype = HighsBoundType::kUpper; thisbchg->boundval = domain->col_lower_[iCol]; - domainchangeProbing.push_back(thisbchg); - // std::cout << "fixing to lower " << iCol << std::endl; + domainchangeDFProbing.push_back(thisbchg); }; auto addFixUpper = [&](int iCol) { @@ -808,8 +824,7 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { thisbchg->column = iCol; thisbchg->boundtype = HighsBoundType::kLower; thisbchg->boundval = domain->col_upper_[iCol]; - domainchangeProbing.push_back(thisbchg); - // std::cout << "fixing to upper " << iCol << std::endl; + domainchangeDFProbing.push_back(thisbchg); }; auto collectFixLower = [&](int iCol) { @@ -993,23 +1008,217 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { // change bound size_t j = 0; - for (; j != domainchangeProbing.size() && !domain->infeasible_; ++ j) { - domain->changeBound(*domainchangeProbing[j], Reason::unspecified()); - delete domainchangeProbing[j]; + for (; j != domainchangeDFProbing.size() && !domain->infeasible_; ++ j) { + domain->changeBound(*domainchangeDFProbing[j], Reason::unspecified()); + delete domainchangeDFProbing[j]; } // std::cout << "#Bchg = " << j << std::endl; - for (j ++; j < domainchangeProbing.size(); ++ j) { + for (j ++; j < domainchangeDFProbing.size(); ++ j) { assert(domain->infeasible_); - delete domainchangeProbing[j]; + delete domainchangeDFProbing[j]; } // record the current number of redundant constraints. previousSize_ = redundantPropagateVec_.size(); } +void HighsDomain::DualfixingProbingPropagation::updateGDFInfo(HighsInt probing_variable, bool val) { + // tool lambda functions + auto addToCandidate = [&](HighsInt k) { + if (gdfCandidatesFlag_[k]) + return; + else { + gdfCandidatesVec_.push_back(k); + gdfCandidatesFlag_[k] = true; + } + }; + + for (const auto x : redundantPropagateFlag_) { + const HighsInt iRow = x / 2; + const bool isRhs = x % 2; + HighsInt rstart = mipsolver->mipdata_->ARstart_[iRow]; + HighsInt rend = mipsolver->mipdata_->ARstart_[iRow + 1]; + + for (auto k = rstart; k < rend; k++) { + const HighsInt iCol = mipsolver->mipdata_->ARindex_[k]; + const double iValue = mipsolver->mipdata_->ARvalue_[k]; + const double cost = mipsolver->model_->col_cost_[iCol]; + bool considered = false; + if (domain->isFixed(iCol) || mipsolver->mipdata_->implications.colsubstituted[iCol]) + continue; + + if (iValue > 0) { + if (isRhs) { // consider upper bound reachable + const double globalUb = mipsolver->model_->col_upper_[iCol]; + const double probingUb = domain->col_upper_[iCol]; + if (!ableToFixToUb(iCol) || domain->getMaxActivity(iRow) == kHighsInf) + continue; + const bool upper_bound_reachable = + domain->getMaxActivity(iRow) + iValue * (globalUb - probingUb) <= mipsolver->model_->row_upper_[iRow] + domain->feastol(); + if (upper_bound_reachable) { + considered = true; + if (iCol == probing_variable && val == 0) + gdfUbReachable_[iCol].insert(iRow); + else { + if (val == 0) + gdfUbReachable0_[iCol].insert(iRow); + if (val == 1) + gdfUbReachable1_[iCol].insert(iRow); + } + } + } + else { // consider lower bound reachable + const double globalLb = mipsolver->model_->col_lower_[iCol]; + const double probingLb = domain->col_lower_[iCol]; + if (!ableToFixToLb(iCol) || domain->getMinActivity(iRow) == -kHighsInf) + continue; + const bool lower_bound_reachable = + domain->getMinActivity(iRow) + iValue * (globalLb - probingLb) >= mipsolver->model_->row_lower_[iRow] - domain->feastol(); + if (lower_bound_reachable) { + considered = true; + if (iCol == probing_variable && val == 1) + gdfLbReachable_[iCol].insert(iRow); + else { + if (val == 0) + gdfLbReachable0_[iCol].insert(iRow); + if (val == 1) + gdfLbReachable1_[iCol].insert(iRow); + } + } + } + } + + else { + if (isRhs) { // consider lower bound reachable + const double globalLb = mipsolver->model_->col_lower_[iCol]; + const double probingLb = domain->col_lower_[iCol]; + if (!ableToFixToLb(iCol) || domain->getMaxActivity(iRow) == kHighsInf) + continue; + const bool lower_bound_reachable = + domain->getMaxActivity(iRow) + iValue * (globalLb - probingLb) <= mipsolver->model_->row_upper_[iRow] + domain->feastol(); + if (lower_bound_reachable) { + considered = true; + if (iCol == probing_variable && val == 1) + gdfLbReachable_[iCol].insert(iRow); + else { + if (val == 0) + gdfLbReachable0_[iCol].insert(iRow); + if (val == 1) + gdfLbReachable1_[iCol].insert(iRow); + } + } + } + else { // consider upper bound reachable + const double globalUb = mipsolver->model_->col_upper_[iCol]; + const double probingUb = domain->col_upper_[iCol]; + if (!ableToFixToUb(iCol) || domain->getMinActivity(iRow) == -kHighsInf) + continue; + const bool upper_bound_reachable = + domain->getMinActivity(iRow) + iValue * (globalUb - probingUb) >= mipsolver->model_->row_lower_[iRow] - domain->feastol(); + if (upper_bound_reachable) { + considered = true; + if (iCol == probing_variable && val == 0) + gdfUbReachable_[iCol].insert(iRow); + else { + if (val == 0) + gdfUbReachable0_[iCol].insert(iRow); + if (val == 1) + gdfUbReachable1_[iCol].insert(iRow); + } + } + } + } + if (considered) + addToCandidate(iCol); + } + } +} + +HighsInt HighsDomain::DualfixingProbingPropagation::processGDFFixing() { + // extract reachable information + auto getIntersection = [&](const std::unordered_set& vec0, + const std::unordered_set& vec1, + std::unordered_set& vReachable) { + if (vec0.empty() || vec1.empty()) + return; + + // always loop in the smaller vector, and search in the larger vector + if (vec0.size() < vec1.size()) { + for (auto it1 = vec0.begin(); it1 != vec0.end(); it1 ++) { + auto it2 = vec1.find(*it1); + if (it2 != vec1.end()) + vReachable.insert(*it1); + } + } + else { + for (auto it1 = vec1.begin(); it1 != vec1.end(); it1 ++) { + auto it2 = vec0.find(*it1); + if (it2 != vec0.end()) + vReachable.insert(*it1); + } + } + }; + + std::vector gdfFixingStack_; + + for (const auto iCol : gdfCandidatesVec_) { + // lower bound reachable + getIntersection(gdfLbReachable0_[iCol], gdfLbReachable1_[iCol], gdfLbReachable_[iCol]); + // upper bound reachable + getIntersection(gdfUbReachable0_[iCol], gdfUbReachable1_[iCol], gdfUbReachable_[iCol]); + // extract fixings + if ((HighsInt)gdfLbReachable_[iCol].size() == colLowerLockOriginal_[iCol]) { + HighsDomainChange* thisbchg = new HighsDomainChange; + thisbchg->column = iCol; + thisbchg->boundtype = HighsBoundType::kUpper; + thisbchg->boundval = mipsolver->model_->col_lower_[iCol]; + gdfFixingStack_.push_back(thisbchg); + } + // a variable cannot be fixed to lb and ub simultaneously + else if ((HighsInt)gdfUbReachable_[iCol].size() == colUpperLockOriginal_[iCol]) { + HighsDomainChange* thisbchg = new HighsDomainChange; + thisbchg->column = iCol; + thisbchg->boundtype = HighsBoundType::kLower; + thisbchg->boundval = mipsolver->model_->col_upper_[iCol]; + gdfFixingStack_.push_back(thisbchg); + } + } + + // apply bound change + size_t j = 0; + for (; j != gdfFixingStack_.size() && !domain->infeasible_; ++ j) { + domain->changeBound(*gdfFixingStack_[j], Reason::unspecified()); + delete gdfFixingStack_[j]; + } + + for (j ++; j < gdfFixingStack_.size(); ++ j) { + assert(domain->infeasible_); + delete gdfFixingStack_[j]; + } + + gdfFixingStack_.clear(); + std::cout << "GDF find " << j << " fixings.\n"; + + return (HighsInt)j; +} + +void HighsDomain::DualfixingProbingPropagation::clearGDFInfo() { + for (const auto x : gdfCandidatesVec_) + gdfCandidatesFlag_[x] = false; + gdfCandidatesVec_.clear(); + + gdfLbReachable0_.clear(); + gdfUbReachable0_.clear(); + gdfLbReachable1_.clear(); + gdfUbReachable1_.clear(); +} + +HighsInt HighsDomain::DualfixingProbingPropagation::finalRoundGDF() { + ; +} namespace highs { template <> @@ -2937,7 +3146,7 @@ bool HighsDomain::propagate() { } } - if (!infeasible_ && dfprobingPropagation.isActive()) { + if (!infeasible_ && dfprobingPropagation.isActive() && mipsolver->options_mip_->presolve_dfprobing) { // std::cout << "Activated by nRedundantIndices = " << dfprobingPropagation.redundantPropagateVec_.size() << std::endl; dfprobingPropagation.propagate(); if (!havePropagationRows() && !dfprobingPropagation.isZeroObjFixingEnabled()) { diff --git a/highs/mip/HighsDomain.h b/highs/mip/HighsDomain.h index 7ad32643fbd..acc66372a38 100644 --- a/highs/mip/HighsDomain.h +++ b/highs/mip/HighsDomain.h @@ -264,6 +264,15 @@ class HighsDomain { std::vector candidatesFlag_; std::unordered_set lockNeedClear_; + std::vector gdfCandidatesVec_; + std::vector gdfCandidatesFlag_; + std::unordered_map> gdfLbReachable0_; + std::unordered_map> gdfLbReachable1_; + std::unordered_map> gdfUbReachable0_; + std::unordered_map> gdfUbReachable1_; + std::unordered_map> gdfLbReachable_; + std::unordered_map> gdfUbReachable_; + void enablePropagator() { enabled_ = true; } @@ -314,7 +323,7 @@ class HighsDomain { void clearRedundantInfo() { previousSize_ = 0; if (!redundantPropagateVec_.empty()) { // clear buffers - for (auto x : redundantPropagateVec_) + for (const auto x : redundantPropagateVec_) redundantPropagateFlag_[x] = false; redundantPropagateVec_.clear(); @@ -341,12 +350,12 @@ class HighsDomain { void recomputeLocks(); void updateRhsRedundant(HighsInt row); void updateLhsRedundant(HighsInt row); - void propagate(); - - - + void updateGDFInfo(HighsInt probing_variable, bool val); + HighsInt processGDFFixing(); + HighsInt finalRoundGDF(); + void clearGDFInfo(); }; private: diff --git a/highs/mip/HighsImplications.cpp b/highs/mip/HighsImplications.cpp index 1692327b813..a0d25175f79 100644 --- a/highs/mip/HighsImplications.cpp +++ b/highs/mip/HighsImplications.cpp @@ -27,9 +27,12 @@ bool HighsImplications::computeImplications(HighsInt col, bool val) { const auto& domchgreason = globaldomain.getDomainChangeReason(); size_t changedend = globaldomain.getChangedCols().size(); - globaldomain.getDfProbingPropagation().clearRedundantInfo(); - if (globaldomain.inProbing_) + const bool useDFProbing = globaldomain.inProbing_ && mipsolver.options_mip_->presolve_dfprobing; + const bool useGDF = globaldomain.inProbing_ && mipsolver.options_mip_->presolve_gdf; + if (useDFProbing || useGDF) { + globaldomain.getDfProbingPropagation().clearRedundantInfo(); globaldomain.getDfProbingPropagation().enablePropagator(); + } HighsInt stackimplicstart = domchgstack.size() + 1; HighsInt numImplications = -stackimplicstart; @@ -68,7 +71,7 @@ bool HighsImplications::computeImplications(HighsInt col, bool val) { if (isInfeasible(col, val)) return true; globaldomain.propagate(); - if (globaldomain.inProbing_) + if (useDFProbing || useGDF) globaldomain.getDfProbingPropagation().disablePropagator(); if (isInfeasible(col, val)) return true; @@ -88,10 +91,10 @@ bool HighsImplications::computeImplications(HighsInt col, bool val) { HighsInt numEntries = mipsolver.mipdata_->cliquetable.getNumEntries(); HighsInt maxEntries = 100000 + mipsolver.numNonzero(); - const HighsInt tentativeStart = globaldomain.inProbing_ ? globaldomain.getDfProbingPropagation().getZeroCostFixingPosition() : kHighsIInf32; - if (globaldomain.inProbing_) { + const HighsInt tentativeStart = useDFProbing ? globaldomain.getDfProbingPropagation().getZeroCostFixingPosition() : kHighsIInf32; + if (useDFProbing) implics_tentative.assign(domchgstack.begin() + stackimplicstart, domchgstack.begin() + stackimplicend); - } + for (HighsInt i = stackimplicstart; i < stackimplicend; ++i) { if (domchgreason[i].type == HighsDomain::Reason::kCliqueTable && ((domchgreason[i].index >> 1) == col || numEntries >= maxEntries)) @@ -106,6 +109,10 @@ bool HighsImplications::computeImplications(HighsInt col, bool val) { // inform caller about lifting opportunities storeLiftingOpportunities(col, val); + // update information to derive generalized dual fixings + if (useGDF) + globaldomain.getDfProbingPropagation().updateGDFInfo(col, val); + // backtrack doBacktrack(changedend); @@ -336,9 +343,16 @@ bool HighsImplications::runProbing(HighsInt col, HighsInt& numReductions) { !implicationsCached(col, 0) && mipsolver.mipdata_->cliquetable.getSubstitution(col) == nullptr) { - // setup for dfprobingPropagation - clearTentativeClique(); - globaldomain.getDfProbingPropagation().setZeroCostFixingPosition(kHighsIInf32); + const bool useDFProbing = globaldomain.inProbing_ && mipsolver.options_mip_->presolve_dfprobing; + const bool useGDF = globaldomain.inProbing_ && mipsolver.options_mip_->presolve_gdf; + // setup for dfprobingPropagation + if (useDFProbing) { + clearTentativeClique(); + globaldomain.getDfProbingPropagation().setZeroCostFixingPosition(kHighsIInf32); + } + if (useGDF) + globaldomain.getDfProbingPropagation().clearGDFInfo(); + bool infeasible = computeImplications(col, 1); if (globaldomain.infeasible()) return true; @@ -352,7 +366,7 @@ bool HighsImplications::runProbing(HighsInt col, HighsInt& numReductions) { if (mipsolver.mipdata_->cliquetable.getSubstitution(col) != nullptr) return true; - if (globaldomain.inProbing_ && !binaryInvolvedInds_.empty()) { + if (useDFProbing && !binaryInvolvedInds_.empty()) { HighsCliqueTable& cliquetable = mipsolver.mipdata_->cliquetable; HighsCliqueTable::CliqueVar clique[2]; bool haveReduction; @@ -503,6 +517,13 @@ bool HighsImplications::runProbing(HighsInt col, HighsInt& numReductions) { if (haveTentativeImplics_one) implications[2 * col + 1].implics_tentative.clear(); + if (useGDF) { + // fix variables using generalized dual fixing + HighsInt nfix = globaldomain.getDfProbingPropagation().processGDFFixing(); + if (nfix > 0) + globaldomain.propagate(); + } + return true; } diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index e8833d7d68f..0b16e0d7875 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1735,7 +1735,8 @@ HPresolve::Result HPresolve::runProbing(HighsPostsolveStack& postsolve_stack) { } }; - domain.getDfProbingPropagation().recomputeLocks(); + if (options->presolve_dfprobing || options->presolve_gdf) + domain.getDfProbingPropagation().recomputeLocks(); for (const auto& binvar : binaries) { // Count the binaries considered @@ -1843,6 +1844,8 @@ HPresolve::Result HPresolve::runProbing(HighsPostsolveStack& postsolve_stack) { } } + if (options->presolve_gdf) + domain.getDfProbingPropagation().finalRoundGDF(); // finalise probing HighsInt numVarsFixed = 0; HighsInt numBndsTightened = 0; From 0482578389eff36acc51ae8f4dd2e13462e6984e Mon Sep 17 00:00:00 2001 From: zwang Date: Mon, 27 Jul 2026 21:50:59 +0800 Subject: [PATCH 15/25] use global bounds to skip fixed variables --- highs/mip/HighsDomain.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/highs/mip/HighsDomain.cpp b/highs/mip/HighsDomain.cpp index 1c3ad8cd9ab..12ee1c0bce1 100644 --- a/highs/mip/HighsDomain.cpp +++ b/highs/mip/HighsDomain.cpp @@ -1046,7 +1046,7 @@ void HighsDomain::DualfixingProbingPropagation::updateGDFInfo(HighsInt probing_v const double iValue = mipsolver->mipdata_->ARvalue_[k]; const double cost = mipsolver->model_->col_cost_[iCol]; bool considered = false; - if (domain->isFixed(iCol) || mipsolver->mipdata_->implications.colsubstituted[iCol]) + if (mipsolver->model_->col_lower_[iCol] == mipsolver->model_->col_upper_[iCol] || mipsolver->mipdata_->implications.colsubstituted[iCol]) continue; if (iValue > 0) { From 316852e9dae309e143a7992adbf049f64c2a2427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=85=86=E7=BB=B4?= Date: Mon, 27 Jul 2026 23:15:56 +0800 Subject: [PATCH 16/25] fix typo in GDF --- highs/mip/HighsDomain.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/highs/mip/HighsDomain.cpp b/highs/mip/HighsDomain.cpp index 1c3ad8cd9ab..a27e3de74cf 100644 --- a/highs/mip/HighsDomain.cpp +++ b/highs/mip/HighsDomain.cpp @@ -1035,7 +1035,7 @@ void HighsDomain::DualfixingProbingPropagation::updateGDFInfo(HighsInt probing_v } }; - for (const auto x : redundantPropagateFlag_) { + for (const auto x : redundantPropagateVec_) { const HighsInt iRow = x / 2; const bool isRhs = x % 2; HighsInt rstart = mipsolver->mipdata_->ARstart_[iRow]; @@ -1174,7 +1174,7 @@ HighsInt HighsDomain::DualfixingProbingPropagation::processGDFFixing() { HighsDomainChange* thisbchg = new HighsDomainChange; thisbchg->column = iCol; thisbchg->boundtype = HighsBoundType::kUpper; - thisbchg->boundval = mipsolver->model_->col_lower_[iCol]; + thisbchg->boundval = domain->col_lower_[iCol]; gdfFixingStack_.push_back(thisbchg); } // a variable cannot be fixed to lb and ub simultaneously @@ -1182,7 +1182,7 @@ HighsInt HighsDomain::DualfixingProbingPropagation::processGDFFixing() { HighsDomainChange* thisbchg = new HighsDomainChange; thisbchg->column = iCol; thisbchg->boundtype = HighsBoundType::kLower; - thisbchg->boundval = mipsolver->model_->col_upper_[iCol]; + thisbchg->boundval = domain->col_upper_[iCol]; gdfFixingStack_.push_back(thisbchg); } } From 01d66d6318982c27f36f680b942fba7a2c25f433 Mon Sep 17 00:00:00 2001 From: zwang Date: Tue, 28 Jul 2026 12:29:58 +0800 Subject: [PATCH 17/25] add debug output --- highs/mip/HighsDomain.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/highs/mip/HighsDomain.cpp b/highs/mip/HighsDomain.cpp index 62a374674ab..615a38ebf35 100644 --- a/highs/mip/HighsDomain.cpp +++ b/highs/mip/HighsDomain.cpp @@ -1059,6 +1059,7 @@ void HighsDomain::DualfixingProbingPropagation::updateGDFInfo(HighsInt probing_v domain->getMaxActivity(iRow) + iValue * (globalUb - probingUb) <= mipsolver->model_->row_upper_[iRow] + domain->feastol(); if (upper_bound_reachable) { considered = true; + printf("Probing on x-%d = %d, for non-zero (%d, %d) = %f, rhs = %f, demonstrate ub reachable.\n", probing_variable, val, iRow, iCol, iValue, mipsolver->model_->row_upper_[iRow]); if (iCol == probing_variable && val == 0) gdfUbReachable_[iCol].insert(iRow); else { @@ -1078,6 +1079,7 @@ void HighsDomain::DualfixingProbingPropagation::updateGDFInfo(HighsInt probing_v domain->getMinActivity(iRow) + iValue * (globalLb - probingLb) >= mipsolver->model_->row_lower_[iRow] - domain->feastol(); if (lower_bound_reachable) { considered = true; + printf("Probing on x-%d = %d, for non-zero (%d, %d) = %f, lhs = %f, demonstrate lb reachable.\n", probing_variable, val, iRow, iCol, iValue, mipsolver->model_->row_lower_[iRow]); if (iCol == probing_variable && val == 1) gdfLbReachable_[iCol].insert(iRow); else { @@ -1100,6 +1102,7 @@ void HighsDomain::DualfixingProbingPropagation::updateGDFInfo(HighsInt probing_v domain->getMaxActivity(iRow) + iValue * (globalLb - probingLb) <= mipsolver->model_->row_upper_[iRow] + domain->feastol(); if (lower_bound_reachable) { considered = true; + printf("Probing on x-%d = %d, for non-zero (%d, %d) = %f, rhs = %f, demonstrate lb reachable.\n", probing_variable, val, iRow, iCol, iValue, mipsolver->model_->row_upper_[iRow]); if (iCol == probing_variable && val == 1) gdfLbReachable_[iCol].insert(iRow); else { @@ -1119,6 +1122,7 @@ void HighsDomain::DualfixingProbingPropagation::updateGDFInfo(HighsInt probing_v domain->getMinActivity(iRow) + iValue * (globalUb - probingUb) >= mipsolver->model_->row_lower_[iRow] - domain->feastol(); if (upper_bound_reachable) { considered = true; + printf("Probing on x-%d = %d, for non-zero (%d, %d) = %f, lhs = %f, demonstrate ub reachable.\n", probing_variable, val, iRow, iCol, iValue, mipsolver->model_->row_lower_[iRow]); if (iCol == probing_variable && val == 0) gdfUbReachable_[iCol].insert(iRow); else { From bc31352ad339babcfe208787edd817cb467fbe80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=85=86=E7=BB=B4?= Date: Tue, 28 Jul 2026 13:52:36 +0800 Subject: [PATCH 18/25] better output --- highs/mip/HighsDomain.cpp | 23 ++++--- highs/mip/HighsImplications.cpp | 117 +++++++++++++++----------------- 2 files changed, 66 insertions(+), 74 deletions(-) diff --git a/highs/mip/HighsDomain.cpp b/highs/mip/HighsDomain.cpp index 615a38ebf35..f03c4d41fdb 100644 --- a/highs/mip/HighsDomain.cpp +++ b/highs/mip/HighsDomain.cpp @@ -985,7 +985,7 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { // if (mipsolver->model_->col_cost_[iCol] >= mipsolver->options_mip_->dual_feasibility_tolerance) { if (mipsolver->model_->col_cost_[iCol] >= mipsolver->options_mip_->dual_feasibility_tolerance) { if (canBeFixedToLower) { - checkVariableLowerLock(iCol); + // checkVariableLowerLock(iCol); addFixLower(iCol); continue; } @@ -993,7 +993,7 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { // if (mipsolver->model_->col_cost_[iCol] <= mipsolver->options_mip_->dual_feasibility_tolerance) { if (mipsolver->model_->col_cost_[iCol] <= mipsolver->options_mip_->dual_feasibility_tolerance) { if (canBeFixedToUpper) { - checkVariableUpperLock(iCol); + // checkVariableUpperLock(iCol); addFixUpper(iCol); continue; } @@ -1059,7 +1059,7 @@ void HighsDomain::DualfixingProbingPropagation::updateGDFInfo(HighsInt probing_v domain->getMaxActivity(iRow) + iValue * (globalUb - probingUb) <= mipsolver->model_->row_upper_[iRow] + domain->feastol(); if (upper_bound_reachable) { considered = true; - printf("Probing on x-%d = %d, for non-zero (%d, %d) = %f, rhs = %f, demonstrate ub reachable.\n", probing_variable, val, iRow, iCol, iValue, mipsolver->model_->row_upper_[iRow]); + // printf("Probing on x-%d = %d, for non-zero (%d, %d) = %f, rhs = %f, demonstrate ub reachable.\n", probing_variable, val, iRow, iCol, iValue, mipsolver->model_->row_upper_[iRow]); if (iCol == probing_variable && val == 0) gdfUbReachable_[iCol].insert(iRow); else { @@ -1079,7 +1079,7 @@ void HighsDomain::DualfixingProbingPropagation::updateGDFInfo(HighsInt probing_v domain->getMinActivity(iRow) + iValue * (globalLb - probingLb) >= mipsolver->model_->row_lower_[iRow] - domain->feastol(); if (lower_bound_reachable) { considered = true; - printf("Probing on x-%d = %d, for non-zero (%d, %d) = %f, lhs = %f, demonstrate lb reachable.\n", probing_variable, val, iRow, iCol, iValue, mipsolver->model_->row_lower_[iRow]); + // printf("Probing on x-%d = %d, for non-zero (%d, %d) = %f, lhs = %f, demonstrate lb reachable.\n", probing_variable, val, iRow, iCol, iValue, mipsolver->model_->row_lower_[iRow]); if (iCol == probing_variable && val == 1) gdfLbReachable_[iCol].insert(iRow); else { @@ -1102,7 +1102,7 @@ void HighsDomain::DualfixingProbingPropagation::updateGDFInfo(HighsInt probing_v domain->getMaxActivity(iRow) + iValue * (globalLb - probingLb) <= mipsolver->model_->row_upper_[iRow] + domain->feastol(); if (lower_bound_reachable) { considered = true; - printf("Probing on x-%d = %d, for non-zero (%d, %d) = %f, rhs = %f, demonstrate lb reachable.\n", probing_variable, val, iRow, iCol, iValue, mipsolver->model_->row_upper_[iRow]); + // printf("Probing on x-%d = %d, for non-zero (%d, %d) = %f, rhs = %f, demonstrate lb reachable.\n", probing_variable, val, iRow, iCol, iValue, mipsolver->model_->row_upper_[iRow]); if (iCol == probing_variable && val == 1) gdfLbReachable_[iCol].insert(iRow); else { @@ -1122,7 +1122,7 @@ void HighsDomain::DualfixingProbingPropagation::updateGDFInfo(HighsInt probing_v domain->getMinActivity(iRow) + iValue * (globalUb - probingUb) >= mipsolver->model_->row_lower_[iRow] - domain->feastol(); if (upper_bound_reachable) { considered = true; - printf("Probing on x-%d = %d, for non-zero (%d, %d) = %f, lhs = %f, demonstrate ub reachable.\n", probing_variable, val, iRow, iCol, iValue, mipsolver->model_->row_lower_[iRow]); + // printf("Probing on x-%d = %d, for non-zero (%d, %d) = %f, lhs = %f, demonstrate ub reachable.\n", probing_variable, val, iRow, iCol, iValue, mipsolver->model_->row_lower_[iRow]); if (iCol == probing_variable && val == 0) gdfUbReachable_[iCol].insert(iRow); else { @@ -1174,7 +1174,7 @@ HighsInt HighsDomain::DualfixingProbingPropagation::processGDFFixing() { // upper bound reachable getIntersection(gdfUbReachable0_[iCol], gdfUbReachable1_[iCol], gdfUbReachable_[iCol]); // extract fixings - if ((HighsInt)gdfLbReachable_[iCol].size() == colLowerLockOriginal_[iCol]) { + if (ableToFixToLb(iCol) && (HighsInt)gdfLbReachable_[iCol].size() == colLowerLockOriginal_[iCol]) { HighsDomainChange* thisbchg = new HighsDomainChange; thisbchg->column = iCol; thisbchg->boundtype = HighsBoundType::kUpper; @@ -1182,7 +1182,7 @@ HighsInt HighsDomain::DualfixingProbingPropagation::processGDFFixing() { gdfFixingStack_.push_back(thisbchg); } // a variable cannot be fixed to lb and ub simultaneously - else if ((HighsInt)gdfUbReachable_[iCol].size() == colUpperLockOriginal_[iCol]) { + else if (ableToFixToUb(iCol) && (HighsInt)gdfUbReachable_[iCol].size() == colUpperLockOriginal_[iCol]) { HighsDomainChange* thisbchg = new HighsDomainChange; thisbchg->column = iCol; thisbchg->boundtype = HighsBoundType::kLower; @@ -1198,13 +1198,14 @@ HighsInt HighsDomain::DualfixingProbingPropagation::processGDFFixing() { delete gdfFixingStack_[j]; } - for (j ++; j < gdfFixingStack_.size(); ++ j) { + for (; j < gdfFixingStack_.size(); ++ j) { assert(domain->infeasible_); delete gdfFixingStack_[j]; } gdfFixingStack_.clear(); - std::cout << "GDF find " << j << " fixings.\n"; + if (j > 0) + std::cout << "GDF find " << j << " fixings.\n"; return (HighsInt)j; } @@ -2971,7 +2972,7 @@ bool HighsDomain::propagate() { if (!conflictprop.propagateConflictInds_.empty()) return true; } - if (dfprobingPropagation.isActive()) + if (dfprobingPropagation.isActive() && mipsolver->options_mip_->presolve_dfprobing) return true; return false; diff --git a/highs/mip/HighsImplications.cpp b/highs/mip/HighsImplications.cpp index a0d25175f79..b0a29506a35 100644 --- a/highs/mip/HighsImplications.cpp +++ b/highs/mip/HighsImplications.cpp @@ -369,70 +369,61 @@ bool HighsImplications::runProbing(HighsInt col, HighsInt& numReductions) { if (useDFProbing && !binaryInvolvedInds_.empty()) { HighsCliqueTable& cliquetable = mipsolver.mipdata_->cliquetable; HighsCliqueTable::CliqueVar clique[2]; - bool haveReduction; - do - { - haveReduction = false; - // Loop over binary variables that are tighened at least once - for (auto k : binaryInvolvedInds_) { - // Skip non-binary variables (being fixed now) or those can be substituted by other binary variables - if (!globaldomain.isBinary(k) || colsubstituted[k]) - continue; - // Return if the whole problem is infeasible - if (globaldomain.infeasible()) - return true; - // Get the information how x[k] is fixed in probing on x[col] = 0 and x[col] = 1 - // For the meaning of ``data'', please see lines 71-82 in HighsImplications.h - uint8_t data = binaryInvolvedFlags_[k]; - if (data == 0) // flag for no reduction - continue; - - if (data == binaryFixType::kGlobalLower) { // x[k] is fixed at 0 under both x[col] = 0 and x[col] = 1 - // fix x[k] = 0 by adding two cliques (i.e., these two cliques should be added in computeImplications() to derive global reductions) - clique[0] = HighsCliqueTable::CliqueVar(col, 0); - clique[1] = HighsCliqueTable::CliqueVar(k, 1); - cliquetable.addClique(mipsolver, &clique[0], 2); - clique[0] = HighsCliqueTable::CliqueVar(col, 1); - clique[1] = HighsCliqueTable::CliqueVar(k, 1); - cliquetable.addClique(mipsolver, &clique[0], 2); - data = 0; - haveReduction = true; - } - else if (data == binaryFixType::kGlobalUpper) { // x[k] is fixed at 1 under both x[col] = 0 and x[col] = 1 - // fix x[k] = 1 by adding two cliques (i.e., these two cliques should be added in computeImplications() to derive global reductions) - clique[0] = HighsCliqueTable::CliqueVar(col, 0); - clique[1] = HighsCliqueTable::CliqueVar(k, 0); - cliquetable.addClique(mipsolver, &clique[0], 2); - clique[0] = HighsCliqueTable::CliqueVar(col, 1); - clique[1] = HighsCliqueTable::CliqueVar(k, 0); - cliquetable.addClique(mipsolver, &clique[0], 2); - data = 0; - haveReduction = true; - } - else if (data == binaryFixType::kSubstituteComplement) { // x[k] is fixed at 0 under x[col] = 1, and is fixed at 1 under x[col] = 0; this makes x[col] + x[k] = 1 - // Adding two cliques (i.e., these two cliques should be added in computeImplications() to derive global reductions) - clique[0] = HighsCliqueTable::CliqueVar(col, 1); - clique[1] = HighsCliqueTable::CliqueVar(k, 1); - cliquetable.addClique(mipsolver, &clique[0], 2); - clique[0] = HighsCliqueTable::CliqueVar(col, 0); - clique[1] = HighsCliqueTable::CliqueVar(k, 0); - cliquetable.addClique(mipsolver, &clique[0], 2); - data = 0; - haveReduction = true; - } - else if (data == binaryFixType::kSubstituteEqual) { // x[k] is fixed at 0 under x[col] = 0, and is fixed at 1 under x[col] = 1; this makes x[col] = x[k] - // Adding two cliques (i.e., these two cliques should be added in computeImplications() to derive global reductions) - clique[0] = HighsCliqueTable::CliqueVar(col, 1); - clique[1] = HighsCliqueTable::CliqueVar(k, 0); - cliquetable.addClique(mipsolver, &clique[0], 2); - clique[0] = HighsCliqueTable::CliqueVar(col, 0); - clique[1] = HighsCliqueTable::CliqueVar(k, 1); - cliquetable.addClique(mipsolver, &clique[0], 2); - data = 0; - haveReduction = true; - } + // Loop over binary variables that are tighened at least once + for (auto k : binaryInvolvedInds_) { + // Skip non-binary variables (being fixed now) or those can be substituted by other binary variables + if (!globaldomain.isBinary(k) || colsubstituted[k]) + continue; + // Return if the whole problem is infeasible + if (globaldomain.infeasible()) + return true; + // Get the information how x[k] is fixed in probing on x[col] = 0 and x[col] = 1 + // For the meaning of ``data'', please see lines 71-82 in HighsImplications.h + uint8_t data = binaryInvolvedFlags_[k]; + if (data == 0) // flag for no reduction + continue; + + if (data == binaryFixType::kGlobalLower) { // x[k] is fixed at 0 under both x[col] = 0 and x[col] = 1 + // fix x[k] = 0 by adding two cliques (i.e., these two cliques should be added in computeImplications() to derive global reductions) + clique[0] = HighsCliqueTable::CliqueVar(col, 0); + clique[1] = HighsCliqueTable::CliqueVar(k, 1); + cliquetable.addClique(mipsolver, &clique[0], 2); + clique[0] = HighsCliqueTable::CliqueVar(col, 1); + clique[1] = HighsCliqueTable::CliqueVar(k, 1); + cliquetable.addClique(mipsolver, &clique[0], 2); + data = 0; + } + else if (data == binaryFixType::kGlobalUpper) { // x[k] is fixed at 1 under both x[col] = 0 and x[col] = 1 + // fix x[k] = 1 by adding two cliques (i.e., these two cliques should be added in computeImplications() to derive global reductions) + clique[0] = HighsCliqueTable::CliqueVar(col, 0); + clique[1] = HighsCliqueTable::CliqueVar(k, 0); + cliquetable.addClique(mipsolver, &clique[0], 2); + clique[0] = HighsCliqueTable::CliqueVar(col, 1); + clique[1] = HighsCliqueTable::CliqueVar(k, 0); + cliquetable.addClique(mipsolver, &clique[0], 2); + data = 0; + } + else if (data == binaryFixType::kSubstituteComplement) { // x[k] is fixed at 0 under x[col] = 1, and is fixed at 1 under x[col] = 0; this makes x[col] + x[k] = 1 + // Adding two cliques (i.e., these two cliques should be added in computeImplications() to derive global reductions) + clique[0] = HighsCliqueTable::CliqueVar(col, 1); + clique[1] = HighsCliqueTable::CliqueVar(k, 1); + cliquetable.addClique(mipsolver, &clique[0], 2); + clique[0] = HighsCliqueTable::CliqueVar(col, 0); + clique[1] = HighsCliqueTable::CliqueVar(k, 0); + cliquetable.addClique(mipsolver, &clique[0], 2); + data = 0; } - } while (haveReduction); + else if (data == binaryFixType::kSubstituteEqual) { // x[k] is fixed at 0 under x[col] = 0, and is fixed at 1 under x[col] = 1; this makes x[col] = x[k] + // Adding two cliques (i.e., these two cliques should be added in computeImplications() to derive global reductions) + clique[0] = HighsCliqueTable::CliqueVar(col, 1); + clique[1] = HighsCliqueTable::CliqueVar(k, 0); + cliquetable.addClique(mipsolver, &clique[0], 2); + clique[0] = HighsCliqueTable::CliqueVar(col, 0); + clique[1] = HighsCliqueTable::CliqueVar(k, 1); + cliquetable.addClique(mipsolver, &clique[0], 2); + data = 0; + } + } // clear the tentative bound changes for binary variables obtained from probing on x[col] clearTentativeClique(); From 4cede010e57aa184fe6071ff5534aba2eddfc47d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=85=86=E7=BB=B4?= Date: Thu, 30 Jul 2026 22:56:56 +0800 Subject: [PATCH 19/25] abort propagation when infeasibility is detected --- highs/mip/HighsDomain.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/highs/mip/HighsDomain.cpp b/highs/mip/HighsDomain.cpp index f03c4d41fdb..20e2bafac44 100644 --- a/highs/mip/HighsDomain.cpp +++ b/highs/mip/HighsDomain.cpp @@ -2972,7 +2972,7 @@ bool HighsDomain::propagate() { if (!conflictprop.propagateConflictInds_.empty()) return true; } - if (dfprobingPropagation.isActive() && mipsolver->options_mip_->presolve_dfprobing) + if (!infeasible_ && dfprobingPropagation.isActive() && mipsolver->options_mip_->presolve_dfprobing) return true; return false; From 89597e4d4f3024bacd4f9ec3eb0dac0e78bdc37c Mon Sep 17 00:00:00 2001 From: Zhaowei-Wang Date: Wed, 5 Aug 2026 21:45:45 +0800 Subject: [PATCH 20/25] change data structure of GDF to vector --- highs/mip/HighsDomain.cpp | 113 +++++++++++++++-------------------- highs/mip/HighsDomain.h | 23 ++++--- highs/presolve/HPresolve.cpp | 2 - 3 files changed, 63 insertions(+), 75 deletions(-) diff --git a/highs/mip/HighsDomain.cpp b/highs/mip/HighsDomain.cpp index 20e2bafac44..747e839d422 100644 --- a/highs/mip/HighsDomain.cpp +++ b/highs/mip/HighsDomain.cpp @@ -655,9 +655,7 @@ HighsDomain::DualfixingProbingPropagation::DualfixingProbingPropagation(const Du gdfLbReachable0_(other.gdfLbReachable0_), gdfLbReachable1_(other.gdfLbReachable1_), gdfUbReachable0_(other.gdfUbReachable0_), - gdfUbReachable1_(other.gdfUbReachable1_), - gdfLbReachable_(other.gdfLbReachable_), - gdfUbReachable_(other.gdfUbReachable_) {;} + gdfUbReachable1_(other.gdfUbReachable1_) {;} void HighsDomain::DualfixingProbingPropagation::recomputeLocks() { mipsolver = domain->mipsolver; @@ -684,12 +682,10 @@ void HighsDomain::DualfixingProbingPropagation::recomputeLocks() { gdfCandidatesVec_.reserve(mipsolver->numCol()); gdfCandidatesFlag_.assign(mipsolver->numCol(), false); - gdfLbReachable0_.clear(); - gdfLbReachable1_.clear(); - gdfUbReachable0_.clear(); - gdfUbReachable1_.clear(); - gdfLbReachable_.clear(); - gdfUbReachable_.clear(); + gdfLbReachable0_.assign(mipsolver->numCol(), 0); + gdfLbReachable1_.assign(mipsolver->numCol(), 0); + gdfUbReachable0_.assign(mipsolver->numCol(), 0); + gdfUbReachable1_.assign(mipsolver->numCol(), 0); const auto model = mipsolver->model_; for (HighsInt iCol = 0; iCol < model->a_matrix_.num_col_; iCol ++) { @@ -1060,13 +1056,15 @@ void HighsDomain::DualfixingProbingPropagation::updateGDFInfo(HighsInt probing_v if (upper_bound_reachable) { considered = true; // printf("Probing on x-%d = %d, for non-zero (%d, %d) = %f, rhs = %f, demonstrate ub reachable.\n", probing_variable, val, iRow, iCol, iValue, mipsolver->model_->row_upper_[iRow]); - if (iCol == probing_variable && val == 0) - gdfUbReachable_[iCol].insert(iRow); + if (iCol == probing_variable && val == 0) { + gdfUbReachable0_[iCol]++; + gdfUbReachable1_[iCol]++; + } else { if (val == 0) - gdfUbReachable0_[iCol].insert(iRow); + gdfUbReachable0_[iCol]++; if (val == 1) - gdfUbReachable1_[iCol].insert(iRow); + gdfUbReachable1_[iCol]++; } } } @@ -1080,13 +1078,15 @@ void HighsDomain::DualfixingProbingPropagation::updateGDFInfo(HighsInt probing_v if (lower_bound_reachable) { considered = true; // printf("Probing on x-%d = %d, for non-zero (%d, %d) = %f, lhs = %f, demonstrate lb reachable.\n", probing_variable, val, iRow, iCol, iValue, mipsolver->model_->row_lower_[iRow]); - if (iCol == probing_variable && val == 1) - gdfLbReachable_[iCol].insert(iRow); + if (iCol == probing_variable && val == 1) { + gdfLbReachable0_[iCol]++; + gdfLbReachable1_[iCol]++; + } else { if (val == 0) - gdfLbReachable0_[iCol].insert(iRow); + gdfLbReachable0_[iCol]++; if (val == 1) - gdfLbReachable1_[iCol].insert(iRow); + gdfLbReachable1_[iCol]++; } } } @@ -1103,13 +1103,15 @@ void HighsDomain::DualfixingProbingPropagation::updateGDFInfo(HighsInt probing_v if (lower_bound_reachable) { considered = true; // printf("Probing on x-%d = %d, for non-zero (%d, %d) = %f, rhs = %f, demonstrate lb reachable.\n", probing_variable, val, iRow, iCol, iValue, mipsolver->model_->row_upper_[iRow]); - if (iCol == probing_variable && val == 1) - gdfLbReachable_[iCol].insert(iRow); + if (iCol == probing_variable && val == 1) { + gdfLbReachable0_[iCol]++; + gdfLbReachable1_[iCol]++; + } else { if (val == 0) - gdfLbReachable0_[iCol].insert(iRow); + gdfLbReachable0_[iCol]++; if (val == 1) - gdfLbReachable1_[iCol].insert(iRow); + gdfLbReachable1_[iCol]++; } } } @@ -1123,13 +1125,15 @@ void HighsDomain::DualfixingProbingPropagation::updateGDFInfo(HighsInt probing_v if (upper_bound_reachable) { considered = true; // printf("Probing on x-%d = %d, for non-zero (%d, %d) = %f, lhs = %f, demonstrate ub reachable.\n", probing_variable, val, iRow, iCol, iValue, mipsolver->model_->row_lower_[iRow]); - if (iCol == probing_variable && val == 0) - gdfUbReachable_[iCol].insert(iRow); + if (iCol == probing_variable && val == 0) { + gdfUbReachable0_[iCol]++; + gdfUbReachable1_[iCol]++; + } else { if (val == 0) - gdfUbReachable0_[iCol].insert(iRow); + gdfUbReachable0_[iCol]++; if (val == 1) - gdfUbReachable1_[iCol].insert(iRow); + gdfUbReachable1_[iCol]++; } } } @@ -1142,39 +1146,14 @@ void HighsDomain::DualfixingProbingPropagation::updateGDFInfo(HighsInt probing_v } HighsInt HighsDomain::DualfixingProbingPropagation::processGDFFixing() { - // extract reachable information - auto getIntersection = [&](const std::unordered_set& vec0, - const std::unordered_set& vec1, - std::unordered_set& vReachable) { - if (vec0.empty() || vec1.empty()) - return; - - // always loop in the smaller vector, and search in the larger vector - if (vec0.size() < vec1.size()) { - for (auto it1 = vec0.begin(); it1 != vec0.end(); it1 ++) { - auto it2 = vec1.find(*it1); - if (it2 != vec1.end()) - vReachable.insert(*it1); - } - } - else { - for (auto it1 = vec1.begin(); it1 != vec1.end(); it1 ++) { - auto it2 = vec0.find(*it1); - if (it2 != vec0.end()) - vReachable.insert(*it1); - } - } - }; - std::vector gdfFixingStack_; for (const auto iCol : gdfCandidatesVec_) { - // lower bound reachable - getIntersection(gdfLbReachable0_[iCol], gdfLbReachable1_[iCol], gdfLbReachable_[iCol]); - // upper bound reachable - getIntersection(gdfUbReachable0_[iCol], gdfUbReachable1_[iCol], gdfUbReachable_[iCol]); - // extract fixings - if (ableToFixToLb(iCol) && (HighsInt)gdfLbReachable_[iCol].size() == colLowerLockOriginal_[iCol]) { + const HighsInt lowerLock = colLowerLockOriginal_[iCol]; + const HighsInt upperLock = colUpperLockOriginal_[iCol]; + if (ableToFixToLb(iCol) && lowerLock > 0 && + gdfLbReachable0_[iCol] == lowerLock && + gdfLbReachable1_[iCol] == lowerLock) { HighsDomainChange* thisbchg = new HighsDomainChange; thisbchg->column = iCol; thisbchg->boundtype = HighsBoundType::kUpper; @@ -1182,7 +1161,9 @@ HighsInt HighsDomain::DualfixingProbingPropagation::processGDFFixing() { gdfFixingStack_.push_back(thisbchg); } // a variable cannot be fixed to lb and ub simultaneously - else if (ableToFixToUb(iCol) && (HighsInt)gdfUbReachable_[iCol].size() == colUpperLockOriginal_[iCol]) { + else if (ableToFixToUb(iCol) && upperLock > 0 && + gdfUbReachable0_[iCol] == upperLock && + gdfUbReachable1_[iCol] == upperLock) { HighsDomainChange* thisbchg = new HighsDomainChange; thisbchg->column = iCol; thisbchg->boundtype = HighsBoundType::kLower; @@ -1211,18 +1192,18 @@ HighsInt HighsDomain::DualfixingProbingPropagation::processGDFFixing() { } void HighsDomain::DualfixingProbingPropagation::clearGDFInfo() { - for (const auto x : gdfCandidatesVec_) + // Reset the per-column count vectors for every column that received at + // least one increment this round. The touched set is exactly + // gdfCandidatesVec_ (every counted column is also a candidate), so we + // reset both with one fused loop. + for (const auto x : gdfCandidatesVec_) { + gdfLbReachable0_[x] = 0; + gdfLbReachable1_[x] = 0; + gdfUbReachable0_[x] = 0; + gdfUbReachable1_[x] = 0; gdfCandidatesFlag_[x] = false; + } gdfCandidatesVec_.clear(); - - gdfLbReachable0_.clear(); - gdfUbReachable0_.clear(); - gdfLbReachable1_.clear(); - gdfUbReachable1_.clear(); -} - -HighsInt HighsDomain::DualfixingProbingPropagation::finalRoundGDF() { - ; } namespace highs { diff --git a/highs/mip/HighsDomain.h b/highs/mip/HighsDomain.h index acc66372a38..d59ce0fe980 100644 --- a/highs/mip/HighsDomain.h +++ b/highs/mip/HighsDomain.h @@ -266,12 +266,22 @@ class HighsDomain { std::vector gdfCandidatesVec_; std::vector gdfCandidatesFlag_; - std::unordered_map> gdfLbReachable0_; - std::unordered_map> gdfLbReachable1_; - std::unordered_map> gdfUbReachable0_; - std::unordered_map> gdfUbReachable1_; - std::unordered_map> gdfLbReachable_; - std::unordered_map> gdfUbReachable_; + // GDF reachable-row counts, indexed directly by column id. For each + // column touched during GDF, we only need to know how many redundant + // rows make the column's lower/upper bound reachable under probing + // x_probing=0 / x_probing=1. The actual row indices are not needed: + // (a) within a single (map, column) the row ids are unique (each + // redundant row visits each column at most once), so the set of rows + // is fully described by its size; (b) the original intersection check + // |set0 ∩ set1| == |locking rows| is equivalent to + // |set0| == |locking rows| AND |set1| == |locking rows| because both + // sets are subsets of the locking rows. processGDFFixing therefore + // does no intersection at all. Indexed by column id (dense) so a + // flat vector beats an unordered_map here. + std::vector gdfLbReachable0_; + std::vector gdfLbReachable1_; + std::vector gdfUbReachable0_; + std::vector gdfUbReachable1_; void enablePropagator() { enabled_ = true; @@ -354,7 +364,6 @@ class HighsDomain { void updateGDFInfo(HighsInt probing_variable, bool val); HighsInt processGDFFixing(); - HighsInt finalRoundGDF(); void clearGDFInfo(); }; diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index 0b16e0d7875..1056c02e816 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1844,8 +1844,6 @@ HPresolve::Result HPresolve::runProbing(HighsPostsolveStack& postsolve_stack) { } } - if (options->presolve_gdf) - domain.getDfProbingPropagation().finalRoundGDF(); // finalise probing HighsInt numVarsFixed = 0; HighsInt numBndsTightened = 0; From 3e34d3b3a83ee010cdddf68d9a518c2854294000 Mon Sep 17 00:00:00 2001 From: Zhaowei-Wang Date: Mon, 10 Aug 2026 20:01:58 +0800 Subject: [PATCH 21/25] Substitute char with HighsBool --- highs/mip/HighsDomain.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/highs/mip/HighsDomain.h b/highs/mip/HighsDomain.h index 04d59c7bd34..3b2cfd874d2 100644 --- a/highs/mip/HighsDomain.h +++ b/highs/mip/HighsDomain.h @@ -240,7 +240,7 @@ class HighsDomain { HighsDomain* domain; HighsMipSolver* mipsolver; // row lower and upper, length = 2 * rownum - std::vector redundantPropagateFlag_; + std::vector redundantPropagateFlag_; std::vector redundantPropagateVec_; enum DFPROBING_FIX_DIRECTION { @@ -261,11 +261,11 @@ class HighsDomain { std::vector colLowerLockReduced_; std::vector colUpperLockReduced_; std::vector candidatesVec_; - std::vector candidatesFlag_; + std::vector candidatesFlag_; std::unordered_set lockNeedClear_; std::vector gdfCandidatesVec_; - std::vector gdfCandidatesFlag_; + std::vector gdfCandidatesFlag_; // GDF reachable-row counts, indexed directly by column id. For each // column touched during GDF, we only need to know how many redundant // rows make the column's lower/upper bound reachable under probing From 6e4f70c5c7a05526d80554e9afe14d7760009c9e Mon Sep 17 00:00:00 2001 From: Zhaowei-Wang Date: Mon, 10 Aug 2026 23:11:24 +0800 Subject: [PATCH 22/25] Update comments --- highs/mip/HighsDomain.cpp | 62 ++++++++++++--------------------- highs/mip/HighsDomain.h | 33 ++++++++++-------- highs/mip/HighsImplications.cpp | 12 ++++--- highs/mip/HighsImplications.h | 15 ++++---- highs/presolve/HPresolve.cpp | 1 + 5 files changed, 56 insertions(+), 67 deletions(-) diff --git a/highs/mip/HighsDomain.cpp b/highs/mip/HighsDomain.cpp index df6bb91cd1f..eb8b4acb88e 100644 --- a/highs/mip/HighsDomain.cpp +++ b/highs/mip/HighsDomain.cpp @@ -686,7 +686,8 @@ void HighsDomain::DualfixingProbingPropagation::recomputeLocks() { gdfLbReachable1_.assign(mipsolver->numCol(), 0); gdfUbReachable0_.assign(mipsolver->numCol(), 0); gdfUbReachable1_.assign(mipsolver->numCol(), 0); - + + // compute the original locks for each variable const auto model = mipsolver->model_; for (HighsInt iCol = 0; iCol < model->a_matrix_.num_col_; iCol ++) { for (HighsInt k = model->a_matrix_.start_[iCol]; k < model->a_matrix_.start_[iCol + 1]; k ++) { @@ -729,30 +730,18 @@ void HighsDomain::DualfixingProbingPropagation::updateLhsRedundant(HighsInt row) void HighsDomain::DualfixingProbingPropagation::propagate() { - // The boolean variable ``startZeroCostFixing_'' is used to flag if we allow variables with zero cost objective coefficients can be fixed in domain propagation. + // The boolean variable ``startZeroCostFixing_'' is used to flag if we allow variables with zero cost can be fixed in domain propagation. // The process of domain propagtion in probing is executed in two phases: - // Phase 1: Apply classic domain propagation, and additionally fix variables with nonzero objective coefficients using dual fixing + // Phase 1: Apply classic domain propagation, and additionally fix variables with non-zero objective coefficients using dual fixing // Phase 2: Apply classic domain propagation, and additionally fix variables (including those with zero objective coefficients) using dual fixing // In Phase 1, ``startZeroCostFixing_'' is set to be ``false'' to exclude variable with zero objective coefficients. // In Phase 2, ``startZeroCostFixing_'' is set to be ``true''. // Note that // (1) For all the bound changes in Phase 1, reductions deduced from them are valid for all optimal solutions; - // (2) For the bound changes in Phase 2, reductions deduced from them can only be used to derive global valid reductions (i.e., variable fixing, global bound tightening, variable substitution). + // (2) For the bound changes in Phase 2, reductions deduced from them can only be used to derive global valid reductions (i.e., variable fixing, global bound tightening, and variable substitution). if (!isEnabled()) return; - // printf("%f, %f\n", domain->getMaxActivity(1001), domain->getMinActivity(1001)); -// #ifndef NDEBUG - for (const HighsInt x : redundantPropagateVec_) { - HighsInt iRow = x / 2; - bool isUpper = x % 2; - if (isUpper && domain->getMaxActivity(iRow) > mipsolver->model_->row_upper_[iRow] + domain->feastol()) - printf("Row %d not rhs redundant, maxAct = %f, rhs = %f.\n", iRow, domain->getMaxActivity(iRow), mipsolver->model_->row_upper_[iRow]); - if (!isUpper && domain->getMinActivity(iRow) < mipsolver->model_->row_lower_[iRow] - domain->feastol()) - printf("Row %d not lhs redundant, minAct = %f, lhs = %f.\n", iRow, domain->getMinActivity(iRow), mipsolver->model_->row_lower_[iRow]); - } -// #endif - assert(candidatesVec_.empty()); vector domainchangeDFProbing; @@ -766,6 +755,7 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { } }; + // debug functions to check locks auto checkVariableLowerLock = [&](HighsInt iCol) { auto model = mipsolver->model_; if (ableToFixToLb(iCol)) { @@ -779,11 +769,6 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { std::cout << "Lower lock: variable " << iCol << " at row = " << iRow << " coef = " << iValue << " not redundant at constraint " << iRow << ", minact = " << domain->getMinActivity(iRow) << ", maxact = " << domain->getMaxActivity(iRow) << " lhs = " << blower << " rhs = " << bupper << std::endl; - // std::cout << "lock rows:\n"; - // for (int kk = model->a_matrix_.start_[iCol]; kk < model->a_matrix_.start_[iCol + 1]; kk ++) { - // std::cout << kk << " "; - // } - // std::cout << std::endl; } } } @@ -823,6 +808,7 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { domainchangeDFProbing.push_back(thisbchg); }; + // only record - we do not actually fix them now as their objective coefficients are zero auto collectFixLower = [&](int iCol) { zeroCostFixedVariables_.emplace_back(iCol, FIXDIRECTION_LOWER_BOUND); }; @@ -831,12 +817,11 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { zeroCostFixedVariables_.emplace_back(iCol, FIXDIRECTION_UPPER_BOUND); }; - - - // get candidate + // exit if no new redundant constraints are found HighsInt maxLockLeft = redundantPropagateVec_.size() - previousSize_; if (maxLockLeft == 0) return; + for (; previousSize_ < redundantPropagateVec_.size(); ++ previousSize_, -- maxLockLeft) { const HighsInt i = redundantPropagateVec_[previousSize_]; const HighsInt iRow = i / 2; @@ -852,6 +837,7 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { const double iValue = mipsolver->mipdata_->ARvalue_[k]; const double cost = mipsolver->model_->col_cost_[iCol]; + // do not insert to candidates if the lock is not reduced enough bool lowerNoInsert = colLowerLockReduced_[iCol] + maxLockLeft < colLowerLockOriginal_[iCol]; bool upperNoInsert = colUpperLockReduced_[iCol] + maxLockLeft < colUpperLockOriginal_[iCol]; @@ -880,6 +866,7 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { const double iValue = mipsolver->mipdata_->ARvalue_[k]; const double cost = mipsolver->model_->col_cost_[iCol]; + // do not insert to candidates if the lock is not reduced enough bool lowerNoInsert = colLowerLockReduced_[iCol] + maxLockLeft < colLowerLockOriginal_[iCol]; bool upperNoInsert = colUpperLockReduced_[iCol] + maxLockLeft < colUpperLockOriginal_[iCol]; @@ -978,7 +965,6 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { } - // if (mipsolver->model_->col_cost_[iCol] >= mipsolver->options_mip_->dual_feasibility_tolerance) { if (mipsolver->model_->col_cost_[iCol] >= mipsolver->options_mip_->dual_feasibility_tolerance) { if (canBeFixedToLower) { // checkVariableLowerLock(iCol); @@ -986,7 +972,7 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { continue; } } - // if (mipsolver->model_->col_cost_[iCol] <= mipsolver->options_mip_->dual_feasibility_tolerance) { + if (mipsolver->model_->col_cost_[iCol] <= mipsolver->options_mip_->dual_feasibility_tolerance) { if (canBeFixedToUpper) { // checkVariableUpperLock(iCol); @@ -1009,14 +995,13 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { delete domainchangeDFProbing[j]; } - // std::cout << "#Bchg = " << j << std::endl; - + // clear the remaining domain changes if infeasible + assert(domain->infeasible_); for (j ++; j < domainchangeDFProbing.size(); ++ j) { - assert(domain->infeasible_); delete domainchangeDFProbing[j]; } - // record the current number of redundant constraints. + // record the current number of redundant constraints previousSize_ = redundantPropagateVec_.size(); } @@ -1031,6 +1016,7 @@ void HighsDomain::DualfixingProbingPropagation::updateGDFInfo(HighsInt probing_v } }; + // only redundant constraints are useful in GDF for (const auto x : redundantPropagateVec_) { const HighsInt iRow = x / 2; const bool isRhs = x % 2; @@ -1055,7 +1041,7 @@ void HighsDomain::DualfixingProbingPropagation::updateGDFInfo(HighsInt probing_v domain->getMaxActivity(iRow) + iValue * (globalUb - probingUb) <= mipsolver->model_->row_upper_[iRow] + domain->feastol(); if (upper_bound_reachable) { considered = true; - // printf("Probing on x-%d = %d, for non-zero (%d, %d) = %f, rhs = %f, demonstrate ub reachable.\n", probing_variable, val, iRow, iCol, iValue, mipsolver->model_->row_upper_[iRow]); + // special treat if the current variable is the probing variable if (iCol == probing_variable && val == 0) { gdfUbReachable0_[iCol]++; gdfUbReachable1_[iCol]++; @@ -1077,7 +1063,7 @@ void HighsDomain::DualfixingProbingPropagation::updateGDFInfo(HighsInt probing_v domain->getMinActivity(iRow) + iValue * (globalLb - probingLb) >= mipsolver->model_->row_lower_[iRow] - domain->feastol(); if (lower_bound_reachable) { considered = true; - // printf("Probing on x-%d = %d, for non-zero (%d, %d) = %f, lhs = %f, demonstrate lb reachable.\n", probing_variable, val, iRow, iCol, iValue, mipsolver->model_->row_lower_[iRow]); + // special treat if the current variable is the probing variable if (iCol == probing_variable && val == 1) { gdfLbReachable0_[iCol]++; gdfLbReachable1_[iCol]++; @@ -1102,7 +1088,7 @@ void HighsDomain::DualfixingProbingPropagation::updateGDFInfo(HighsInt probing_v domain->getMaxActivity(iRow) + iValue * (globalLb - probingLb) <= mipsolver->model_->row_upper_[iRow] + domain->feastol(); if (lower_bound_reachable) { considered = true; - // printf("Probing on x-%d = %d, for non-zero (%d, %d) = %f, rhs = %f, demonstrate lb reachable.\n", probing_variable, val, iRow, iCol, iValue, mipsolver->model_->row_upper_[iRow]); + // special treat if the current variable is the probing variable if (iCol == probing_variable && val == 1) { gdfLbReachable0_[iCol]++; gdfLbReachable1_[iCol]++; @@ -1124,7 +1110,7 @@ void HighsDomain::DualfixingProbingPropagation::updateGDFInfo(HighsInt probing_v domain->getMinActivity(iRow) + iValue * (globalUb - probingUb) >= mipsolver->model_->row_lower_[iRow] - domain->feastol(); if (upper_bound_reachable) { considered = true; - // printf("Probing on x-%d = %d, for non-zero (%d, %d) = %f, lhs = %f, demonstrate ub reachable.\n", probing_variable, val, iRow, iCol, iValue, mipsolver->model_->row_lower_[iRow]); + // special treat if the current variable is the probing variable if (iCol == probing_variable && val == 0) { gdfUbReachable0_[iCol]++; gdfUbReachable1_[iCol]++; @@ -1148,6 +1134,7 @@ void HighsDomain::DualfixingProbingPropagation::updateGDFInfo(HighsInt probing_v HighsInt HighsDomain::DualfixingProbingPropagation::processGDFFixing() { std::vector gdfFixingStack_; + // derive global fixings from the GDF information for (const auto iCol : gdfCandidatesVec_) { const HighsInt lowerLock = colLowerLockOriginal_[iCol]; const HighsInt upperLock = colUpperLockOriginal_[iCol]; @@ -1179,23 +1166,18 @@ HighsInt HighsDomain::DualfixingProbingPropagation::processGDFFixing() { delete gdfFixingStack_[j]; } + // clear the remaining domain changes if infeasible for (; j < gdfFixingStack_.size(); ++ j) { assert(domain->infeasible_); delete gdfFixingStack_[j]; } gdfFixingStack_.clear(); - if (j > 0) - std::cout << "GDF find " << j << " fixings.\n"; return (HighsInt)j; } void HighsDomain::DualfixingProbingPropagation::clearGDFInfo() { - // Reset the per-column count vectors for every column that received at - // least one increment this round. The touched set is exactly - // gdfCandidatesVec_ (every counted column is also a candidate), so we - // reset both with one fused loop. for (const auto x : gdfCandidatesVec_) { gdfLbReachable0_[x] = 0; gdfLbReachable1_[x] = 0; diff --git a/highs/mip/HighsDomain.h b/highs/mip/HighsDomain.h index 3b2cfd874d2..c7ad93767b5 100644 --- a/highs/mip/HighsDomain.h +++ b/highs/mip/HighsDomain.h @@ -239,10 +239,12 @@ class HighsDomain { struct DualfixingProbingPropagation { HighsDomain* domain; HighsMipSolver* mipsolver; + // row lower and upper, length = 2 * rownum std::vector redundantPropagateFlag_; std::vector redundantPropagateVec_; - + + // For zero-cost variables, we need to know which direction we can fix them to. enum DFPROBING_FIX_DIRECTION { FIXDIRECTION_NOT_DECIDED = 0, FIXDIRECTION_LOWER_BOUND, @@ -250,34 +252,32 @@ class HighsDomain { }; std::vector zeroCostVarsDirection_; std::vector> zeroCostFixedVariables_; + + // Flag and position in the domchgstack of the first zero-cost variable that can be fixed to its lower or upper bound. bool startZeroCostFixing_; size_t zeroCostStartPos_; bool enabled_ = false; size_t previousSize_; + // Original lower and upper locks, and the reduced locks after propagation. std::vector colLowerLockOriginal_; std::vector colUpperLockOriginal_; std::vector colLowerLockReduced_; std::vector colUpperLockReduced_; + + // temporary buffers for DFProbing std::vector candidatesVec_; std::vector candidatesFlag_; std::unordered_set lockNeedClear_; + // temporary buffers for GDF std::vector gdfCandidatesVec_; std::vector gdfCandidatesFlag_; - // GDF reachable-row counts, indexed directly by column id. For each - // column touched during GDF, we only need to know how many redundant - // rows make the column's lower/upper bound reachable under probing - // x_probing=0 / x_probing=1. The actual row indices are not needed: - // (a) within a single (map, column) the row ids are unique (each - // redundant row visits each column at most once), so the set of rows - // is fully described by its size; (b) the original intersection check - // |set0 ∩ set1| == |locking rows| is equivalent to - // |set0| == |locking rows| AND |set1| == |locking rows| because both - // sets are subsets of the locking rows. processGDFFixing therefore - // does no intersection at all. Indexed by column id (dense) so a - // flat vector beats an unordered_map here. + + // GDF reachable-row counts, indexed by column id. For each + // variable touched during probing, we only need to know how many + // rows make this variable lower/upper bound reachable. std::vector gdfLbReachable0_; std::vector gdfLbReachable1_; std::vector gdfUbReachable0_; @@ -295,10 +295,12 @@ class HighsDomain { return enabled_; } + // active only when new redundant rows are found. bool isActive() { return enabled_ && redundantPropagateVec_.size() > previousSize_; } + // mark the position when the first zero-cost variable can be fixed to its lower or upper bound. void setZeroCostFixingPosition(HighsInt v) { zeroCostStartPos_ = v; } @@ -329,7 +331,7 @@ class HighsDomain { && mipsolver->model_->col_upper_[col] < kHighsInf; } - + // remove redundant information void clearRedundantInfo() { previousSize_ = 0; if (!redundantPropagateVec_.empty()) { // clear buffers @@ -361,7 +363,8 @@ class HighsDomain { void updateRhsRedundant(HighsInt row); void updateLhsRedundant(HighsInt row); void propagate(); - + + // functionalities for GDF void updateGDFInfo(HighsInt probing_variable, bool val); HighsInt processGDFFixing(); void clearGDFInfo(); diff --git a/highs/mip/HighsImplications.cpp b/highs/mip/HighsImplications.cpp index cf5ed640a01..55b2f358f42 100644 --- a/highs/mip/HighsImplications.cpp +++ b/highs/mip/HighsImplications.cpp @@ -27,8 +27,10 @@ bool HighsImplications::computeImplications(HighsInt col, bool val) { const auto& domchgreason = globaldomain.getDomainChangeReason(); size_t changedend = globaldomain.getChangedCols().size(); + // get two flags const bool useDFProbing = globaldomain.inProbing_ && mipsolver.options_mip_->presolve_dfprobing; const bool useGDF = globaldomain.inProbing_ && mipsolver.options_mip_->presolve_gdf; + // record redundant rows if any of the two flags is true if (useDFProbing || useGDF) { globaldomain.getDfProbingPropagation().clearRedundantInfo(); globaldomain.getDfProbingPropagation().enablePropagator(); @@ -100,7 +102,7 @@ bool HighsImplications::computeImplications(HighsInt col, bool val) { ((domchgreason[i].index >> 1) == col || numEntries >= maxEntries)) continue; - if (i >= tentativeStart) // cache tentative implications + if (i >= tentativeStart) // record tentative implications continue; implics.push_back(domchgstack[i]); @@ -122,7 +124,7 @@ bool HighsImplications::computeImplications(HighsInt col, bool val) { [&](const HighsDomainChange& a) { return !globaldomain.isBinary(a.column); }); - // Store the tentative bound changes (fixing) of binary variables separately + // store the tentative bound changes of binary variables separately for (auto i = binstart_tmp; i != implics_tentative.end(); ++ i) recordTentativeCliques(val, *i); implics_tentative.erase(binstart_tmp, implics_tentative.end()); @@ -374,11 +376,11 @@ bool HighsImplications::runProbing(HighsInt col, HighsInt& numReductions) { // Skip non-binary variables (being fixed now) or those can be substituted by other binary variables if (!globaldomain.isBinary(k) || colsubstituted[k]) continue; - // Return if the whole problem is infeasible + // Return if infeasible if (globaldomain.infeasible()) return true; // Get the information how x[k] is fixed in probing on x[col] = 0 and x[col] = 1 - // For the meaning of ``data'', please see lines 71-82 in HighsImplications.h + // For the meaning of ``data'', please see lines 71-89 in HighsImplications.h uint8_t data = binaryInvolvedFlags_[k]; if (data == 0) // flag for no reduction continue; @@ -503,6 +505,7 @@ bool HighsImplications::runProbing(HighsInt col, HighsInt& numReductions) { } } + // clear tentative implications if (haveTentativeImplics_zero) implications[2 * col].implics_tentative.clear(); if (haveTentativeImplics_one) @@ -511,6 +514,7 @@ bool HighsImplications::runProbing(HighsInt col, HighsInt& numReductions) { if (useGDF) { // fix variables using generalized dual fixing HighsInt nfix = globaldomain.getDfProbingPropagation().processGDFFixing(); + // propagate if necessary if (nfix > 0) globaldomain.propagate(); } diff --git a/highs/mip/HighsImplications.h b/highs/mip/HighsImplications.h index 0f42349e08b..424bc2a259e 100644 --- a/highs/mip/HighsImplications.h +++ b/highs/mip/HighsImplications.h @@ -25,7 +25,7 @@ class HighsImplications { struct Implics { std::vector implics; - /* the "tentative" implications. + /* The "tentative" implications: A implication of type x_j \ge (\ell^1_j - \ell^0_j) x_k + \ell^0_j is called "tentative", if (1) c_j = 0 (2) x_j is fixed by applying dual fixing in probing @@ -66,14 +66,14 @@ class HighsImplications { std::vector substitutions; std::vector colsubstituted; - // if a binary variable x_j is: (1) c_j = 0 (2) x_j is fixed by applying dual fixing in probing + // vector used to derive global reductions from dfprobing std::vector binaryInvolvedInds_; enum binaryFixType { - kNoReduction = 0b0000, - kGlobalLower = 0b1010, - kGlobalUpper = 0b0101, + kNoReduction = 0b0000, + kGlobalLower = 0b1010, + kGlobalUpper = 0b0101, kSubstituteComplement = 0b1001, - kSubstituteEqual = 0b0110, + kSubstituteEqual = 0b0110, }; /* Possible values for binaryInvolvedFlags_ @@ -153,7 +153,6 @@ class HighsImplications { return implications[loc].implics; } - // get the "tentative implications" w.r.t non-binary variables const std::vector& getImplications_tentative(HighsInt col, bool val) { HighsInt loc = 2 * col + val; return implications[loc].implics_tentative; @@ -257,7 +256,7 @@ class HighsImplications { } } } - else { + else { // probing x_k = 1 if (bchg.boundtype == HighsBoundType::kLower) { // fixed to 1 if (!isFixedTo1(val, iCol)) { if (binaryInvolvedFlags_[iCol] == 0) diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index ee0b1f9d7e6..93201952049 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -1863,6 +1863,7 @@ HPresolve::Result HPresolve::runProbing(HighsPostsolveStack& postsolve_stack) { } }; + // setup for dfprobing and gdf if (options->presolve_dfprobing || options->presolve_gdf) domain.getDfProbingPropagation().recomputeLocks(); From e87c31a94d53fd2404a132f04d69d93f600e69e9 Mon Sep 17 00:00:00 2001 From: Zhaowei-Wang Date: Mon, 10 Aug 2026 23:32:13 +0800 Subject: [PATCH 23/25] fix assert --- highs/mip/HighsDomain.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/highs/mip/HighsDomain.cpp b/highs/mip/HighsDomain.cpp index eb8b4acb88e..0b0cf871fed 100644 --- a/highs/mip/HighsDomain.cpp +++ b/highs/mip/HighsDomain.cpp @@ -996,8 +996,8 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { } // clear the remaining domain changes if infeasible - assert(domain->infeasible_); for (j ++; j < domainchangeDFProbing.size(); ++ j) { + assert(domain->infeasible_); delete domainchangeDFProbing[j]; } From 83d2ba05550ba8a1c7b6e494ab76e7fe2a758b92 Mon Sep 17 00:00:00 2001 From: Zhaowei-Wang Date: Tue, 11 Aug 2026 01:28:53 +0800 Subject: [PATCH 24/25] fix test: lifting-for-probing, and clear information in recomputeLocks() --- highs/mip/HighsDomain.cpp | 22 ++++++++++++++-------- highs/mip/HighsDomain.h | 2 +- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/highs/mip/HighsDomain.cpp b/highs/mip/HighsDomain.cpp index 0b0cf871fed..a8b0eb0082d 100644 --- a/highs/mip/HighsDomain.cpp +++ b/highs/mip/HighsDomain.cpp @@ -677,8 +677,10 @@ void HighsDomain::DualfixingProbingPropagation::recomputeLocks() { candidatesVec_.clear(); candidatesVec_.reserve(mipsolver->numCol()); candidatesFlag_.assign(mipsolver->numCol(), false); + lockNeedClear_.clear(); lockNeedClear_.reserve(mipsolver->numCol()); + gdfCandidatesVec_.clear(); gdfCandidatesVec_.reserve(mipsolver->numCol()); gdfCandidatesFlag_.assign(mipsolver->numCol(), false); @@ -2105,8 +2107,9 @@ void HighsDomain::updateActivityLbChange(HighsInt col, double oldbound, assert(tmpinf == activitymininf_[mip->a_matrix_.index_[i]]); } #endif - - if (recordRedundantRows_ && + // If dfprobingPropagation.isZeroObjFixingEnabled() is true, + // then we cannot record redundant rows for lifting, as this bound change could disregarded. + if (recordRedundantRows_ && !dfprobingPropagation.isZeroObjFixingEnabled() && mip->row_lower_[mip->a_matrix_.index_[i]] != -kHighsInf && mip->row_upper_[mip->a_matrix_.index_[i]] == kHighsInf) updateRedundantRows(mip->a_matrix_.index_[i]); @@ -2157,8 +2160,9 @@ void HighsDomain::updateActivityLbChange(HighsInt col, double oldbound, assert(tmpinf == activitymaxinf_[mip->a_matrix_.index_[i]]); } #endif - - if (recordRedundantRows_ && + // If dfprobingPropagation.isZeroObjFixingEnabled() is true, + // then we cannot record redundant rows for lifting, as this bound change could disregarded. + if (recordRedundantRows_ && !dfprobingPropagation.isZeroObjFixingEnabled() && mip->row_lower_[mip->a_matrix_.index_[i]] == -kHighsInf && mip->row_upper_[mip->a_matrix_.index_[i]] != kHighsInf) updateRedundantRows(mip->a_matrix_.index_[i]); @@ -2278,8 +2282,9 @@ void HighsDomain::updateActivityUbChange(HighsInt col, double oldbound, assert(tmpinf == activitymaxinf_[mip->a_matrix_.index_[i]]); } #endif - - if (recordRedundantRows_ && + // If dfprobingPropagation.isZeroObjFixingEnabled() is true, + // then we cannot record redundant rows for lifting, as this bound change could disregarded. + if (recordRedundantRows_ && !dfprobingPropagation.isZeroObjFixingEnabled() && mip->row_lower_[mip->a_matrix_.index_[i]] == -kHighsInf && mip->row_upper_[mip->a_matrix_.index_[i]] != kHighsInf) updateRedundantRows(mip->a_matrix_.index_[i]); @@ -2333,8 +2338,9 @@ void HighsDomain::updateActivityUbChange(HighsInt col, double oldbound, assert(tmpinf == activitymininf_[mip->a_matrix_.index_[i]]); } #endif - - if (recordRedundantRows_ && + // If dfprobingPropagation.isZeroObjFixingEnabled() is true, + // then we cannot record redundant rows for lifting, as this bound change could disregarded. + if (recordRedundantRows_ && !dfprobingPropagation.isZeroObjFixingEnabled() && mip->row_lower_[mip->a_matrix_.index_[i]] != -kHighsInf && mip->row_upper_[mip->a_matrix_.index_[i]] == kHighsInf) updateRedundantRows(mip->a_matrix_.index_[i]); diff --git a/highs/mip/HighsDomain.h b/highs/mip/HighsDomain.h index c7ad93767b5..49f80546c1f 100644 --- a/highs/mip/HighsDomain.h +++ b/highs/mip/HighsDomain.h @@ -254,7 +254,7 @@ class HighsDomain { std::vector> zeroCostFixedVariables_; // Flag and position in the domchgstack of the first zero-cost variable that can be fixed to its lower or upper bound. - bool startZeroCostFixing_; + bool startZeroCostFixing_ = false; size_t zeroCostStartPos_; bool enabled_ = false; From 0385e82e74c59eb0bdf6a8466227a6d678205b16 Mon Sep 17 00:00:00 2001 From: Zhaowei-Wang Date: Tue, 11 Aug 2026 12:03:56 +0800 Subject: [PATCH 25/25] fix test: clang-format --- highs/lp_data/HighsOptions.h | 16 +- highs/mip/HighsDomain.cpp | 430 ++++++++++++++++++-------------- highs/mip/HighsDomain.h | 74 +++--- highs/mip/HighsImplications.cpp | 123 +++++---- highs/mip/HighsImplications.h | 62 ++--- 5 files changed, 375 insertions(+), 330 deletions(-) diff --git a/highs/lp_data/HighsOptions.h b/highs/lp_data/HighsOptions.h index 8b8ecd45386..06298d71445 100644 --- a/highs/lp_data/HighsOptions.h +++ b/highs/lp_data/HighsOptions.h @@ -1765,19 +1765,17 @@ class HighsOptions : public HighsOptionsStruct { advanced, ¢ring_ratio_tolerance, 0, 100, kHighsInf); records.push_back(record_double); - record_bool = - new OptionRecordBool("presolve_dfprobing", - "Use the dual fixing aumgented probing technique in presolve", advanced, - &presolve_dfprobing, true); + record_bool = new OptionRecordBool( + "presolve_dfprobing", + "Use the dual fixing aumgented probing technique in presolve", advanced, + &presolve_dfprobing, true); records.push_back(record_bool); - record_bool = - new OptionRecordBool("presolve_gdf", - "Use the generalized dual fixing technique in presolve", advanced, - &presolve_gdf, true); + record_bool = new OptionRecordBool( + "presolve_gdf", "Use the generalized dual fixing technique in presolve", + advanced, &presolve_gdf, true); records.push_back(record_bool); - // Set up the log_options aliases log_options.clear(); log_options.log_stream = diff --git a/highs/mip/HighsDomain.cpp b/highs/mip/HighsDomain.cpp index a8b0eb0082d..ec7c698c27a 100644 --- a/highs/mip/HighsDomain.cpp +++ b/highs/mip/HighsDomain.cpp @@ -638,31 +638,35 @@ void HighsDomain::CutpoolPropagation::updateActivityUbChange( } } -HighsDomain::DualfixingProbingPropagation::DualfixingProbingPropagation(const DualfixingProbingPropagation& other) - : redundantPropagateFlag_(other.redundantPropagateFlag_), - redundantPropagateVec_(other.redundantPropagateVec_), - zeroCostVarsDirection_(other.zeroCostVarsDirection_), - zeroCostFixedVariables_(other.zeroCostFixedVariables_), - colLowerLockOriginal_(other.colLowerLockOriginal_), - colUpperLockOriginal_(other.colUpperLockOriginal_), - colLowerLockReduced_(other.colLowerLockReduced_), - colUpperLockReduced_(other.colUpperLockReduced_), - candidatesVec_(other.candidatesVec_), - candidatesFlag_(other.candidatesFlag_), - lockNeedClear_(other.lockNeedClear_), - gdfCandidatesVec_(other.gdfCandidatesVec_), - gdfCandidatesFlag_(other.gdfCandidatesFlag_), - gdfLbReachable0_(other.gdfLbReachable0_), - gdfLbReachable1_(other.gdfLbReachable1_), - gdfUbReachable0_(other.gdfUbReachable0_), - gdfUbReachable1_(other.gdfUbReachable1_) {;} +HighsDomain::DualfixingProbingPropagation::DualfixingProbingPropagation( + const DualfixingProbingPropagation& other) + : redundantPropagateFlag_(other.redundantPropagateFlag_), + redundantPropagateVec_(other.redundantPropagateVec_), + zeroCostVarsDirection_(other.zeroCostVarsDirection_), + zeroCostFixedVariables_(other.zeroCostFixedVariables_), + colLowerLockOriginal_(other.colLowerLockOriginal_), + colUpperLockOriginal_(other.colUpperLockOriginal_), + colLowerLockReduced_(other.colLowerLockReduced_), + colUpperLockReduced_(other.colUpperLockReduced_), + candidatesVec_(other.candidatesVec_), + candidatesFlag_(other.candidatesFlag_), + lockNeedClear_(other.lockNeedClear_), + gdfCandidatesVec_(other.gdfCandidatesVec_), + gdfCandidatesFlag_(other.gdfCandidatesFlag_), + gdfLbReachable0_(other.gdfLbReachable0_), + gdfLbReachable1_(other.gdfLbReachable1_), + gdfUbReachable0_(other.gdfUbReachable0_), + gdfUbReachable1_(other.gdfUbReachable1_) { + ; +} void HighsDomain::DualfixingProbingPropagation::recomputeLocks() { mipsolver = domain->mipsolver; redundantPropagateFlag_.assign(2 * mipsolver->numRow(), false); redundantPropagateVec_.clear(); redundantPropagateVec_.reserve(2 * mipsolver->numRow()); - zeroCostVarsDirection_.assign(2 * mipsolver->numCol(), FIXDIRECTION_NOT_DECIDED); + zeroCostVarsDirection_.assign(2 * mipsolver->numCol(), + FIXDIRECTION_NOT_DECIDED); zeroCostFixedVariables_.clear(); zeroCostFixedVariables_.reserve(2 * mipsolver->numCol()); @@ -688,61 +692,71 @@ void HighsDomain::DualfixingProbingPropagation::recomputeLocks() { gdfLbReachable1_.assign(mipsolver->numCol(), 0); gdfUbReachable0_.assign(mipsolver->numCol(), 0); gdfUbReachable1_.assign(mipsolver->numCol(), 0); - + // compute the original locks for each variable const auto model = mipsolver->model_; - for (HighsInt iCol = 0; iCol < model->a_matrix_.num_col_; iCol ++) { - for (HighsInt k = model->a_matrix_.start_[iCol]; k < model->a_matrix_.start_[iCol + 1]; k ++) { + for (HighsInt iCol = 0; iCol < model->a_matrix_.num_col_; iCol++) { + for (HighsInt k = model->a_matrix_.start_[iCol]; + k < model->a_matrix_.start_[iCol + 1]; k++) { const HighsInt iRow = model->a_matrix_.index_[k]; const double iValue = model->a_matrix_.value_[k]; const double lhs = model->row_lower_[iRow], rhs = model->row_upper_[iRow]; if ((iValue > 0 && rhs != kHighsInf) || (iValue < 0 && lhs != -kHighsInf)) - colUpperLockOriginal_[iCol] ++; + colUpperLockOriginal_[iCol]++; if ((iValue > 0 && lhs != -kHighsInf) || (iValue < 0 && rhs != kHighsInf)) - colLowerLockOriginal_[iCol] ++; + colLowerLockOriginal_[iCol]++; } } } -void HighsDomain::DualfixingProbingPropagation::updateRhsRedundant(HighsInt row) { - if (!isEnabled()) - return; +void HighsDomain::DualfixingProbingPropagation::updateRhsRedundant( + HighsInt row) { + if (!isEnabled()) return; - if (domain->activitymaxinf_[row] != 0 || redundantPropagateFlag_[2 * row + 1] || mipsolver->model_->row_upper_[row] == kHighsInf) + if (domain->activitymaxinf_[row] != 0 || + redundantPropagateFlag_[2 * row + 1] || + mipsolver->model_->row_upper_[row] == kHighsInf) return; - if (domain->getMaxActivity(row) <= mipsolver->model_->row_upper_[row] + mipsolver->mipdata_->feastol) { + if (domain->getMaxActivity(row) <= + mipsolver->model_->row_upper_[row] + mipsolver->mipdata_->feastol) { redundantPropagateVec_.push_back(2 * row + 1); redundantPropagateFlag_[2 * row + 1] = 1; } } -void HighsDomain::DualfixingProbingPropagation::updateLhsRedundant(HighsInt row) { - if (!isEnabled()) - return; +void HighsDomain::DualfixingProbingPropagation::updateLhsRedundant( + HighsInt row) { + if (!isEnabled()) return; - if (domain->activitymininf_[row] != 0 || redundantPropagateFlag_[2 * row] || mipsolver->model_->row_lower_[row] == -kHighsInf) + if (domain->activitymininf_[row] != 0 || redundantPropagateFlag_[2 * row] || + mipsolver->model_->row_lower_[row] == -kHighsInf) return; - if (domain->getMinActivity(row) >= mipsolver->model_->row_lower_[row] - mipsolver->mipdata_->feastol) { + if (domain->getMinActivity(row) >= + mipsolver->model_->row_lower_[row] - mipsolver->mipdata_->feastol) { redundantPropagateVec_.push_back(2 * row); redundantPropagateFlag_[2 * row] = 1; } } - void HighsDomain::DualfixingProbingPropagation::propagate() { - // The boolean variable ``startZeroCostFixing_'' is used to flag if we allow variables with zero cost can be fixed in domain propagation. - // The process of domain propagtion in probing is executed in two phases: - // Phase 1: Apply classic domain propagation, and additionally fix variables with non-zero objective coefficients using dual fixing - // Phase 2: Apply classic domain propagation, and additionally fix variables (including those with zero objective coefficients) using dual fixing - // In Phase 1, ``startZeroCostFixing_'' is set to be ``false'' to exclude variable with zero objective coefficients. - // In Phase 2, ``startZeroCostFixing_'' is set to be ``true''. - // Note that - // (1) For all the bound changes in Phase 1, reductions deduced from them are valid for all optimal solutions; - // (2) For the bound changes in Phase 2, reductions deduced from them can only be used to derive global valid reductions (i.e., variable fixing, global bound tightening, and variable substitution). - if (!isEnabled()) - return; + // The boolean variable ``startZeroCostFixing_'' is used to flag if we allow + // variables with zero cost can be fixed in domain propagation. The process of + // domain propagtion in probing is executed in two phases: + // Phase 1: Apply classic domain propagation, and additionally fix + // variables with non-zero objective coefficients using dual fixing Phase + // 2: Apply classic domain propagation, and additionally fix variables + // (including those with zero objective coefficients) using dual fixing + // In Phase 1, ``startZeroCostFixing_'' is set to be ``false'' to exclude + // variable with zero objective coefficients. In Phase 2, + // ``startZeroCostFixing_'' is set to be ``true''. Note that + // (1) For all the bound changes in Phase 1, reductions deduced from them + // are valid for all optimal solutions; (2) For the bound changes in Phase + // 2, reductions deduced from them can only be used to derive global valid + // reductions (i.e., variable fixing, global bound tightening, and variable + // substitution). + if (!isEnabled()) return; assert(candidatesVec_.empty()); vector domainchangeDFProbing; @@ -761,15 +775,21 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { auto checkVariableLowerLock = [&](HighsInt iCol) { auto model = mipsolver->model_; if (ableToFixToLb(iCol)) { - for (HighsInt k = model->a_matrix_.start_[iCol]; k < model->a_matrix_.start_[iCol + 1]; k ++) { + for (HighsInt k = model->a_matrix_.start_[iCol]; + k < model->a_matrix_.start_[iCol + 1]; k++) { const HighsInt iRow = model->a_matrix_.index_[k]; const double iValue = model->a_matrix_.value_[k]; - const double blower = model->row_lower_[iRow], bupper = model->row_upper_[iRow]; - const bool lhsOk = iValue > 0 && domain->getMinActivity(iRow) >= blower - domain->feastol(); - const bool rhsOk = iValue < 0 && domain->getMaxActivity(iRow) <= bupper + domain->feastol(); + const double blower = model->row_lower_[iRow], + bupper = model->row_upper_[iRow]; + const bool lhsOk = iValue > 0 && domain->getMinActivity(iRow) >= + blower - domain->feastol(); + const bool rhsOk = iValue < 0 && domain->getMaxActivity(iRow) <= + bupper + domain->feastol(); if (!lhsOk && !rhsOk) { - std::cout << "Lower lock: variable " << iCol << " at row = " << iRow << " coef = " << iValue - << " not redundant at constraint " << iRow << ", minact = " << domain->getMinActivity(iRow) << ", maxact = " << domain->getMaxActivity(iRow) + std::cout << "Lower lock: variable " << iCol << " at row = " << iRow + << " coef = " << iValue << " not redundant at constraint " + << iRow << ", minact = " << domain->getMinActivity(iRow) + << ", maxact = " << domain->getMaxActivity(iRow) << " lhs = " << blower << " rhs = " << bupper << std::endl; } } @@ -779,15 +799,21 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { auto checkVariableUpperLock = [&](HighsInt iCol) { auto model = mipsolver->model_; if (ableToFixToUb(iCol)) { - for (HighsInt k = model->a_matrix_.start_[iCol]; k < model->a_matrix_.start_[iCol + 1]; k ++) { + for (HighsInt k = model->a_matrix_.start_[iCol]; + k < model->a_matrix_.start_[iCol + 1]; k++) { const HighsInt iRow = model->a_matrix_.index_[k]; const double iValue = model->a_matrix_.value_[k]; - const double blower = model->row_lower_[iRow], bupper = model->row_upper_[iRow]; - const bool lhsOk = iValue < 0 && domain->getMinActivity(iRow) >= blower - domain->feastol(); - const bool rhsOk = iValue > 0 && domain->getMaxActivity(iRow) <= bupper + domain->feastol(); + const double blower = model->row_lower_[iRow], + bupper = model->row_upper_[iRow]; + const bool lhsOk = iValue < 0 && domain->getMinActivity(iRow) >= + blower - domain->feastol(); + const bool rhsOk = iValue > 0 && domain->getMaxActivity(iRow) <= + bupper + domain->feastol(); if (!lhsOk && !rhsOk) { - std::cout << "Upper lock: variable " << iCol << " at row = " << iRow << " coef = " << iValue - << " not redundant at constraint " << iRow << ", minact = " << domain->getMinActivity(iRow) << ", maxact = " << domain->getMaxActivity(iRow) + std::cout << "Upper lock: variable " << iCol << " at row = " << iRow + << " coef = " << iValue << " not redundant at constraint " + << iRow << ", minact = " << domain->getMinActivity(iRow) + << ", maxact = " << domain->getMaxActivity(iRow) << " lhs = " << blower << " rhs = " << bupper << std::endl; } } @@ -810,7 +836,8 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { domainchangeDFProbing.push_back(thisbchg); }; - // only record - we do not actually fix them now as their objective coefficients are zero + // only record - we do not actually fix them now as their objective + // coefficients are zero auto collectFixLower = [&](int iCol) { zeroCostFixedVariables_.emplace_back(iCol, FIXDIRECTION_LOWER_BOUND); }; @@ -821,83 +848,95 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { // exit if no new redundant constraints are found HighsInt maxLockLeft = redundantPropagateVec_.size() - previousSize_; - if (maxLockLeft == 0) - return; + if (maxLockLeft == 0) return; - for (; previousSize_ < redundantPropagateVec_.size(); ++ previousSize_, -- maxLockLeft) { + for (; previousSize_ < redundantPropagateVec_.size(); + ++previousSize_, --maxLockLeft) { const HighsInt i = redundantPropagateVec_[previousSize_]; const HighsInt iRow = i / 2; assert(iRow < mipsolver->numRow()); - if (i % 2 == 0) { // lower redundant + if (i % 2 == 0) { // lower redundant HighsInt rstart = mipsolver->mipdata_->ARstart_[iRow]; HighsInt rend = mipsolver->mipdata_->ARstart_[iRow + 1]; - for (auto k = rstart; k < rend; ++ k) { + for (auto k = rstart; k < rend; ++k) { const HighsInt iCol = mipsolver->mipdata_->ARindex_[k]; - if (domain->isFixed(iCol)) - continue; + if (domain->isFixed(iCol)) continue; const double iValue = mipsolver->mipdata_->ARvalue_[k]; const double cost = mipsolver->model_->col_cost_[iCol]; // do not insert to candidates if the lock is not reduced enough - bool lowerNoInsert = colLowerLockReduced_[iCol] + maxLockLeft < colLowerLockOriginal_[iCol]; - bool upperNoInsert = colUpperLockReduced_[iCol] + maxLockLeft < colUpperLockOriginal_[iCol]; + bool lowerNoInsert = colLowerLockReduced_[iCol] + maxLockLeft < + colLowerLockOriginal_[iCol]; + bool upperNoInsert = colUpperLockReduced_[iCol] + maxLockLeft < + colUpperLockOriginal_[iCol]; - if (iValue > 0 && cost >= mipsolver->options_mip_->dual_feasibility_tolerance) { + if (iValue > 0 && + cost >= mipsolver->options_mip_->dual_feasibility_tolerance) { lockNeedClear_.insert(iCol); - colLowerLockReduced_[iCol] ++; - lowerNoInsert = lowerNoInsert && colLowerLockReduced_[iCol] + maxLockLeft < colLowerLockOriginal_[iCol]; - } - else if (iValue < 0 && cost <= mipsolver->options_mip_->dual_feasibility_tolerance) { + colLowerLockReduced_[iCol]++; + lowerNoInsert = + lowerNoInsert && colLowerLockReduced_[iCol] + maxLockLeft < + colLowerLockOriginal_[iCol]; + } else if (iValue < 0 && + cost <= + mipsolver->options_mip_->dual_feasibility_tolerance) { lockNeedClear_.insert(iCol); - colUpperLockReduced_[iCol] ++; - upperNoInsert = upperNoInsert && colUpperLockReduced_[iCol] + maxLockLeft < colUpperLockOriginal_[iCol]; + colUpperLockReduced_[iCol]++; + upperNoInsert = + upperNoInsert && colUpperLockReduced_[iCol] + maxLockLeft < + colUpperLockOriginal_[iCol]; } - if (!lowerNoInsert || !upperNoInsert) - addToCandidate(iCol); + if (!lowerNoInsert || !upperNoInsert) addToCandidate(iCol); } - } - else { // upper redundant + } else { // upper redundant HighsInt rstart = mipsolver->mipdata_->ARstart_[iRow]; HighsInt rend = mipsolver->mipdata_->ARstart_[iRow + 1]; for (auto k = rstart; k < rend; k++) { const HighsInt iCol = mipsolver->mipdata_->ARindex_[k]; - if (domain->isFixed(iCol)) - continue; + if (domain->isFixed(iCol)) continue; const double iValue = mipsolver->mipdata_->ARvalue_[k]; const double cost = mipsolver->model_->col_cost_[iCol]; // do not insert to candidates if the lock is not reduced enough - bool lowerNoInsert = colLowerLockReduced_[iCol] + maxLockLeft < colLowerLockOriginal_[iCol]; - bool upperNoInsert = colUpperLockReduced_[iCol] + maxLockLeft < colUpperLockOriginal_[iCol]; + bool lowerNoInsert = colLowerLockReduced_[iCol] + maxLockLeft < + colLowerLockOriginal_[iCol]; + bool upperNoInsert = colUpperLockReduced_[iCol] + maxLockLeft < + colUpperLockOriginal_[iCol]; - if (iValue < 0 && cost >= mipsolver->options_mip_->dual_feasibility_tolerance) { + if (iValue < 0 && + cost >= mipsolver->options_mip_->dual_feasibility_tolerance) { lockNeedClear_.insert(iCol); - colLowerLockReduced_[iCol] ++; - lowerNoInsert = lowerNoInsert && colLowerLockReduced_[iCol] + maxLockLeft < colLowerLockOriginal_[iCol]; - } - else if (iValue > 0 && cost <= mipsolver->options_mip_->dual_feasibility_tolerance) { + colLowerLockReduced_[iCol]++; + lowerNoInsert = + lowerNoInsert && colLowerLockReduced_[iCol] + maxLockLeft < + colLowerLockOriginal_[iCol]; + } else if (iValue > 0 && + cost <= + mipsolver->options_mip_->dual_feasibility_tolerance) { lockNeedClear_.insert(iCol); - colUpperLockReduced_[iCol] ++; - upperNoInsert = upperNoInsert && colUpperLockReduced_[iCol] + maxLockLeft < colUpperLockOriginal_[iCol]; + colUpperLockReduced_[iCol]++; + upperNoInsert = + upperNoInsert && colUpperLockReduced_[iCol] + maxLockLeft < + colUpperLockOriginal_[iCol]; } - if (!lowerNoInsert || !upperNoInsert) - addToCandidate(iCol); + if (!lowerNoInsert || !upperNoInsert) addToCandidate(iCol); } } } for (auto iCol : candidatesVec_) { - if (domain->isFixed(iCol)) - continue; - const bool canBeFixedToLower = colLowerLockReduced_[iCol] == colLowerLockOriginal_[iCol]; - const bool canBeFixedToUpper = colUpperLockReduced_[iCol] == colUpperLockOriginal_[iCol]; - if (!canBeFixedToLower && !canBeFixedToUpper) - continue; - - if (fabs(mipsolver->model_->col_cost_[iCol]) <= mipsolver->options_mip_->dual_feasibility_tolerance) { + if (domain->isFixed(iCol)) continue; + const bool canBeFixedToLower = + colLowerLockReduced_[iCol] == colLowerLockOriginal_[iCol]; + const bool canBeFixedToUpper = + colUpperLockReduced_[iCol] == colUpperLockOriginal_[iCol]; + if (!canBeFixedToLower && !canBeFixedToUpper) continue; + + if (fabs(mipsolver->model_->col_cost_[iCol]) <= + mipsolver->options_mip_->dual_feasibility_tolerance) { if (startZeroCostFixing_) { // not fixed before if (zeroCostVarsDirection_[iCol] == FIXDIRECTION_NOT_DECIDED) { @@ -906,8 +945,7 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { if (mipsolver->model_->col_cost_[iCol] >= 0) { addFixLower(iCol); zeroCostVarsDirection_[iCol] = FIXDIRECTION_LOWER_BOUND; - } - else { + } else { addFixUpper(iCol); zeroCostVarsDirection_[iCol] = FIXDIRECTION_UPPER_BOUND; } @@ -916,22 +954,24 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { else if (canBeFixedToLower) { addFixLower(iCol); zeroCostVarsDirection_[iCol] = FIXDIRECTION_LOWER_BOUND; - } - else if (canBeFixedToUpper) { + } else if (canBeFixedToUpper) { addFixUpper(iCol); zeroCostVarsDirection_[iCol] = FIXDIRECTION_UPPER_BOUND; } } // fix to lb - else if (zeroCostVarsDirection_[iCol] == FIXDIRECTION_LOWER_BOUND && canBeFixedToLower) + else if (zeroCostVarsDirection_[iCol] == FIXDIRECTION_LOWER_BOUND && + canBeFixedToLower) addFixLower(iCol); - // fix to ub - else if (zeroCostVarsDirection_[iCol] == FIXDIRECTION_UPPER_BOUND && canBeFixedToUpper) + // fix to ub + else if (zeroCostVarsDirection_[iCol] == FIXDIRECTION_UPPER_BOUND && + canBeFixedToUpper) addFixUpper(iCol); continue; } - // do not perfrom zero cost variable fixing, just collect them and choose directions + // do not perfrom zero cost variable fixing, just collect them and choose + // directions else { // not fixed before if (zeroCostVarsDirection_[iCol] == FIXDIRECTION_NOT_DECIDED) { @@ -940,25 +980,22 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { if (mipsolver->model_->col_cost_[iCol] >= 0) { collectFixLower(iCol); zeroCostVarsDirection_[iCol] = FIXDIRECTION_LOWER_BOUND; - } - else { + } else { collectFixUpper(iCol); zeroCostVarsDirection_[iCol] = FIXDIRECTION_UPPER_BOUND; } - } - else if (canBeFixedToLower) { // fix to lower and set its direction + } else if (canBeFixedToLower) { // fix to lower and set its direction collectFixLower(iCol); zeroCostVarsDirection_[iCol] = FIXDIRECTION_LOWER_BOUND; - } - else if (canBeFixedToUpper) { + } else if (canBeFixedToUpper) { collectFixUpper(iCol); zeroCostVarsDirection_[iCol] = FIXDIRECTION_UPPER_BOUND; } - } - else if (zeroCostVarsDirection_[iCol] == FIXDIRECTION_UPPER_BOUND && canBeFixedToUpper) { // fix to upper + } else if (zeroCostVarsDirection_[iCol] == FIXDIRECTION_UPPER_BOUND && + canBeFixedToUpper) { // fix to upper collectFixUpper(iCol); - } - else if (zeroCostVarsDirection_[iCol] == FIXDIRECTION_LOWER_BOUND && canBeFixedToLower) { // fix to lower + } else if (zeroCostVarsDirection_[iCol] == FIXDIRECTION_LOWER_BOUND && + canBeFixedToLower) { // fix to lower collectFixLower(iCol); } // we have collected this column @@ -966,8 +1003,8 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { } } - - if (mipsolver->model_->col_cost_[iCol] >= mipsolver->options_mip_->dual_feasibility_tolerance) { + if (mipsolver->model_->col_cost_[iCol] >= + mipsolver->options_mip_->dual_feasibility_tolerance) { if (canBeFixedToLower) { // checkVariableLowerLock(iCol); addFixLower(iCol); @@ -975,7 +1012,8 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { } } - if (mipsolver->model_->col_cost_[iCol] <= mipsolver->options_mip_->dual_feasibility_tolerance) { + if (mipsolver->model_->col_cost_[iCol] <= + mipsolver->options_mip_->dual_feasibility_tolerance) { if (canBeFixedToUpper) { // checkVariableUpperLock(iCol); addFixUpper(iCol); @@ -992,13 +1030,13 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { // change bound size_t j = 0; - for (; j != domainchangeDFProbing.size() && !domain->infeasible_; ++ j) { + for (; j != domainchangeDFProbing.size() && !domain->infeasible_; ++j) { domain->changeBound(*domainchangeDFProbing[j], Reason::unspecified()); delete domainchangeDFProbing[j]; } // clear the remaining domain changes if infeasible - for (j ++; j < domainchangeDFProbing.size(); ++ j) { + for (j++; j < domainchangeDFProbing.size(); ++j) { assert(domain->infeasible_); delete domainchangeDFProbing[j]; } @@ -1007,7 +1045,8 @@ void HighsDomain::DualfixingProbingPropagation::propagate() { previousSize_ = redundantPropagateVec_.size(); } -void HighsDomain::DualfixingProbingPropagation::updateGDFInfo(HighsInt probing_variable, bool val) { +void HighsDomain::DualfixingProbingPropagation::updateGDFInfo( + HighsInt probing_variable, bool val) { // tool lambda functions auto addToCandidate = [&](HighsInt k) { if (gdfCandidatesFlag_[k]) @@ -1017,118 +1056,111 @@ void HighsDomain::DualfixingProbingPropagation::updateGDFInfo(HighsInt probing_v gdfCandidatesFlag_[k] = true; } }; - + // only redundant constraints are useful in GDF for (const auto x : redundantPropagateVec_) { const HighsInt iRow = x / 2; const bool isRhs = x % 2; HighsInt rstart = mipsolver->mipdata_->ARstart_[iRow]; HighsInt rend = mipsolver->mipdata_->ARstart_[iRow + 1]; - + for (auto k = rstart; k < rend; k++) { const HighsInt iCol = mipsolver->mipdata_->ARindex_[k]; const double iValue = mipsolver->mipdata_->ARvalue_[k]; const double cost = mipsolver->model_->col_cost_[iCol]; bool considered = false; - if (mipsolver->model_->col_lower_[iCol] == mipsolver->model_->col_upper_[iCol] || mipsolver->mipdata_->implications.colsubstituted[iCol]) + if (mipsolver->model_->col_lower_[iCol] == + mipsolver->model_->col_upper_[iCol] || + mipsolver->mipdata_->implications.colsubstituted[iCol]) continue; - + if (iValue > 0) { - if (isRhs) { // consider upper bound reachable + if (isRhs) { // consider upper bound reachable const double globalUb = mipsolver->model_->col_upper_[iCol]; const double probingUb = domain->col_upper_[iCol]; if (!ableToFixToUb(iCol) || domain->getMaxActivity(iRow) == kHighsInf) continue; - const bool upper_bound_reachable = - domain->getMaxActivity(iRow) + iValue * (globalUb - probingUb) <= mipsolver->model_->row_upper_[iRow] + domain->feastol(); + const bool upper_bound_reachable = + domain->getMaxActivity(iRow) + iValue * (globalUb - probingUb) <= + mipsolver->model_->row_upper_[iRow] + domain->feastol(); if (upper_bound_reachable) { considered = true; // special treat if the current variable is the probing variable if (iCol == probing_variable && val == 0) { gdfUbReachable0_[iCol]++; gdfUbReachable1_[iCol]++; - } - else { - if (val == 0) - gdfUbReachable0_[iCol]++; - if (val == 1) - gdfUbReachable1_[iCol]++; + } else { + if (val == 0) gdfUbReachable0_[iCol]++; + if (val == 1) gdfUbReachable1_[iCol]++; } } - } - else { // consider lower bound reachable + } else { // consider lower bound reachable const double globalLb = mipsolver->model_->col_lower_[iCol]; const double probingLb = domain->col_lower_[iCol]; - if (!ableToFixToLb(iCol) || domain->getMinActivity(iRow) == -kHighsInf) + if (!ableToFixToLb(iCol) || + domain->getMinActivity(iRow) == -kHighsInf) continue; - const bool lower_bound_reachable = - domain->getMinActivity(iRow) + iValue * (globalLb - probingLb) >= mipsolver->model_->row_lower_[iRow] - domain->feastol(); + const bool lower_bound_reachable = + domain->getMinActivity(iRow) + iValue * (globalLb - probingLb) >= + mipsolver->model_->row_lower_[iRow] - domain->feastol(); if (lower_bound_reachable) { considered = true; // special treat if the current variable is the probing variable if (iCol == probing_variable && val == 1) { gdfLbReachable0_[iCol]++; gdfLbReachable1_[iCol]++; - } - else { - if (val == 0) - gdfLbReachable0_[iCol]++; - if (val == 1) - gdfLbReachable1_[iCol]++; + } else { + if (val == 0) gdfLbReachable0_[iCol]++; + if (val == 1) gdfLbReachable1_[iCol]++; } } } } else { - if (isRhs) { // consider lower bound reachable + if (isRhs) { // consider lower bound reachable const double globalLb = mipsolver->model_->col_lower_[iCol]; const double probingLb = domain->col_lower_[iCol]; if (!ableToFixToLb(iCol) || domain->getMaxActivity(iRow) == kHighsInf) continue; - const bool lower_bound_reachable = - domain->getMaxActivity(iRow) + iValue * (globalLb - probingLb) <= mipsolver->model_->row_upper_[iRow] + domain->feastol(); + const bool lower_bound_reachable = + domain->getMaxActivity(iRow) + iValue * (globalLb - probingLb) <= + mipsolver->model_->row_upper_[iRow] + domain->feastol(); if (lower_bound_reachable) { considered = true; // special treat if the current variable is the probing variable if (iCol == probing_variable && val == 1) { gdfLbReachable0_[iCol]++; gdfLbReachable1_[iCol]++; - } - else { - if (val == 0) - gdfLbReachable0_[iCol]++; - if (val == 1) - gdfLbReachable1_[iCol]++; + } else { + if (val == 0) gdfLbReachable0_[iCol]++; + if (val == 1) gdfLbReachable1_[iCol]++; } } - } - else { // consider upper bound reachable + } else { // consider upper bound reachable const double globalUb = mipsolver->model_->col_upper_[iCol]; const double probingUb = domain->col_upper_[iCol]; - if (!ableToFixToUb(iCol) || domain->getMinActivity(iRow) == -kHighsInf) + if (!ableToFixToUb(iCol) || + domain->getMinActivity(iRow) == -kHighsInf) continue; - const bool upper_bound_reachable = - domain->getMinActivity(iRow) + iValue * (globalUb - probingUb) >= mipsolver->model_->row_lower_[iRow] - domain->feastol(); + const bool upper_bound_reachable = + domain->getMinActivity(iRow) + iValue * (globalUb - probingUb) >= + mipsolver->model_->row_lower_[iRow] - domain->feastol(); if (upper_bound_reachable) { considered = true; // special treat if the current variable is the probing variable if (iCol == probing_variable && val == 0) { gdfUbReachable0_[iCol]++; gdfUbReachable1_[iCol]++; - } - else { - if (val == 0) - gdfUbReachable0_[iCol]++; - if (val == 1) - gdfUbReachable1_[iCol]++; + } else { + if (val == 0) gdfUbReachable0_[iCol]++; + if (val == 1) gdfUbReachable1_[iCol]++; } } } } - if (considered) - addToCandidate(iCol); + if (considered) addToCandidate(iCol); } } } @@ -1160,16 +1192,16 @@ HighsInt HighsDomain::DualfixingProbingPropagation::processGDFFixing() { gdfFixingStack_.push_back(thisbchg); } } - + // apply bound change size_t j = 0; - for (; j != gdfFixingStack_.size() && !domain->infeasible_; ++ j) { + for (; j != gdfFixingStack_.size() && !domain->infeasible_; ++j) { domain->changeBound(*gdfFixingStack_[j], Reason::unspecified()); delete gdfFixingStack_[j]; } // clear the remaining domain changes if infeasible - for (; j < gdfFixingStack_.size(); ++ j) { + for (; j < gdfFixingStack_.size(); ++j) { assert(domain->infeasible_); delete gdfFixingStack_[j]; } @@ -2107,13 +2139,15 @@ void HighsDomain::updateActivityLbChange(HighsInt col, double oldbound, assert(tmpinf == activitymininf_[mip->a_matrix_.index_[i]]); } #endif - // If dfprobingPropagation.isZeroObjFixingEnabled() is true, - // then we cannot record redundant rows for lifting, as this bound change could disregarded. - if (recordRedundantRows_ && !dfprobingPropagation.isZeroObjFixingEnabled() && + // If dfprobingPropagation.isZeroObjFixingEnabled() is true, + // then we cannot record redundant rows for lifting, as this bound change + // could disregarded. + if (recordRedundantRows_ && + !dfprobingPropagation.isZeroObjFixingEnabled() && mip->row_lower_[mip->a_matrix_.index_[i]] != -kHighsInf && mip->row_upper_[mip->a_matrix_.index_[i]] == kHighsInf) updateRedundantRows(mip->a_matrix_.index_[i]); - + if (newbound >= oldbound + mipsolver->mipdata_->feastol) dfprobingPropagation.updateLhsRedundant(mip->a_matrix_.index_[i]); @@ -2160,9 +2194,11 @@ void HighsDomain::updateActivityLbChange(HighsInt col, double oldbound, assert(tmpinf == activitymaxinf_[mip->a_matrix_.index_[i]]); } #endif - // If dfprobingPropagation.isZeroObjFixingEnabled() is true, - // then we cannot record redundant rows for lifting, as this bound change could disregarded. - if (recordRedundantRows_ && !dfprobingPropagation.isZeroObjFixingEnabled() && + // If dfprobingPropagation.isZeroObjFixingEnabled() is true, + // then we cannot record redundant rows for lifting, as this bound change + // could disregarded. + if (recordRedundantRows_ && + !dfprobingPropagation.isZeroObjFixingEnabled() && mip->row_lower_[mip->a_matrix_.index_[i]] == -kHighsInf && mip->row_upper_[mip->a_matrix_.index_[i]] != kHighsInf) updateRedundantRows(mip->a_matrix_.index_[i]); @@ -2282,13 +2318,15 @@ void HighsDomain::updateActivityUbChange(HighsInt col, double oldbound, assert(tmpinf == activitymaxinf_[mip->a_matrix_.index_[i]]); } #endif - // If dfprobingPropagation.isZeroObjFixingEnabled() is true, - // then we cannot record redundant rows for lifting, as this bound change could disregarded. - if (recordRedundantRows_ && !dfprobingPropagation.isZeroObjFixingEnabled() && + // If dfprobingPropagation.isZeroObjFixingEnabled() is true, + // then we cannot record redundant rows for lifting, as this bound change + // could disregarded. + if (recordRedundantRows_ && + !dfprobingPropagation.isZeroObjFixingEnabled() && mip->row_lower_[mip->a_matrix_.index_[i]] == -kHighsInf && mip->row_upper_[mip->a_matrix_.index_[i]] != kHighsInf) updateRedundantRows(mip->a_matrix_.index_[i]); - + if (newbound <= oldbound - mipsolver->mipdata_->feastol) dfprobingPropagation.updateRhsRedundant(mip->a_matrix_.index_[i]); @@ -2338,13 +2376,15 @@ void HighsDomain::updateActivityUbChange(HighsInt col, double oldbound, assert(tmpinf == activitymininf_[mip->a_matrix_.index_[i]]); } #endif - // If dfprobingPropagation.isZeroObjFixingEnabled() is true, - // then we cannot record redundant rows for lifting, as this bound change could disregarded. - if (recordRedundantRows_ && !dfprobingPropagation.isZeroObjFixingEnabled() && + // If dfprobingPropagation.isZeroObjFixingEnabled() is true, + // then we cannot record redundant rows for lifting, as this bound change + // could disregarded. + if (recordRedundantRows_ && + !dfprobingPropagation.isZeroObjFixingEnabled() && mip->row_lower_[mip->a_matrix_.index_[i]] != -kHighsInf && mip->row_upper_[mip->a_matrix_.index_[i]] == kHighsInf) updateRedundantRows(mip->a_matrix_.index_[i]); - + if (newbound <= oldbound - mipsolver->mipdata_->feastol) dfprobingPropagation.updateLhsRedundant(mip->a_matrix_.index_[i]); @@ -2941,7 +2981,8 @@ bool HighsDomain::propagate() { if (!conflictprop.propagateConflictInds_.empty()) return true; } - if (!infeasible_ && dfprobingPropagation.isActive() && mipsolver->options_mip_->presolve_dfprobing) + if (!infeasible_ && dfprobingPropagation.isActive() && + mipsolver->options_mip_->presolve_dfprobing) return true; return false; @@ -3119,11 +3160,14 @@ bool HighsDomain::propagate() { propagateinds.clear(); } } - - if (!infeasible_ && dfprobingPropagation.isActive() && mipsolver->options_mip_->presolve_dfprobing) { - // std::cout << "Activated by nRedundantIndices = " << dfprobingPropagation.redundantPropagateVec_.size() << std::endl; + + if (!infeasible_ && dfprobingPropagation.isActive() && + mipsolver->options_mip_->presolve_dfprobing) { + // std::cout << "Activated by nRedundantIndices = " << + // dfprobingPropagation.redundantPropagateVec_.size() << std::endl; dfprobingPropagation.propagate(); - if (!havePropagationRows() && !dfprobingPropagation.isZeroObjFixingEnabled()) { + if (!havePropagationRows() && + !dfprobingPropagation.isZeroObjFixingEnabled()) { dfprobingPropagation.enableZeroObjFixing(); dfprobingPropagation.setZeroCostFixingPosition(domchgstack_.size()); dfprobingPropagation.propagate(); diff --git a/highs/mip/HighsDomain.h b/highs/mip/HighsDomain.h index 49f80546c1f..978ade04852 100644 --- a/highs/mip/HighsDomain.h +++ b/highs/mip/HighsDomain.h @@ -12,8 +12,8 @@ #include #include #include -#include #include +#include #include "HighsPseudocost.h" #include "mip/HighsDomainChange.h" @@ -239,12 +239,12 @@ class HighsDomain { struct DualfixingProbingPropagation { HighsDomain* domain; HighsMipSolver* mipsolver; - + // row lower and upper, length = 2 * rownum std::vector redundantPropagateFlag_; std::vector redundantPropagateVec_; - - // For zero-cost variables, we need to know which direction we can fix them to. + + // For zero-cost variables, we need to know which direction we can fix them enum DFPROBING_FIX_DIRECTION { FIXDIRECTION_NOT_DECIDED = 0, FIXDIRECTION_LOWER_BOUND, @@ -252,8 +252,9 @@ class HighsDomain { }; std::vector zeroCostVarsDirection_; std::vector> zeroCostFixedVariables_; - - // Flag and position in the domchgstack of the first zero-cost variable that can be fixed to its lower or upper bound. + + // Flag and position in the domchgstack of the first zero-cost variable that + // can be fixed to its lower or upper bound. bool startZeroCostFixing_ = false; size_t zeroCostStartPos_; @@ -283,67 +284,54 @@ class HighsDomain { std::vector gdfUbReachable0_; std::vector gdfUbReachable1_; - void enablePropagator() { - enabled_ = true; - } + void enablePropagator() { enabled_ = true; } - void disablePropagator() { - enabled_ = false; - } + void disablePropagator() { enabled_ = false; } - bool isEnabled() { - return enabled_; - } + bool isEnabled() { return enabled_; } // active only when new redundant rows are found. bool isActive() { return enabled_ && redundantPropagateVec_.size() > previousSize_; } - // mark the position when the first zero-cost variable can be fixed to its lower or upper bound. - void setZeroCostFixingPosition(HighsInt v) { - zeroCostStartPos_ = v; - } + // mark the position when the first zero-cost variable can be fixed to its + // lower or upper bound. + void setZeroCostFixingPosition(HighsInt v) { zeroCostStartPos_ = v; } - size_t getZeroCostFixingPosition() { - return zeroCostStartPos_; - } + size_t getZeroCostFixingPosition() { return zeroCostStartPos_; } - void enableZeroObjFixing() { - startZeroCostFixing_ = true; - } + void enableZeroObjFixing() { startZeroCostFixing_ = true; } - void disableZeroObjFixing() { - startZeroCostFixing_ = false; - } + void disableZeroObjFixing() { startZeroCostFixing_ = false; } - bool isZeroObjFixingEnabled() { - return startZeroCostFixing_; - } + bool isZeroObjFixingEnabled() { return startZeroCostFixing_; } bool ableToFixToLb(int col) { - return mipsolver->model_->col_cost_[col] >= -mipsolver->options_mip_->dual_feasibility_tolerance - && mipsolver->model_->col_lower_[col] > -kHighsInf; + return mipsolver->model_->col_cost_[col] >= + -mipsolver->options_mip_->dual_feasibility_tolerance && + mipsolver->model_->col_lower_[col] > -kHighsInf; } bool ableToFixToUb(int col) { - return mipsolver->model_->col_cost_[col] <= mipsolver->options_mip_->dual_feasibility_tolerance - && mipsolver->model_->col_upper_[col] < kHighsInf; + return mipsolver->model_->col_cost_[col] <= + mipsolver->options_mip_->dual_feasibility_tolerance && + mipsolver->model_->col_upper_[col] < kHighsInf; } // remove redundant information void clearRedundantInfo() { previousSize_ = 0; - if (!redundantPropagateVec_.empty()) { // clear buffers + if (!redundantPropagateVec_.empty()) { // clear buffers for (const auto x : redundantPropagateVec_) redundantPropagateFlag_[x] = false; redundantPropagateVec_.clear(); } - - for (size_t i = 0; i < redundantPropagateFlag_.size(); ++ i) + + for (size_t i = 0; i < redundantPropagateFlag_.size(); ++i) assert(!redundantPropagateFlag_[i]); - + zeroCostFixedVariables_.clear(); for (const auto x : lockNeedClear_) @@ -351,19 +339,19 @@ class HighsDomain { lockNeedClear_.clear(); } - DualfixingProbingPropagation() {;}; - + DualfixingProbingPropagation() { ; }; + DualfixingProbingPropagation(HighsDomain* domain) : domain(domain) {}; DualfixingProbingPropagation(const DualfixingProbingPropagation& other); - ~DualfixingProbingPropagation() {;}; + ~DualfixingProbingPropagation() { ; }; void recomputeLocks(); void updateRhsRedundant(HighsInt row); void updateLhsRedundant(HighsInt row); void propagate(); - + // functionalities for GDF void updateGDFInfo(HighsInt probing_variable, bool val); HighsInt processGDFFixing(); diff --git a/highs/mip/HighsImplications.cpp b/highs/mip/HighsImplications.cpp index 55b2f358f42..787c911c12e 100644 --- a/highs/mip/HighsImplications.cpp +++ b/highs/mip/HighsImplications.cpp @@ -28,8 +28,10 @@ bool HighsImplications::computeImplications(HighsInt col, bool val) { size_t changedend = globaldomain.getChangedCols().size(); // get two flags - const bool useDFProbing = globaldomain.inProbing_ && mipsolver.options_mip_->presolve_dfprobing; - const bool useGDF = globaldomain.inProbing_ && mipsolver.options_mip_->presolve_gdf; + const bool useDFProbing = + globaldomain.inProbing_ && mipsolver.options_mip_->presolve_dfprobing; + const bool useGDF = + globaldomain.inProbing_ && mipsolver.options_mip_->presolve_gdf; // record redundant rows if any of the two flags is true if (useDFProbing || useGDF) { globaldomain.getDfProbingPropagation().clearRedundantInfo(); @@ -93,17 +95,21 @@ bool HighsImplications::computeImplications(HighsInt col, bool val) { HighsInt numEntries = mipsolver.mipdata_->cliquetable.getNumEntries(); HighsInt maxEntries = 100000 + mipsolver.numNonzero(); - const HighsInt tentativeStart = useDFProbing ? globaldomain.getDfProbingPropagation().getZeroCostFixingPosition() : kHighsIInf32; + const HighsInt tentativeStart = + useDFProbing + ? globaldomain.getDfProbingPropagation().getZeroCostFixingPosition() + : kHighsIInf32; if (useDFProbing) - implics_tentative.assign(domchgstack.begin() + stackimplicstart, domchgstack.begin() + stackimplicend); + implics_tentative.assign(domchgstack.begin() + stackimplicstart, + domchgstack.begin() + stackimplicend); for (HighsInt i = stackimplicstart; i < stackimplicend; ++i) { if (domchgreason[i].type == HighsDomain::Reason::kCliqueTable && ((domchgreason[i].index >> 1) == col || numEntries >= maxEntries)) continue; - - if (i >= tentativeStart) // record tentative implications - continue; + + if (i >= tentativeStart) // record tentative implications + continue; implics.push_back(domchgstack[i]); } @@ -112,20 +118,20 @@ bool HighsImplications::computeImplications(HighsInt col, bool val) { storeLiftingOpportunities(col, val); // update information to derive generalized dual fixings - if (useGDF) - globaldomain.getDfProbingPropagation().updateGDFInfo(col, val); + if (useGDF) globaldomain.getDfProbingPropagation().updateGDFInfo(col, val); // backtrack doBacktrack(changedend); if (!implics_tentative.empty()) { // add the implications of binary variables to the clique table - auto binstart_tmp = std::partition(implics_tentative.begin(), implics_tentative.end(), - [&](const HighsDomainChange& a) { - return !globaldomain.isBinary(a.column); - }); - // store the tentative bound changes of binary variables separately - for (auto i = binstart_tmp; i != implics_tentative.end(); ++ i) + auto binstart_tmp = + std::partition(implics_tentative.begin(), implics_tentative.end(), + [&](const HighsDomainChange& a) { + return !globaldomain.isBinary(a.column); + }); + // store the tentative bound changes of binary variables separately + for (auto i = binstart_tmp; i != implics_tentative.end(); ++i) recordTentativeCliques(val, *i); implics_tentative.erase(binstart_tmp, implics_tentative.end()); } @@ -344,17 +350,17 @@ bool HighsImplications::runProbing(HighsInt col, HighsInt& numReductions) { if (globaldomain.isBinary(col) && !implicationsCached(col, 1) && !implicationsCached(col, 0) && mipsolver.mipdata_->cliquetable.getSubstitution(col) == nullptr) { - - const bool useDFProbing = globaldomain.inProbing_ && mipsolver.options_mip_->presolve_dfprobing; - const bool useGDF = globaldomain.inProbing_ && mipsolver.options_mip_->presolve_gdf; + const bool useDFProbing = + globaldomain.inProbing_ && mipsolver.options_mip_->presolve_dfprobing; + const bool useGDF = + globaldomain.inProbing_ && mipsolver.options_mip_->presolve_gdf; // setup for dfprobingPropagation if (useDFProbing) { clearTentativeClique(); - globaldomain.getDfProbingPropagation().setZeroCostFixingPosition(kHighsIInf32); + globaldomain.getDfProbingPropagation().setZeroCostFixingPosition( + kHighsIInf32); } - if (useGDF) - globaldomain.getDfProbingPropagation().clearGDFInfo(); - + if (useGDF) globaldomain.getDfProbingPropagation().clearGDFInfo(); bool infeasible = computeImplications(col, 1); if (globaldomain.infeasible()) return true; @@ -373,20 +379,23 @@ bool HighsImplications::runProbing(HighsInt col, HighsInt& numReductions) { HighsCliqueTable::CliqueVar clique[2]; // Loop over binary variables that are tighened at least once for (auto k : binaryInvolvedInds_) { - // Skip non-binary variables (being fixed now) or those can be substituted by other binary variables - if (!globaldomain.isBinary(k) || colsubstituted[k]) - continue; + // Skip non-binary variables (being fixed now) or those can be + // substituted by other binary variables + if (!globaldomain.isBinary(k) || colsubstituted[k]) continue; // Return if infeasible - if (globaldomain.infeasible()) - return true; - // Get the information how x[k] is fixed in probing on x[col] = 0 and x[col] = 1 - // For the meaning of ``data'', please see lines 71-89 in HighsImplications.h + if (globaldomain.infeasible()) return true; + // Get the information how x[k] is fixed in probing on x[col] = 0 and + // x[col] = 1 For the meaning of ``data'', please see lines 71-89 in + // HighsImplications.h uint8_t data = binaryInvolvedFlags_[k]; - if (data == 0) // flag for no reduction + if (data == 0) // flag for no reduction continue; - if (data == binaryFixType::kGlobalLower) { // x[k] is fixed at 0 under both x[col] = 0 and x[col] = 1 - // fix x[k] = 0 by adding two cliques (i.e., these two cliques should be added in computeImplications() to derive global reductions) + if (data == + binaryFixType::kGlobalLower) { // x[k] is fixed at 0 under both + // x[col] = 0 and x[col] = 1 + // fix x[k] = 0 by adding two cliques (i.e., these two cliques should + // be added in computeImplications() to derive global reductions) clique[0] = HighsCliqueTable::CliqueVar(col, 0); clique[1] = HighsCliqueTable::CliqueVar(k, 1); cliquetable.addClique(mipsolver, &clique[0], 2); @@ -394,9 +403,11 @@ bool HighsImplications::runProbing(HighsInt col, HighsInt& numReductions) { clique[1] = HighsCliqueTable::CliqueVar(k, 1); cliquetable.addClique(mipsolver, &clique[0], 2); data = 0; - } - else if (data == binaryFixType::kGlobalUpper) { // x[k] is fixed at 1 under both x[col] = 0 and x[col] = 1 - // fix x[k] = 1 by adding two cliques (i.e., these two cliques should be added in computeImplications() to derive global reductions) + } else if (data == binaryFixType::kGlobalUpper) { // x[k] is fixed at 1 + // under both x[col] + // = 0 and x[col] = 1 + // fix x[k] = 1 by adding two cliques (i.e., these two cliques should + // be added in computeImplications() to derive global reductions) clique[0] = HighsCliqueTable::CliqueVar(col, 0); clique[1] = HighsCliqueTable::CliqueVar(k, 0); cliquetable.addClique(mipsolver, &clique[0], 2); @@ -404,9 +415,14 @@ bool HighsImplications::runProbing(HighsInt col, HighsInt& numReductions) { clique[1] = HighsCliqueTable::CliqueVar(k, 0); cliquetable.addClique(mipsolver, &clique[0], 2); data = 0; - } - else if (data == binaryFixType::kSubstituteComplement) { // x[k] is fixed at 0 under x[col] = 1, and is fixed at 1 under x[col] = 0; this makes x[col] + x[k] = 1 - // Adding two cliques (i.e., these two cliques should be added in computeImplications() to derive global reductions) + } else if (data == + binaryFixType:: + kSubstituteComplement) { // x[k] is fixed at 0 under + // x[col] = 1, and is fixed at + // 1 under x[col] = 0; this + // makes x[col] + x[k] = 1 + // Adding two cliques (i.e., these two cliques should be added in + // computeImplications() to derive global reductions) clique[0] = HighsCliqueTable::CliqueVar(col, 1); clique[1] = HighsCliqueTable::CliqueVar(k, 1); cliquetable.addClique(mipsolver, &clique[0], 2); @@ -414,9 +430,13 @@ bool HighsImplications::runProbing(HighsInt col, HighsInt& numReductions) { clique[1] = HighsCliqueTable::CliqueVar(k, 0); cliquetable.addClique(mipsolver, &clique[0], 2); data = 0; - } - else if (data == binaryFixType::kSubstituteEqual) { // x[k] is fixed at 0 under x[col] = 0, and is fixed at 1 under x[col] = 1; this makes x[col] = x[k] - // Adding two cliques (i.e., these two cliques should be added in computeImplications() to derive global reductions) + } else if (data == + binaryFixType:: + kSubstituteEqual) { // x[k] is fixed at 0 under x[col] = + // 0, and is fixed at 1 under x[col] + // = 1; this makes x[col] = x[k] + // Adding two cliques (i.e., these two cliques should be added in + // computeImplications() to derive global reductions) clique[0] = HighsCliqueTable::CliqueVar(col, 1); clique[1] = HighsCliqueTable::CliqueVar(k, 0); cliquetable.addClique(mipsolver, &clique[0], 2); @@ -427,19 +447,25 @@ bool HighsImplications::runProbing(HighsInt col, HighsInt& numReductions) { } } - // clear the tentative bound changes for binary variables obtained from probing on x[col] + // clear the tentative bound changes for binary variables obtained from + // probing on x[col] clearTentativeClique(); } // analyze implications - // also include the bound changes of non-binary variables here, to derive tighter global bounds and variable substitutions - const bool haveTentativeImplics_zero = !implications[2 * col].implics_tentative.empty(); - const bool haveTentativeImplics_one = !implications[2 * col + 1].implics_tentative.empty(); + // also include the bound changes of non-binary variables here, to derive + // tighter global bounds and variable substitutions + const bool haveTentativeImplics_zero = + !implications[2 * col].implics_tentative.empty(); + const bool haveTentativeImplics_one = + !implications[2 * col + 1].implics_tentative.empty(); const std::vector& implicsdown = - haveTentativeImplics_zero ? getImplications_tentative(col, 0) : getImplications(col, 0, infeasible); + haveTentativeImplics_zero ? getImplications_tentative(col, 0) + : getImplications(col, 0, infeasible); const std::vector& implicsup = - haveTentativeImplics_one ? getImplications_tentative(col, 1) : getImplications(col, 1, infeasible); + haveTentativeImplics_one ? getImplications_tentative(col, 1) + : getImplications(col, 1, infeasible); HighsInt nimplicsdown = implicsdown.size(); HighsInt nimplicsup = implicsup.size(); HighsInt u = 0; @@ -515,8 +541,7 @@ bool HighsImplications::runProbing(HighsInt col, HighsInt& numReductions) { // fix variables using generalized dual fixing HighsInt nfix = globaldomain.getDfProbingPropagation().processGDFFixing(); // propagate if necessary - if (nfix > 0) - globaldomain.propagate(); + if (nfix > 0) globaldomain.propagate(); } return true; diff --git a/highs/mip/HighsImplications.h b/highs/mip/HighsImplications.h index 424bc2a259e..9cdaf6b2bb3 100644 --- a/highs/mip/HighsImplications.h +++ b/highs/mip/HighsImplications.h @@ -26,11 +26,10 @@ class HighsImplications { struct Implics { std::vector implics; /* The "tentative" implications: - A implication of type x_j \ge (\ell^1_j - \ell^0_j) x_k + \ell^0_j is called "tentative", if - (1) c_j = 0 - (2) x_j is fixed by applying dual fixing in probing - These implications can only be used to perform globally valid reductions. - Therefore, special treatment is required. + A implication of type x_j \ge (\ell^1_j - \ell^0_j) x_k + \ell^0_j is + called "tentative", if (1) c_j = 0 (2) x_j is fixed by applying dual + fixing in probing These implications can only be used to perform globally + valid reductions. Therefore, special treatment is required. */ std::vector implics_tentative; bool computed = false; @@ -69,11 +68,11 @@ class HighsImplications { // vector used to derive global reductions from dfprobing std::vector binaryInvolvedInds_; enum binaryFixType { - kNoReduction = 0b0000, - kGlobalLower = 0b1010, - kGlobalUpper = 0b0101, + kNoReduction = 0b0000, + kGlobalLower = 0b1010, + kGlobalUpper = 0b0101, kSubstituteComplement = 0b1001, - kSubstituteEqual = 0b0110, + kSubstituteEqual = 0b0110, }; /* Possible values for binaryInvolvedFlags_ @@ -129,7 +128,6 @@ class HighsImplications { nextCleanupCall = mipsolver.numNonzero(); binaryInvolvedInds_.reserve(numcol); binaryInvolvedFlags_.assign(numcol, 0b0000); - } constexpr static int64_t calcMaxVarBounds(HighsInt numcol) { @@ -153,12 +151,12 @@ class HighsImplications { return implications[loc].implics; } - const std::vector& getImplications_tentative(HighsInt col, bool val) { + const std::vector& getImplications_tentative(HighsInt col, + bool val) { HighsInt loc = 2 * col + val; return implications[loc].implics_tentative; } - bool implicationsCached(HighsInt col, bool val) { HighsInt loc = 2 * col + val; return implications[loc].computed; @@ -240,35 +238,32 @@ class HighsImplications { // collect tentative binary implications void recordTentativeCliques(bool val, const HighsDomainChange& bchg) { const int iCol = bchg.column; - if (val == 0) { // probing x_k = 0 - if (bchg.boundtype == HighsBoundType::kLower) { // fixed to 1 + if (val == 0) { // probing x_k = 0 + if (bchg.boundtype == HighsBoundType::kLower) { // fixed to 1 if (!isFixedTo1(val, iCol)) { if (binaryInvolvedFlags_[iCol] == 0) binaryInvolvedInds_.push_back(iCol); - binaryInvolvedFlags_[iCol] += 0b0001; // 0001 + binaryInvolvedFlags_[iCol] += 0b0001; // 0001 } - } - else { // fixed to 0 + } else { // fixed to 0 if (!isFixedTo0(val, iCol)) { if (binaryInvolvedFlags_[iCol] == 0) binaryInvolvedInds_.push_back(iCol); - binaryInvolvedFlags_[iCol] += 0b0010; // 0010 + binaryInvolvedFlags_[iCol] += 0b0010; // 0010 } } - } - else { // probing x_k = 1 - if (bchg.boundtype == HighsBoundType::kLower) { // fixed to 1 + } else { // probing x_k = 1 + if (bchg.boundtype == HighsBoundType::kLower) { // fixed to 1 if (!isFixedTo1(val, iCol)) { if (binaryInvolvedFlags_[iCol] == 0) binaryInvolvedInds_.push_back(iCol); - binaryInvolvedFlags_[iCol] += 0b0100; // 0100 + binaryInvolvedFlags_[iCol] += 0b0100; // 0100 } - } - else { // fixed to 0 + } else { // fixed to 0 if (!isFixedTo0(val, iCol)) { if (binaryInvolvedFlags_[iCol] == 0) binaryInvolvedInds_.push_back(iCol); - binaryInvolvedFlags_[iCol] += 0b1000; // 1000 + binaryInvolvedFlags_[iCol] += 0b1000; // 1000 } } } @@ -281,35 +276,30 @@ class HighsImplications { } // tools for recordTentativeCliques bool isFixedTo0(bool val, HighsInt iCol) { - if (binaryInvolvedFlags_[iCol] == 0) - return false; + if (binaryInvolvedFlags_[iCol] == 0) return false; uint8_t mask; - if (val == 0) { // probing at x = 0, last two digits + if (val == 0) { // probing at x = 0, last two digits mask = 1 << (1); return (binaryInvolvedFlags_[iCol] & mask) != 0; - } - else { // probing at x = 1, first two digits + } else { // probing at x = 1, first two digits mask = 1 << (3); return (binaryInvolvedFlags_[iCol] & mask) != 0; } } // tools for recordTentativeCliques bool isFixedTo1(bool val, HighsInt iCol) { - if (binaryInvolvedFlags_[iCol] == 0) - return false; + if (binaryInvolvedFlags_[iCol] == 0) return false; uint8_t mask; - if (val == 0) { // probing at x = 0, last two digits + if (val == 0) { // probing at x = 0, last two digits mask = 1; return (binaryInvolvedFlags_[iCol] & mask) != 0; - } - else { // probint at x = 1, first two digits + } else { // probint at x = 1, first two digits mask = 1 << (2); return (binaryInvolvedFlags_[iCol] & mask) != 0; } } - }; #endif