@@ -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 ;
0 commit comments