Skip to content

Commit 1d8882e

Browse files
committed
Make master worker use local global copy
1 parent 892d098 commit 1d8882e

4 files changed

Lines changed: 86 additions & 165 deletions

File tree

highs/mip/HighsMipSolver.cpp

Lines changed: 84 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -250,34 +250,6 @@ void HighsMipSolver::run() {
250250
int(mipdata_->lps.at(0).getLpSolver().getNumRow()));
251251

252252
std::shared_ptr<const HighsBasis> basis;
253-
// HighsSearch search{*this, mipdata_->pseudocost};
254-
255-
// HighsSearch& search = *mipdata_->workers[0].search_ptr_.get();
256-
257-
// they should live in the same space so the pointers don't get confused.
258-
// HighsMipWorker master_worker(*this, mipdata_->lp);
259-
// HighsSearch& search = *master_worker.search_ptr_.get();
260-
261-
// This version works during refactor with the master pseudocost.
262-
// valgrind OK.
263-
// HighsSearch search{master_worker, mipdata_->pseudocost};
264-
// search.setLpRelaxation(&mipdata_->lp);
265-
// MT: I think search should be ties to the master worker
266-
master_worker.resetSearch();
267-
master_worker.resetSepa();
268-
HighsSearch& search = *master_worker.search_ptr_;
269-
270-
// This search is from the worker and will use the worker pseudocost.
271-
// does not work yet, fails at domain propagation somewhere.
272-
// HighsSearch& search = *mipdata_->workers[0].search_ptr_.get();
273-
// search.setLpRelaxation(&mipdata_->lp);
274-
275-
mipdata_->debugSolution.registerDomain(search.getLocalDomain());
276-
277-
// HighsSeparation sepa(*this);
278-
// HighsSeparation sepa(master_worker);
279-
// sepa.setLpRelaxation(&mipdata_->lp);
280-
HighsSeparation& sepa = *master_worker.sepa_ptr_;
281253

282254
double prev_lower_bound = mipdata_->lower_bound;
283255

@@ -301,23 +273,12 @@ void HighsMipSolver::run() {
301273
assert(num_nodes == 1);
302274
}
303275

304-
search.installNode(mipdata_->nodequeue.popBestBoundNode());
305-
int64_t numStallNodes = 0;
306-
int64_t lastLbLeave = 0;
307-
int64_t numQueueLeaves = 0;
308-
HighsInt numHugeTreeEstim = 0;
309-
int64_t numNodesLastCheck = mipdata_->num_nodes;
310-
int64_t nextCheck = mipdata_->num_nodes;
311-
double treeweightLastCheck = 0.0;
312-
double upperLimLastCheck = mipdata_->upper_limit;
313-
double lowerBoundLastCheck = mipdata_->lower_bound;
314-
analysis_.mipTimerStart(kMipClockSearch);
315-
316-
int k = 0;
317-
318276
// Initialize worker relaxations and mipworkers
319277
const HighsInt mip_search_concurrency = options_mip_->mip_search_concurrency;
320-
const HighsInt num_worker = mip_search_concurrency - 1;
278+
const HighsInt num_workers =
279+
highs::parallel::num_threads() == 1 || mip_search_concurrency <= 1
280+
? 1
281+
: mip_search_concurrency * highs::parallel::num_threads();
321282
highs::parallel::TaskGroup tg;
322283

323284
auto destroyOldWorkers = [&]() {
@@ -339,7 +300,9 @@ void HighsMipSolver::run() {
339300
}
340301
};
341302

