diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index bc2dd55591..38641281b9 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -310,7 +310,6 @@ void HighsMipSolver::run() { mipdata_->lps.back().notifyCutPoolsLpCopied(1); mipdata_->workers.back().randgen.initialise(options_mip_->random_seed + mipdata_->workers.size() - 1); - mipdata_->workers.back().nodequeue.setNumCol(numCol()); mipdata_->debugSolution.registerDomain( mipdata_->workers.back().search_ptr_->getLocalDomain()); } @@ -337,8 +336,6 @@ void HighsMipSolver::run() { worker.getLpRelaxation().setMipWorker(worker); worker.resetSearch(); worker.resetSepa(); - worker.nodequeue.clear(); - worker.nodequeue.setNumCol(numCol()); }; auto syncSolutions = [&]() -> void { @@ -452,8 +449,6 @@ void HighsMipSolver::run() { master_worker.resetSearch(); master_worker.resetSepa(); - master_worker.nodequeue.clear(); - master_worker.nodequeue.setNumCol(numCol()); master_worker.upper_bound = mipdata_->upper_bound; master_worker.upper_limit = mipdata_->upper_limit; master_worker.optimality_limit = mipdata_->optimality_limit; @@ -462,6 +457,10 @@ void HighsMipSolver::run() { master_worker.search_ptr_->getLocalDomain()); profiling_->start(kMipClockSearch); + // Maximum node processing budget for each worker in a batch during parallel + constexpr HighsInt kMaxNodesPerWorker = 100; + HighsInt maxNodesPerWorkerLim = + max_num_workers > 1 ? kMaxNodesPerWorker : kHighsIInf; int64_t numStallNodes = 0; int64_t lastLbLeave = 0; int64_t numQueueLeaves = 0; @@ -481,14 +480,14 @@ void HighsMipSolver::run() { return false; }; - auto checkRestart = [&](const HighsMipWorker& worker, - HighsInt numWorkerVotes) -> RestartVote { + auto checkRestart = [&](const HighsMipWorker& worker, HighsInt numWorkerVotes, + HighsCDouble currTreeWeight) -> RestartVote { int64_t nNodes = worker.search_ptr_->nnodes + mipdata_->num_nodes; if (!submip && nNodes >= nextCheck && options_mip_->mip_allow_restart) { const HighsInt nTreeRestarts = mipdata_->numRestarts - mipdata_->numRestartsRoot; const HighsCDouble treeWeight = - worker.search_ptr_->treeweight + mipdata_->pruned_treeweight; + currTreeWeight + mipdata_->pruned_treeweight; double currNodeEstim = numNodesLastCheck - mipdata_->num_nodes_before_run + (nNodes - numNodesLastCheck) * double(1.0 - treeWeight) / @@ -582,7 +581,8 @@ void HighsMipSolver::run() { } // Using joint information after workers are synced, query a restart const RestartVote vote = checkRestart( - master_worker, (numRestartVotes + numHugeTreeVotes + 2) / 2); + master_worker, (numRestartVotes + numHugeTreeVotes + 2) / 2, + master_worker.search_ptr_->treeweight); if (vote == RestartVote::kWouldRestart) { performRestart(); return true; @@ -609,62 +609,53 @@ void HighsMipSolver::run() { const HighsInt num_search_workers = std::min(num_workers, static_cast(mipdata_->nodequeue.numActiveNodes())); - const HighsInt num_heuristic_workers = - mipdata_->upper_bound < kHighsInf - ? std::max(HighsInt{1}, (num_search_workers + 3) / 4) - : std::max(HighsInt{1}, (num_search_workers + 1) / 2); for (HighsInt i = 0; i < num_search_workers; i++) { assert(!mipdata_->workers[i].search_ptr_->hasNode()); indices.emplace_back(i); - mipdata_->workers[i].setAllowHeuristics(i < num_heuristic_workers); + mipdata_->workers[i].setAllowHeuristics(true); } }; - auto installNodes = [&](std::vector& indices, - bool& limit_reached) -> void { - for (const HighsInt i : indices) { - if ((indices.size() == 1 && numQueueLeaves - lastLbLeave >= 10) || - (indices.size() > 1 && i == 0)) { - mipdata_->workers[i].search_ptr_->installNode( - mipdata_->nodequeue.popBestBoundNode()); - lastLbLeave = numQueueLeaves; - } else { - HighsInt bestBoundNodeStackSize = - mipdata_->nodequeue.getBestBoundDomchgStackSize(); - double bestBoundNodeLb = mipdata_->nodequeue.getBestLowerBound(); - HighsNodeQueue::OpenNode nextNode(mipdata_->nodequeue.popBestNode()); - if (nextNode.lower_bound == bestBoundNodeLb && - (HighsInt)nextNode.domchgstack.size() == bestBoundNodeStackSize) + auto prepareNodes = [&](const std::vector& indices) { + const HighsInt numNodesPerWorker = + indices.size() > 1 && + static_cast(mipdata_->nodequeue.numActiveNodes()) >= + 2 * indices.size() + ? 2 + : 1; + for (HighsInt j = 0; j != numNodesPerWorker; j++) { + for (const HighsInt i : indices) { + if ((indices.size() == 1 && numQueueLeaves - lastLbLeave >= 10) || + (indices.size() > 1 && i == 0 && j == 0)) { + mipdata_->workers[i].preparedNodes.push_back( + std::move(mipdata_->nodequeue.popBestBoundNode())); lastLbLeave = numQueueLeaves; - mipdata_->workers[i].search_ptr_->installNode(std::move(nextNode)); - } - - ++numQueueLeaves; - - if (mipdata_->workers[i].search_ptr_->getCurrentEstimate() >= - mipdata_->upper_limit) { - ++numStallNodes; - if (options_mip_->mip_max_stall_nodes != kHighsIInf && - numStallNodes >= options_mip_->mip_max_stall_nodes) { - limit_reached = true; - modelstatus_ = HighsModelStatus::kSolutionLimit; - break; + } else { + HighsInt bestBoundNodeStackSize = + mipdata_->nodequeue.getBestBoundDomchgStackSize(); + double bestBoundNodeLb = mipdata_->nodequeue.getBestLowerBound(); + mipdata_->workers[i].preparedNodes.push_back( + std::move(mipdata_->nodequeue.popBestNode())); + HighsNodeQueue::OpenNode& nextNode = + mipdata_->workers[i].preparedNodes.back(); + if (nextNode.lower_bound == bestBoundNodeLb && + static_cast(nextNode.domchgstack.size()) == + bestBoundNodeStackSize) + lastLbLeave = numQueueLeaves; } - } else - numStallNodes = 0; + ++numQueueLeaves; + } } }; auto evaluateNode = [&](HighsInt i) -> bool { if (!mipdata_->parallelLockActive()) profiling_->start(kMipClockEvaluateNode1); - if (mipdata_->workers[i].search_ptr_->evaluateNode() == + HighsMipWorker& worker = mipdata_->workers[i]; + if (worker.search_ptr_->evaluateNode() == HighsSearch::NodeResult::kSubOptimal) { - HighsNodeQueue& globalqueue = mipdata_->parallelLockActive() - ? mipdata_->workers[i].nodequeue - : mipdata_->nodequeue; - mipdata_->workers[i].search_ptr_->currentNodeToQueue(globalqueue); + worker.search_ptr_->stashCurrentNode(); if (!mipdata_->parallelLockActive()) profiling_->stop(kMipClockEvaluateNode1); return true; @@ -674,7 +665,15 @@ void HighsMipSolver::run() { return false; }; - auto pruneNode = [&](HighsInt i) -> bool { + auto assignEarlyTermination = [&](HighsMipWorker& worker) { + if (worker.getGlobalDomain().infeasible() || + mipdata_->lower_bound > worker.getOptimalityLimit()) { + mipdata_->updateWorkerEarlyTermination(worker); + } + }; + + auto pruneNode = [&](const HighsInt i, bool& infeasible) -> bool { + assert(infeasible == false); if (!mipdata_->parallelLockActive()) profiling_->start(kMipClockNodePrunedLoop); bool pruned = false; @@ -684,20 +683,17 @@ void HighsMipSolver::run() { pruned = true; ++mipdata_->workers[i].search_ptr_->getLocalNodes(); ++mipdata_->workers[i].search_ptr_->getLocalLeaves(); + if (mipdata_->workers[i].getGlobalDomain().infeasible()) { + assignEarlyTermination(mipdata_->workers[i]); + infeasible = true; + } } if (!mipdata_->parallelLockActive()) profiling_->stop(kMipClockNodePrunedLoop); - return mipdata_->workers[i].getGlobalDomain().infeasible() || pruned; - }; - - auto assignEarlyTermination = [&](HighsMipWorker& worker) { - if (worker.getGlobalDomain().infeasible() || - mipdata_->lower_bound > worker.getOptimalityLimit()) { - mipdata_->updateWorkerEarlyTermination(worker); - } + return pruned; }; - auto separateAndStoreBasis = [&](HighsInt i) -> bool { + auto separateAndStoreBasis = [&](const HighsInt i, bool& infeasible) -> void { HighsMipWorker& worker = mipdata_->workers[i]; if (options_mip_->mip_allow_cut_separation_at_nodes) { if (!mipdata_->parallelLockActive()) @@ -711,12 +707,10 @@ void HighsMipSolver::run() { if (worker.getGlobalDomain().infeasible()) { worker.search_ptr_->cutoffNode(); - HighsNodeQueue& globalqueue = mipdata_->parallelLockActive() - ? worker.nodequeue - : mipdata_->nodequeue; - worker.search_ptr_->openNodesToQueue(globalqueue); + worker.search_ptr_->stashOpenNodes(); assignEarlyTermination(worker); - return true; + infeasible = true; + return; } if (worker.getLpRelaxation().getStatus() != @@ -735,32 +729,28 @@ void HighsMipSolver::run() { basis = std::make_shared(std::move(b)); worker.getLpRelaxation().setStoredBasis(basis); } - - return false; }; auto backtrackPlunge = [&](HighsInt i) { if (!mipdata_->parallelLockActive()) profiling_->start(kMipClockBacktrackPlunge); - const bool backtrack_plunge = - mipdata_->workers[i].search_ptr_->backtrackPlunge( - mipdata_->parallelLockActive() ? mipdata_->workers[i].nodequeue - : mipdata_->nodequeue); + HighsMipWorker& worker = mipdata_->workers[i]; + const bool backtrack_plunge = worker.search_ptr_->backtrackPlunge(); if (!mipdata_->parallelLockActive()) profiling_->stop(kMipClockBacktrackPlunge); if (!backtrack_plunge) return true; - assert(mipdata_->workers[i].search_ptr_->hasNode()); + assert(worker.search_ptr_->hasNode()); - if (mipdata_->workers[i].getConflictPool().getNumConflicts() > + if (worker.getConflictPool().getNumConflicts() > options_mip_->mip_pool_soft_limit) { - mipdata_->workers[i].getConflictPool().performAging(); + worker.getConflictPool().performAging(); } return false; }; - auto runHeuristics = [&](HighsInt i) -> bool { + auto runHeuristics = [&](const HighsInt i, bool& infeasible) -> bool { HighsMipWorker& worker = mipdata_->workers[i]; if (!mipdata_->parallelLockActive()) profiling_->start(kMipClockDiveEvaluateNode); @@ -814,7 +804,8 @@ void HighsMipSolver::run() { if (!mipdata_->parallelLockActive()) profiling_->stop(kMipClockDivePrimalHeuristics); - return worker.getGlobalDomain().infeasible(); + if (worker.getGlobalDomain().infeasible()) infeasible = true; + return false; }; auto dive = [&](HighsInt i, HighsInt nodeLim) -> bool { @@ -837,55 +828,114 @@ void HighsMipSolver::run() { auto processNodes = [&](const std::vector& indices, std::vector& restarts, + std::vector& stallNodes, const bool skip_separation, const HighsInt nodeLim, const HighsInt plungeLimit, double avgiter) { - auto processNode = [&](const HighsInt i) { + auto processWorkerNodes = [&](const HighsInt i) { HighsMipWorker& worker = mipdata_->workers[i]; - int64_t nodes_explored = 0; - if (!skip_separation) { - evaluateNode(i); - assignEarlyTermination(worker); - if (pruneNode(i)) return; - if (worker.search_ptr_->checkLimits()) return; - if (!mipdata_->parallelLockActive()) mipdata_->printDisplayLine(); - if (separateAndStoreBasis(i)) return; - } - worker.getConflictPool().performAging(); - HighsInt iterlimit = 10 * std::max(avgiter, mipdata_->avgrootlpiters); - iterlimit = std::max({HighsInt{10000}, iterlimit, - HighsInt((3 * mipdata_->firstrootlpiters) / 2)}); - worker.getLpRelaxation().setIterationLimit(iterlimit); + worker.prepNodeIdx = 0; + int64_t total_nodes_explored = 0; bool considerHeuristics = true; - while (true) { - if (considerHeuristics && (skip_separation || nodeLim == kHighsIInf) && - worker.getAllowHeuristics() && mipdata_->moreHeuristicsAllowed()) { - if (runHeuristics(i)) break; + bool stop = false; + while (worker.prepNodeIdx != worker.preparedNodes.size()) { + int64_t nodes_explored = 0; + int64_t lastLocalNodeCount = worker.search_ptr_->getLocalNodes(); + worker.search_ptr_->installNode( + std::move(worker.preparedNodes[worker.prepNodeIdx])); + ++worker.prepNodeIdx; + if (worker.search_ptr_->getCurrentEstimate() >= worker.upper_limit) { + stallNodes[i]++; } - considerHeuristics = false; - if (worker.getGlobalDomain().infeasible()) break; - if (dive(i, nodeLim)) break; - if (worker.search_ptr_->checkLimits( - worker.search_ptr_->getLocalNodes())) { - break; + if (!skip_separation) { + evaluateNode(i); + assignEarlyTermination(worker); + if (pruneNode(i, stop)) { + total_nodes_explored++; + if (worker.early_termination) stop = true; + if (stop || total_nodes_explored >= nodeLim) break; + continue; + } + if (stop || worker.search_ptr_->checkLimits()) { + stop = true; + break; + }; + if (!mipdata_->parallelLockActive()) mipdata_->printDisplayLine(); + separateAndStoreBasis(i, stop); + if (stop) break; } - - if (worker.search_ptr_->getLocalNodes() + nodes_explored >= plungeLimit) - break; - if (!mipdata_->parallelLockActive()) { - nodes_explored += worker.search_ptr_->getLocalNodes(); + worker.getConflictPool().performAging(); + HighsInt iterlimit = 10 * std::max(avgiter, mipdata_->avgrootlpiters); + iterlimit = std::max( + {HighsInt{10000}, iterlimit, + static_cast((3 * mipdata_->firstrootlpiters) / 2)}); + worker.getLpRelaxation().setIterationLimit(iterlimit); + while (true) { + if (considerHeuristics && + (skip_separation || nodeLim == maxNodesPerWorkerLim) && + worker.getAllowHeuristics() && + mipdata_->moreHeuristicsAllowed()) { + if (runHeuristics(i, stop)) break; + if (stop) break; + } + considerHeuristics = false; + if (worker.getGlobalDomain().infeasible()) { + stop = true; + break; + } + if (dive(i, nodeLim)) break; + if (worker.search_ptr_->checkLimits( + worker.search_ptr_->getLocalNodes())) { + stop = true; + break; + } + + nodes_explored += + worker.search_ptr_->getLocalNodes() - lastLocalNodeCount; + lastLocalNodeCount = worker.search_ptr_->getLocalNodes(); + if (nodes_explored >= plungeLimit) break; + if (backtrackPlunge(i)) break; + if (!mipdata_->parallelLockActive()) { + worker.search_ptr_->flushStatistics(*this); + mipdata_->printDisplayLine(); + lastLocalNodeCount = 0; + } } - if (backtrackPlunge(i)) break; - if (!mipdata_->parallelLockActive()) { - worker.search_ptr_->flushStatistics(*this); - mipdata_->printDisplayLine(); + nodes_explored += + worker.search_ptr_->getLocalNodes() - lastLocalNodeCount; + lastLocalNodeCount = worker.search_ptr_->getLocalNodes(); + total_nodes_explored += nodes_explored; + assignEarlyTermination(worker); + if (worker.early_termination) stop = true; + worker.search_ptr_->stashOpenNodes(); + if (total_nodes_explored >= nodeLim || stop) { + break; } } - assignEarlyTermination(worker); - if (nodeLim == kHighsIInf) { - restarts[i] = checkRestart(worker, 1); + if (worker.search_ptr_->hasNode()) { + worker.search_ptr_->stashOpenNodes(); + } + for (size_t j = worker.prepNodeIdx; j != worker.preparedNodes.size(); + j++) { + worker.processedNodes.emplace_back(std::move(worker.preparedNodes[j]), + worker.search_ptr_->countTreeWeight); + } + worker.preparedNodes.clear(); + worker.prepNodeIdx = 0; + if (nodeLim == maxNodesPerWorkerLim) { + // Need to spoof tree weight + HighsCDouble currTreeWeight = worker.search_ptr_->treeweight; + for (auto& node_treeweight_pair : worker.processedNodes) { + if (node_treeweight_pair.second && + node_treeweight_pair.first.lower_bound > + worker.optimality_limit) { + currTreeWeight += + std::ldexp(1.0, 1 - node_treeweight_pair.first.depth); + } + } + restarts[i] = checkRestart(worker, 1, currTreeWeight); } }; - runTask(processNode, tg, true, false, indices); + runTask(processWorkerNodes, tg, true, false, indices); }; auto syncSepaStats = [&](HighsMipWorker& worker) { @@ -901,8 +951,9 @@ void HighsMipSolver::run() { search_indices.reserve(max_num_workers); std::vector workerRestartVotes(getMaxNumWorkers(), RestartVote::kNoCheck); + std::vector stallNodesPerWorker(getMaxNumWorkers()); bool root_node = true; // Don't separate the root node again - HighsInt nodeLim = max_num_workers > 1 ? 1 : kHighsIInf; // for ramp up + HighsInt nodeLim = max_num_workers > 1 ? 1 : maxNodesPerWorkerLim; // ramp-up while (!mipdata_->nodequeue.empty()) { // Possibly query existence of an external solution if (!submip) @@ -921,25 +972,24 @@ void HighsMipSolver::run() { // Assign nodes to workers bool limit_reached = false; if (root_node) { - master_worker.search_ptr_->installNode( - mipdata_->nodequeue.popBestBoundNode()); + master_worker.preparedNodes.push_back( + std::move(mipdata_->nodequeue.popBestBoundNode())); } else { - installNodes(search_indices, limit_reached); + prepareNodes(search_indices); } - if (limit_reached) break; - if (nodeLim != kHighsIInf) { + if (nodeLim != maxNodesPerWorkerLim) { if (num_workers >= max_num_workers) { nodeLim = std::max(HighsInt{20}, 2 * nodeLim); } - if (nodeLim > 100) { - nodeLim = kHighsIInf; + if (nodeLim >= maxNodesPerWorkerLim) { + nodeLim = maxNodesPerWorkerLim; } } // Process nodes (separation / heuristics / dives) - processNodes(search_indices, workerRestartVotes, root_node, nodeLim, 100, - mipdata_->getLp().getAvgSolveIters()); + processNodes(search_indices, workerRestartVotes, stallNodesPerWorker, + root_node, nodeLim, 100, mipdata_->getLp().getAvgSolveIters()); root_node = false; @@ -970,7 +1020,7 @@ void HighsMipSolver::run() { for (const HighsInt i : search_indices) { if (i == early_terminated_worker) continue; HighsMipWorker& worker = mipdata_->workers[i]; - worker.nodequeue.clear(); + worker.processedNodes.clear(); worker.search_ptr_->resetStatistics(); worker.resetHeurStats(); worker.resetSepaStats(); @@ -989,14 +1039,17 @@ void HighsMipSolver::run() { infeasible = true; } profiling_->start(kMipClockOpenNodesToQueue0); - worker.search_ptr_->openNodesToQueue(mipdata_->nodequeue); - while (worker.nodequeue.numNodes() > 0) { - HighsNodeQueue::OpenNode node = - std::move(worker.nodequeue.popBestNode()); - mipdata_->nodequeue.emplaceNode( + assert(!worker.search_ptr_->hasNode()); + for (auto& node_treeweight_pair : worker.processedNodes) { + HighsNodeQueue::OpenNode& node = node_treeweight_pair.first; + double tmpTreeWeight = mipdata_->nodequeue.emplaceNode( std::move(node.domchgstack), std::move(node.branchings), node.lower_bound, node.estimate, node.depth); + if (node_treeweight_pair.second) { + worker.search_ptr_->treeweight += tmpTreeWeight; + } } + worker.processedNodes.clear(); profiling_->stop(kMipClockOpenNodesToQueue0); worker.search_ptr_->flushStatistics(*this); syncSepaStats(worker); @@ -1023,6 +1076,23 @@ void HighsMipSolver::run() { break; } + for (const HighsInt i : search_indices) { + if (stallNodesPerWorker[i] == 0) { + numStallNodes = 0; + } else { + numStallNodes += stallNodesPerWorker[i]; + } + stallNodesPerWorker[i] = 0; + if (options_mip_->mip_max_stall_nodes != kHighsIInf && + numStallNodes >= + std::max(HighsInt{1}, options_mip_->mip_max_stall_nodes)) { + limit_reached = true; + modelstatus_ = HighsModelStatus::kSolutionLimit; + break; + } + } + if (limit_reached) break; + assert(!nodesInstalled()); // Sync global information @@ -1056,7 +1126,8 @@ void HighsMipSolver::run() { mipdata_->nodequeue.numNodes() > num_workers; resetGlobalDomain(spawn_more_workers, mipdata_->hasMultipleWorkers()); - if (nodeLim == kHighsIInf && checkWorkerRestartVotes(workerRestartVotes)) + if (nodeLim == maxNodesPerWorkerLim && + checkWorkerRestartVotes(workerRestartVotes)) goto restart; if (spawn_more_workers) { diff --git a/highs/mip/HighsMipSolver.h b/highs/mip/HighsMipSolver.h index f989d288d7..e2b1bacdef 100644 --- a/highs/mip/HighsMipSolver.h +++ b/highs/mip/HighsMipSolver.h @@ -175,12 +175,10 @@ class HighsMipSolver { void setParallelLock(bool lock) const; void setProfiling(HighsProfiling* profiling); HighsInt getMaxNumWorkers() const { - if (highs::parallel::num_threads() == 1 || - options_mip_->parallel != kHighsOnString || submip) { + if (options_mip_->parallel != kHighsOnString || submip) { return 1; } - return static_cast( - std::ceil(1.7 * highs::parallel::num_threads())); + return highs::parallel::num_threads(); } }; diff --git a/highs/mip/HighsMipWorker.h b/highs/mip/HighsMipWorker.h index 1836e83d95..a600f03116 100644 --- a/highs/mip/HighsMipWorker.h +++ b/highs/mip/HighsMipWorker.h @@ -70,7 +70,9 @@ class HighsMipWorker { public: std::unique_ptr search_ptr_; std::unique_ptr sepa_ptr_; - HighsNodeQueue nodequeue; + std::vector> processedNodes; + std::vector preparedNodes; + size_t prepNodeIdx; double upper_bound; double upper_limit; diff --git a/highs/mip/HighsSearch.cpp b/highs/mip/HighsSearch.cpp index bf39ada9ef..ff56f31f40 100644 --- a/highs/mip/HighsSearch.cpp +++ b/highs/mip/HighsSearch.cpp @@ -8,6 +8,7 @@ #include "mip/HighsSearch.h" #include +#include #include "lp_data/HConst.h" #include "mip/HighsCutGeneration.h" @@ -716,7 +717,18 @@ const HighsSearch::NodeData* HighsSearch::getParentNodeData() const { return &nodestack[nodestack.size() - 2]; } -void HighsSearch::currentNodeToQueue(HighsNodeQueue& nodequeue) { +void HighsSearch::stashNodeToProcessed(double lb, double estimate, + HighsInt depth) { + std::vector branchPositions; + auto domchgStack = localdom.getReducedDomainChangeStack(branchPositions); + mipworker.processedNodes.emplace_back( + std::piecewise_construct, + std::forward_as_tuple(std::move(domchgStack), std::move(branchPositions), + lb, estimate, depth), + std::forward_as_tuple(countTreeWeight)); +} + +void HighsSearch::stashCurrentNode() { auto oldchangedcols = localdom.getChangedCols().size(); bool prune = nodestack.back().lower_bound > getCutoffBound(); if (!prune) { @@ -728,14 +740,9 @@ void HighsSearch::currentNodeToQueue(HighsNodeQueue& nodequeue) { pseudocost); } if (!prune) { - std::vector branchPositions; - auto domchgStack = localdom.getReducedDomainChangeStack(branchPositions); - double tmpTreeWeight = nodequeue.emplaceNode( - std::move(domchgStack), std::move(branchPositions), - std::max(nodestack.back().lower_bound, - localdom.getObjectiveLowerBound()), - nodestack.back().estimate, getCurrentDepth()); - if (countTreeWeight) treeweight += tmpTreeWeight; + stashNodeToProcessed(std::max(nodestack.back().lower_bound, + localdom.getObjectiveLowerBound()), + nodestack.back().estimate, getCurrentDepth()); } else { mipsolver.mipdata_->debugSolution.nodePruned(localdom); if (countTreeWeight) treeweight += std::ldexp(1.0, 1 - getCurrentDepth()); @@ -743,7 +750,7 @@ void HighsSearch::currentNodeToQueue(HighsNodeQueue& nodequeue) { nodestack.back().opensubtrees = 0; } -void HighsSearch::openNodesToQueue(HighsNodeQueue& nodequeue) { +void HighsSearch::stashOpenNodes() { if (nodestack.empty()) return; // get the basis of the node highest up in the tree @@ -769,14 +776,9 @@ void HighsSearch::openNodesToQueue(HighsNodeQueue& nodequeue) { mipworker.getGlobalDomain(), pseudocost); } if (!prune) { - std::vector branchPositions; - auto domchgStack = localdom.getReducedDomainChangeStack(branchPositions); - double tmpTreeWeight = nodequeue.emplaceNode( - std::move(domchgStack), std::move(branchPositions), - std::max(nodestack.back().lower_bound, - localdom.getObjectiveLowerBound()), - nodestack.back().estimate, getCurrentDepth()); - if (countTreeWeight) treeweight += tmpTreeWeight; + stashNodeToProcessed(std::max(nodestack.back().lower_bound, + localdom.getObjectiveLowerBound()), + nodestack.back().estimate, getCurrentDepth()); } else { mipsolver.mipdata_->debugSolution.nodePruned(localdom); if (countTreeWeight) treeweight += std::ldexp(1.0, 1 - getCurrentDepth()); @@ -1620,7 +1622,7 @@ bool HighsSearch::backtrack(bool recoverBasis) { return true; } -bool HighsSearch::backtrackPlunge(HighsNodeQueue& nodequeue) { +bool HighsSearch::backtrackPlunge() { const std::vector& domchgstack = localdom.getDomainChangeStack(); @@ -1687,21 +1689,14 @@ bool HighsSearch::backtrackPlunge(HighsNodeQueue& nodequeue) { currnode.opensubtrees = 0; bool fallbackbranch = currnode.branchingdecision.boundval == currnode.branching_point; - double nodeScore; if (currnode.branchingdecision.boundtype == HighsBoundType::kLower) { currnode.branchingdecision.boundtype = HighsBoundType::kUpper; currnode.branchingdecision.boundval = std::floor(currnode.branchingdecision.boundval - 0.5); - nodeScore = pseudocost.getScoreDown( - currnode.branchingdecision.column, - fallbackbranch ? 0.5 : currnode.branching_point); } else { currnode.branchingdecision.boundtype = HighsBoundType::kLower; currnode.branchingdecision.boundval = std::ceil(currnode.branchingdecision.boundval + 0.5); - nodeScore = pseudocost.getScoreUp( - currnode.branchingdecision.column, - fallbackbranch ? 0.5 : currnode.branching_point); } if (fallbackbranch) @@ -1738,49 +1733,11 @@ bool HighsSearch::backtrackPlunge(HighsNodeQueue& nodequeue) { nodelb = std::max(nodelb, localdom.getObjectiveLowerBound()); bool nodeToQueue = nodelb > mipworker.getOptimalityLimit(); - // we check if switching to the other branch of an ancestor yields a higher - // additive branch score than staying in this node and if so we postpone the - // node and put it to the queue to backtrack further. - if (!nodeToQueue) { - for (HighsInt i = nodestack.size() - 2; i >= 0; --i) { - if (nodestack[i].opensubtrees == 0) continue; - - bool fallbackbranch = nodestack[i].branchingdecision.boundval == - nodestack[i].branching_point; - double branchpoint = - fallbackbranch ? 0.5 : nodestack[i].branching_point; - double ancestorScoreActive; - double ancestorScoreInactive; - if (nodestack[i].branchingdecision.boundtype == - HighsBoundType::kLower) { - ancestorScoreInactive = pseudocost.getScoreDown( - nodestack[i].branchingdecision.column, branchpoint); - ancestorScoreActive = pseudocost.getScoreUp( - nodestack[i].branchingdecision.column, branchpoint); - } else { - ancestorScoreActive = pseudocost.getScoreDown( - nodestack[i].branchingdecision.column, branchpoint); - ancestorScoreInactive = pseudocost.getScoreUp( - nodestack[i].branchingdecision.column, branchpoint); - } - - // if (!mipsolver.submip) - // printf("nodeScore: %g, ancestorScore: %g\n", nodeScore, - // ancestorScore); - nodeToQueue = ancestorScoreInactive - ancestorScoreActive > - nodeScore + getFeasTol(); - break; - } - } if (nodeToQueue) { // if (!mipsolver.submip) printf("node goes to queue\n"); - std::vector branchPositions; - auto domchgStack = localdom.getReducedDomainChangeStack(branchPositions); - double tmpTreeWeight = nodequeue.emplaceNode( - std::move(domchgStack), std::move(branchPositions), nodelb, - nodestack.back().estimate, getCurrentDepth() + 1); - if (countTreeWeight) treeweight += tmpTreeWeight; + stashNodeToProcessed(nodelb, nodestack.back().estimate, + getCurrentDepth() + 1); localdom.backtrack(); localdom.clearChangedCols(numChangedCols); continue; diff --git a/highs/mip/HighsSearch.h b/highs/mip/HighsSearch.h index 373bfbc4a2..b927cf3eab 100644 --- a/highs/mip/HighsSearch.h +++ b/highs/mip/HighsSearch.h @@ -145,6 +145,8 @@ class HighsSearch { bool orbitsValidInChildNode(const HighsDomainChange& branchChg) const; + void stashNodeToProcessed(double lb, double estimate, HighsInt depth); + public: HighsSearch(HighsMipWorker& mipworker, HighsPseudocost& pseudocost); @@ -200,9 +202,9 @@ class HighsSearch { HighsInt getCurrentDepth() const { return nodestack.size() + depthoffset; } - void openNodesToQueue(HighsNodeQueue& nodequeue); + void stashOpenNodes(); - void currentNodeToQueue(HighsNodeQueue& nodequeue); + void stashCurrentNode(); void flushStatistics(HighsMipSolver& mipsolver); @@ -226,10 +228,9 @@ class HighsSearch { /// backtrack one level in DFS manner bool backtrack(bool recoverBasis = true); - /// backtrack an unspecified amount of depth level until the next - /// node that seems worthwhile to continue the plunge. Put unpromising nodes - /// to the node queue - bool backtrackPlunge(HighsNodeQueue& nodequeue); + /// backtrack until the next node (DFS manner) that can be worthwhile + /// to continue the plunge. Put unpromising nodes to workers processedNodes + bool backtrackPlunge(); /// for heuristics. Will discard nodes above targetDepth regardless of their /// status