342-
auto constructMasterWorkerPools = [&](HighsMipWorker& worker) {
303+
auto constructAdditionalWorkerData = [&](HighsMipWorker& worker) {
304+
// A use case: Change pointer in master worker to local copies of global
305+
// info
343306
assert(mipdata_->cutpools.size() == 1 &&
344307
mipdata_->conflictpools.size() == 1);
345308
assert(&worker == &mipdata_->workers.at(0));
@@ -349,9 +312,12 @@ void HighsMipSolver::run() {
349312
mipdata_->conflictpools.emplace_back(5 * options_mip_->mip_pool_age_limit,
350313
options_mip_->mip_pool_soft_limit);
351314
worker.conflictpool_ = &mipdata_->conflictpools.back();
352-
// TODO MT: Is this needed? (Probably)
353-
// worker.search_ptr_->localdom.addCutpool(*worker.cutpool_);
354-
// worker.search_ptr_->localdom.addConflictPool(*worker.conflictpool_);
315+
mipdata_->domains.emplace_back(mipdata_->domain);
316+
worker.globaldom_ = &mipdata_->domains.back();
317+
worker.globaldom_->addCutpool(*worker.cutpool_);
318+
worker.globaldom_->addConflictPool(*worker.conflictpool_);
319+
worker.resetSearch();
320+
worker.lprelaxation_->setMipWorker(worker);
355321
};
356322

357323
auto createNewWorker = [&](HighsInt i) {
@@ -368,14 +334,52 @@ void HighsMipSolver::run() {
368334
mipdata_->lp.notifyCutPoolsLpCopied(1);
369335
};
370336

337+
auto resetGlobalDomain = [&](bool force = false) -> void {
338+
// if global propagation found bound changes, we update the domain
339+
if (!mipdata_->domain.getChangedCols().empty() || force) {
340+
analysis_.mipTimerStart(kMipClockUpdateLocalDomain);
341+
highsLogDev(options_mip_->log_options, HighsLogType::kInfo,
342+
"added %" HIGHSINT_FORMAT " global bound changes\n",
343+
(HighsInt)mipdata_->domain.getChangedCols().size());
344+
mipdata_->cliquetable.cleanupFixed(mipdata_->domain);
345+
for (HighsInt col : mipdata_->domain.getChangedCols())
346+
mipdata_->implications.cleanupVarbounds(col);
347+
348+
mipdata_->domain.setDomainChangeStack(std::vector<HighsDomainChange>());
349+
mipdata_->domain.clearChangedCols();
350+
mipdata_->workers[0].search_ptr_->resetLocalDomain();
351+
mipdata_->removeFixedIndices();
352+
analysis_.mipTimerStop(kMipClockUpdateLocalDomain);
353+
}
354+
};
355+
356+
// TODO: Should we be propagating this first?
357+
if (num_workers > 1) resetGlobalDomain(true);
371358
destroyOldWorkers();
372-
constructMasterWorkerPools(master_worker);
359+
constructAdditionalWorkerData(master_worker);
373360
master_worker.upper_bound = mipdata_->upper_bound;
374361
master_worker.solutions_.clear();
375-
for (HighsInt i = 1; i != mip_search_concurrency; ++i) {
362+
for (HighsInt i = 1; i != num_workers; ++i) {
376363
createNewWorker(i);
377364
}
378365

366+
master_worker.resetSepa();
367+
HighsSearch& search = *master_worker.search_ptr_;
368+
mipdata_->debugSolution.registerDomain(search.getLocalDomain());
369+
HighsSeparation& sepa = *master_worker.sepa_ptr_;
370+
371+
analysis_.mipTimerStart(kMipClockSearch);
372+
search.installNode(mipdata_->nodequeue.popBestBoundNode());
373+
int64_t numStallNodes = 0;
374+
int64_t lastLbLeave = 0;
375+
int64_t numQueueLeaves = 0;
376+
HighsInt numHugeTreeEstim = 0;
377+
int64_t numNodesLastCheck = mipdata_->num_nodes;
378+
int64_t nextCheck = mipdata_->num_nodes;
379+
double treeweightLastCheck = 0.0;
380+
double upperLimLastCheck = mipdata_->upper_limit;
381+
double lowerBoundLastCheck = mipdata_->lower_bound;
382+
379383
// Lambda for combining limit_reached across searches
380384
auto limitReached = [&]() -> bool {
381385
bool limit_reached = false;
@@ -394,20 +398,6 @@ void HighsMipSolver::run() {
394398
return break_search;
395399
};
396400

397-
// Lambda checking whether loop pass is to be skipped
398-
auto performedDive = [&](const HighsSearch& search,
399-
const HighsInt iSearch) -> bool {
400-
if (iSearch == 0) {
401-
assert(search.performed_dive_);
402-
} else {
403-
assert(!search.performed_dive_);
404-
}
405-
// Make sure that if a dive has been performed, we're not
406-
// continuing after breaking from the search
407-
if (search.performed_dive_) assert(!breakSearch());
408-
return search.performed_dive_;
409-
};
410-
411401
auto setParallelLock = [&](bool lock) -> void {
412402
if (!mipdata_->hasMultipleWorkers()) return;
413403
mipdata_->parallel_lock = lock;
@@ -464,36 +454,19 @@ void HighsMipSolver::run() {
464454
// 2. Push all changes from the true global domain
465455
// 3. Clear changedCols and domChgStack, and reset local search domain for
466456
// all workers
467-
for (HighsInt i = 1; i < mipdata_->workers.size(); ++i) {
468-
mipdata_->workers[i].getGlobalDomain().backtrackToGlobal();
469-
for (const HighsDomainChange& domchg :
470-
mipdata_->domain.getDomainChangeStack()) {
471-
mipdata_->workers[i].getGlobalDomain().changeBound(
472-
domchg, HighsDomain::Reason::unspecified());
457+
// TODO MT: Is it simpler to just copy the domain each time
458+
if (mipdata_->hasMultipleWorkers()) {
459+
for (HighsMipWorker& worker : mipdata_->workers) {
460+
for (const HighsDomainChange& domchg :
461+
mipdata_->domain.getDomainChangeStack()) {
462+
worker.getGlobalDomain().changeBound(
463+
domchg, HighsDomain::Reason::unspecified());
464+
}
465+
worker.getGlobalDomain().setDomainChangeStack(
466+
std::vector<HighsDomainChange>());
467+
worker.getGlobalDomain().clearChangedCols();
468+
worker.search_ptr_->resetLocalDomain();
473469
}
474-
mipdata_->workers[i].getGlobalDomain().setDomainChangeStack(
475-
std::vector<HighsDomainChange>());
476-
mipdata_->workers[i].getGlobalDomain().clearChangedCols();
477-
mipdata_->workers[i].search_ptr_->resetLocalDomain();
478-
}
479-
};
480-
481-
auto resetMasterWorkerDomain = [&]() -> void {
482-
// if global propagation found bound changes, we update the domain
483-
if (!mipdata_->domain.getChangedCols().empty()) {
484-
analysis_.mipTimerStart(kMipClockUpdateLocalDomain);
485-
highsLogDev(options_mip_->log_options, HighsLogType::kInfo,
486-
"added %" HIGHSINT_FORMAT " global bound changes\n",
487-
(HighsInt)mipdata_->domain.getChangedCols().size());
488-
mipdata_->cliquetable.cleanupFixed(mipdata_->domain);
489-
for (HighsInt col : mipdata_->domain.getChangedCols())
490-
mipdata_->implications.cleanupVarbounds(col);
491-
492-
mipdata_->domain.setDomainChangeStack(std::vector<HighsDomainChange>());
493-
mipdata_->domain.clearChangedCols();
494-
search.resetLocalDomain();
495-
mipdata_->removeFixedIndices();
496-
analysis_.mipTimerStop(kMipClockUpdateLocalDomain);
497470
}
498471
};
499472

@@ -513,7 +486,7 @@ void HighsMipSolver::run() {
513486

514487
auto getSearchIndicesWithNoNodes = [&]() -> std::vector<HighsInt> {
515488
std::vector<HighsInt> search_indices;
516-
for (HighsInt i = 0; i < mip_search_concurrency; i++) {
489+
for (HighsInt i = 0; i < mipdata_->workers.size(); i++) {
517490
if (!mipdata_->workers[i].search_ptr_->hasNode()) {
518491
search_indices.emplace_back(i);
519492
}
@@ -526,7 +499,7 @@ void HighsMipSolver::run() {
526499

527500
auto getSearchIndicesWithNodes = [&]() -> std::vector<HighsInt> {
528501
std::vector<HighsInt> search_indices;
529-
for (HighsInt i = 0; i < mip_search_concurrency; i++) {
502+
for (HighsInt i = 0; i < mipdata_->workers.size(); i++) {
530503
if (mipdata_->workers[i].search_ptr_->hasNode()) {
531504
search_indices.emplace_back(i);
532505
}
@@ -573,7 +546,7 @@ void HighsMipSolver::run() {
573546
analysis_.mipTimerStart(kMipClockEvaluateNode1);
574547
setParallelLock(true);
575548
for (HighsInt i = 0; i != search_indices.size(); i++) {
576-
if (mipdata_->parallelLockActive()) {
549+
if (mipdata_->parallelLockActive() && search_indices.size() > 1) {
577550
tg.spawn([&, i]() {
578551
search_results[i] =
579552
mipdata_->workers[search_indices[i]].search_ptr_->evaluateNode();
@@ -652,7 +625,7 @@ void HighsMipSolver::run() {
652625

653626
if (!thread_safe) {
654627
assert(index == 0);
655-
resetMasterWorkerDomain();
628+
resetGlobalDomain();
656629
}
657630

658631
analysis_.mipTimerStop(kMipClockNodePrunedLoop);
@@ -674,7 +647,7 @@ void HighsMipSolver::run() {
674647
if (!mipdata_->workers[search_indices[i]]
675648
.search_ptr_->currentNodePruned())
676649
continue;
677-
if (mipdata_->parallelLockActive()) {
650+
if (mipdata_->parallelLockActive() && search_indices.size() > 1) {
678651
tg.spawn([&, i]() {
679652
doHandlePrunedNodes(search_indices[i], mipdata_->parallelLockActive(),
680653
flush[i], infeasible[i]);
@@ -883,14 +856,14 @@ void HighsMipSolver::run() {
883856
};
884857

885858
auto diveAllSearches = [&]() -> bool {
886-
std::vector<double> dive_times(mip_search_concurrency,
859+
std::vector<double> dive_times(mipdata_->workers.size(),
887860
-analysis_.mipTimerRead(kMipClockTheDive));
888861
analysis_.mipTimerStart(kMipClockTheDive);
889862
std::vector<HighsSearch::NodeResult> dive_results(
890-
mip_search_concurrency, HighsSearch::NodeResult::kBranched);
863+
mipdata_->workers.size(), HighsSearch::NodeResult::kBranched);
891864
setParallelLock(true);
892-
if (mip_search_concurrency > 1) {
893-
for (int i = 0; i < mip_search_concurrency; i++) {
865+
if (mipdata_->workers.size() > 1) {
866+
for (int i = 0; i < mipdata_->workers.size(); i++) {
894867
tg.spawn([&, i]() {
895868
if (!mipdata_->workers[i].search_ptr_->hasNode() ||
896869
mipdata_->workers[i].search_ptr_->currentNodePruned()) {
@@ -911,7 +884,7 @@ void HighsMipSolver::run() {
911884
analysis_.mipTimerStop(kMipClockTheDive);
912885
setParallelLock(false);
913886
bool suboptimal = false;
914-
for (int i = 0; i < mip_search_concurrency; i++) {
887+
for (int i = 0; i < mipdata_->workers.size(); i++) {
915888
if (dive_times[i] != -1) {
916889
analysis_.dive_time.push_back(dive_times[i]);
917890
if (dive_results[i] == HighsSearch::NodeResult::kSubOptimal) {
@@ -946,8 +919,8 @@ void HighsMipSolver::run() {
946919
HighsInt((3 * mipdata_->firstrootlpiters) / 2)});
947920

948921
mipdata_->lp.setIterationLimit(iterlimit);
949-
for (int i = 0; i < mip_search_concurrency; i++) {
950-
mipdata_->lps[i].setIterationLimit(iterlimit);
922+
for (HighsLpRelaxation& lp : mipdata_->lps) {
923+
lp.setIterationLimit(iterlimit);
951924
}
952925

953926
// perform the dive and put the open nodes to the queue
@@ -1001,8 +974,8 @@ void HighsMipSolver::run() {
1001974
}
1002975
analysis_.mipTimerStop(kMipClockPerformAging2);
1003976

1004-
for (int i = 0; i < mip_search_concurrency; i++) {
1005-
mipdata_->workers[i].search_ptr_->flushStatistics();
977+
for (HighsMipWorker& worker : mipdata_->workers) {
978+
worker.search_ptr_->flushStatistics();
1006979
}
1007980
mipdata_->printDisplayLine();
1008981
if (mipdata_->hasMultipleWorkers()) break;
@@ -1018,8 +991,8 @@ void HighsMipSolver::run() {
1018991
}
1019992
analysis_.mipTimerStop(kMipClockOpenNodesToQueue0);
1020993

1021-
for (int i = 0; i < mip_search_concurrency; i++) {
1022-
mipdata_->workers[i].search_ptr_->flushStatistics();
994+
for (HighsMipWorker& worker : mipdata_->workers) {
995+
worker.search_ptr_->flushStatistics();
1023996
}
1024997

1025998
syncSolutions();
@@ -1074,7 +1047,7 @@ void HighsMipSolver::run() {
10741047
}
10751048

10761049
// set local global domains of all workers to copy changes of global
1077-
resetWorkerDomains();
1050+
if (mipdata_->hasMultipleWorkers()) resetWorkerDomains();
10781051

10791052
double prev_lower_bound = mipdata_->lower_bound;
10801053

@@ -1089,7 +1062,7 @@ void HighsMipSolver::run() {
10891062
if (mipdata_->nodequeue.empty()) break;
10901063

10911064
// flush all changes made to the global domain
1092-
resetMasterWorkerDomain();
1065+
resetGlobalDomain();
10931066

10941067
if (!submip && mipdata_->num_nodes >= nextCheck) {
10951068
auto nTreeRestarts = mipdata_->numRestarts - mipdata_->numRestartsRoot;
@@ -1180,6 +1153,7 @@ void HighsMipSolver::run() {
11801153
// printf("popping node from nodequeue (length = %" HIGHSINT_FORMAT ")\n",
11811154
// (HighsInt)nodequeue.size());
11821155
std::vector<HighsInt> search_indices = getSearchIndicesWithNoNodes();
1156+
search_indices.resize(1);
11831157

11841158
installNodes(search_indices, limit_reached);
11851159
if (limit_reached) break;

highs/mip/HighsMipSolverData.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,6 +1393,8 @@ void HighsMipSolverData::performRestart() {
13931393
if (mipsolver.options_mip_->mip_search_concurrency > 1) {
13941394
mipsolver.mipdata_->workers[0].cutpool_ = &cutpool;
13951395
mipsolver.mipdata_->workers[0].conflictpool_ = &conflictPool;
1396+
mipsolver.mipdata_->workers[0].globaldom_ = &domain;
1397+
// mipsolver.mipdata_->workers[0].lprelaxation_ = &lp;
13961398
}
13971399

13981400
// remove the pointer into the stack-space of this function

0 commit comments

Comments
 (0)