diff --git a/check/TestBasisSolves.cpp b/check/TestBasisSolves.cpp index d60c49f2204..b74aeacdb47 100644 --- a/check/TestBasisSolves.cpp +++ b/check/TestBasisSolves.cpp @@ -449,7 +449,7 @@ TEST_CASE("Basis-solves", "[highs_basis_solves]") { // filename = std::string(HIGHS_DIR) + "/check/instances/25fv47.mps"; Highs highs; - if (!dev_run) highs.setOptionValue("output_flag", false); + highs.setOptionValue("output_flag", dev_run); vector basic_variables; vector rhs, solution_row, solution_col; diff --git a/check/TestCheckSolution.cpp b/check/TestCheckSolution.cpp index 1e2f667e113..a03d41240ac 100644 --- a/check/TestCheckSolution.cpp +++ b/check/TestCheckSolution.cpp @@ -87,6 +87,7 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") { HighsSolution optimal_solution = highs.getSolution(); HighsInt scratch_num_nodes = info.mip_node_count; + HighsInt scratch_num_simplex = info.simplex_iteration_count; if (dev_run) printf("Num nodes = %d\n", int(scratch_num_nodes)); std::string solution_file = test_name + model + ".sol"; @@ -113,7 +114,10 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") { highs.run(); if (dev_run) printf("Num nodes = %d\n", int(info.mip_node_count)); - REQUIRE(info.mip_node_count != scratch_num_nodes); + const bool different_search = + info.mip_node_count != scratch_num_nodes || + info.simplex_iteration_count != scratch_num_simplex; + REQUIRE(different_search); highs.clear(); } @@ -132,7 +136,10 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") { highs.run(); if (dev_run) printf("Num nodes = %d\n", int(info.mip_node_count)); - REQUIRE(info.mip_node_count != scratch_num_nodes); + const bool different_search = + info.mip_node_count != scratch_num_nodes || + info.simplex_iteration_count != scratch_num_simplex; + REQUIRE(different_search); highs.clear(); } @@ -162,7 +169,10 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") { highs.run(); if (dev_run) printf("Num nodes = %d\n", int(info.mip_node_count)); - REQUIRE(info.mip_node_count != scratch_num_nodes); + const bool different_search = + info.mip_node_count != scratch_num_nodes || + info.simplex_iteration_count != scratch_num_simplex; + REQUIRE(different_search); highs.clear(); } @@ -185,7 +195,10 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") { highs.run(); if (dev_run) printf("Num nodes = %d\n", int(info.mip_node_count)); - REQUIRE(info.mip_node_count != scratch_num_nodes); + const bool different_search = + info.mip_node_count != scratch_num_nodes || + info.simplex_iteration_count != scratch_num_simplex; + REQUIRE(different_search); highs.clear(); } @@ -232,7 +245,10 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") { return_status = highs.setSolution(starting_solution); REQUIRE(return_status == HighsStatus::kOk); highs.run(); - REQUIRE(info.mip_node_count != scratch_num_nodes); + const bool different_search = + info.mip_node_count != scratch_num_nodes || + info.simplex_iteration_count != scratch_num_simplex; + REQUIRE(different_search); highs.clear(); } @@ -283,7 +299,10 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") { return_status = highs.setSolution(num_entries, index.data(), value.data()); REQUIRE(return_status == HighsStatus::kOk); highs.run(); - REQUIRE(info.mip_node_count != scratch_num_nodes); + const bool different_search = + info.mip_node_count != scratch_num_nodes || + info.simplex_iteration_count != scratch_num_simplex; + REQUIRE(different_search); highs.clear(); } assert(other_tests); diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index f5d358f4415..5a45a903588 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -1162,6 +1162,7 @@ TEST_CASE("mip-lp-solver", "[highs_test_mip_solver]") { #endif } +/* TEST_CASE("mip-sub-solver-time", "[highs_test_mip_solver]") { const std::string model = "flugpl"; //"rgn"; // std::string model_file = @@ -1174,6 +1175,7 @@ TEST_CASE("mip-sub-solver-time", "[highs_test_mip_solver]") { REQUIRE(h.run() == HighsStatus::kOk); REQUIRE(h.getModelStatus() == HighsModelStatus::kOptimal); } +*/ TEST_CASE("get-fixed-lp", "[highs_test_mip_solver]") { std::string model = "avgas"; @@ -1405,6 +1407,28 @@ TEST_CASE("issue-2173", "[highs_test_mip_solver]") { solve(highs, kHighsOnString, require_model_status, optimal_objective); } +TEST_CASE("parallel-mip-determinism", "[highs_test_mip_solver]") { + std::string filename = std::string(HIGHS_DIR) + "/check/instances/bell5.mps"; + HighsInt num_runs = 6; + std::vector lp_iters(num_runs); + for (HighsInt i = 0; i < num_runs; i++) { + Highs highs; + highs.setOptionValue("output_flag", dev_run); + highs.setOptionValue("mip_rel_gap", 0); + highs.setOptionValue("threads", 2); + highs.setOptionValue("mip_search_concurrency", 2); + if (i % 2 == 0) highs.setOptionValue("mip_search_simulate_concurrency", 1); + highs.readModel(filename); + const HighsModelStatus require_model_status = HighsModelStatus::kOptimal; + const double optimal_objective = 8966406.491519; + solve(highs, kHighsOffString, require_model_status, optimal_objective); + lp_iters[i] = highs.getInfo().simplex_iteration_count; + if (i > 0) { + REQUIRE(lp_iters[i] == lp_iters[0]); + } + } +} + TEST_CASE("issue-2957", "[highs_test_mip_solver]") { HighsLp lp; lp.num_col_ = 2; diff --git a/check/TestSemiVariables.cpp b/check/TestSemiVariables.cpp index 5d2db970f5f..89cf9679fc2 100644 --- a/check/TestSemiVariables.cpp +++ b/check/TestSemiVariables.cpp @@ -26,6 +26,7 @@ TEST_CASE("semi-variable-model", "[highs_test_semi_variables]") { const double semi_col_lower = lp.col_lower_[semi_col]; const double semi_col_upper = lp.col_upper_[semi_col]; lp.col_cost_[semi_col] = semi_col_cost; + lp.model_name_ = "semi-variable-model"; optimal_objective_function_value = 6.83333; // Legal to have infinte upper bounds on semi-variables lp.col_upper_[semi_col] = inf; diff --git a/cmake/sources.cmake b/cmake/sources.cmake index a1683a8315a..89302f0e45f 100644 --- a/cmake/sources.cmake +++ b/cmake/sources.cmake @@ -380,9 +380,9 @@ set(highs_sources mip/HighsImplications.cpp mip/HighsLpAggregator.cpp mip/HighsLpRelaxation.cpp - mip/HighsMipAnalysis.cpp mip/HighsMipSolver.cpp mip/HighsMipSolverData.cpp + mip/HighsMipWorker.cpp mip/HighsModkSeparator.cpp mip/HighsNodeQueue.cpp mip/HighsObjectiveFunction.cpp @@ -510,9 +510,9 @@ set(highs_headers mip/HighsImplications.h mip/HighsLpAggregator.h mip/HighsLpRelaxation.h - mip/HighsMipAnalysis.h mip/HighsMipSolver.h mip/HighsMipSolverData.h + mip/HighsMipWorker.h mip/HighsModkSeparator.h mip/HighsNodeQueue.h mip/HighsObjectiveFunction.h diff --git a/highs/Highs.h b/highs/Highs.h index f8b046a99cc..8962eed7b2c 100644 --- a/highs/Highs.h +++ b/highs/Highs.h @@ -1235,24 +1235,9 @@ class Highs { HighsStatus setBasis(); /** - * @brief Return a const reference to the internal sub-solver call and time - * instance - */ - const HighsSubSolverCallTime& getSubSolverCallTime() const { - return sub_solver_call_time_; - } - - /** - * @brief Report internal sub-solver call and time instance - */ - void reportSubSolverCallTime() const; - - /** - * @brief Initialise the internal sub-solver call and time instance + * @brief Report profiling */ - void initialiseSubSolverCallTime() { - this->sub_solver_call_time_.initialise(); - } + void reportProfiling() const; /** * @brief Run IPX crossover from a given HighsSolution instance and, @@ -1282,6 +1267,14 @@ class Highs { std::string basisValidityToString(const HighsInt basis_validity) const; std::string presolveRuleTypeToString(const HighsInt presolve_rule) const; + /** + * @brief Ensures that the global scheduler is initialized, + * returning HighsStatus::kError if it has already been initialized, + * but the threads option is nonzero and not equal to + * this->max_threads_. + */ + HighsStatus initializeMultiThreading(); + /** * @brief Releases all resources held by the global scheduler instance. It is * not thread-safe to call this function while calling run() or presolve() on @@ -1297,6 +1290,29 @@ class Highs { */ static void resetGlobalScheduler(bool blocking = false); + /** + * @brief If profiling is not nullptr, sets up profiling and copies + * its pointer to Highs + */ + void initializeProfiling(HighsProfiling* profiling); + void initializeSingleThreadedProfiling(HighsProfiling* profiling); + + /** + * @brief Clears and then initializes profiling + */ + void resetProfiling(); + + /** + * @brief If Highs::profiling_ is not nullptr, clears profiling and + * sets Highs::profiling_ to nullptr + */ + void clearProfiling(); + + /** + * @brief Checks that pointer is not nullptr, and copies it to Highs + */ + void setProfiling(HighsProfiling* profiling); + // Start of advanced methods: only for internal use! // Nested methods below Highs::run() @@ -1563,9 +1579,9 @@ class Highs { HighsPresolveLog presolve_log_; - HighsSubSolverCallTime sub_solver_call_time_; + HighsProfiling* profiling_ = nullptr; - HighsInt max_threads = 0; + HighsInt max_threads_ = 0; // This is strictly for debugging. It's used to check whether // returnFromOptimizeModel() was called after the previous call to // Highs::optimizeModel() and, assuming that this is always done, it checks diff --git a/highs/lp_data/HConst.h b/highs/lp_data/HConst.h index ed5372ae54f..38751788fc4 100644 --- a/highs/lp_data/HConst.h +++ b/highs/lp_data/HConst.h @@ -33,6 +33,7 @@ const std::string kHighsChooseString = "choose"; const std::string kHighsOnString = "on"; const HighsInt kHighsMaxStringLength = 512; const HighsInt kSimplexConcurrencyLimit = 8; +const HighsInt kMipSearchConcurrencyLimit = 8; const double kRunningAverageMultiplier = 0.05; const double kExcessivelySmallObjectiveCoefficient = 1e-4; @@ -302,8 +303,22 @@ enum IisStatus : int { kIisStatusMax = kIisStatusInConflict }; +enum MipChooseSubMipRecord : int { + kMipRecord = -1, + kChooseRecord, + kSubMipRecord +}; + +enum PresolveSolvePostsolveIndex : int { + kPresolveTime = 0, + kSolveTime, + kPostsolveTime, + kToPresolveSolvePostsolve +}; + enum SubSolverIndex : int { - kSubSolverMip = 0, + kFromSubSolver = kToPresolveSolvePostsolve, + kSubSolverMip = kFromSubSolver, kSubSolverDuSimplexBasis, kSubSolverDuSimplexNoBasis, kSubSolverPrSimplexBasis, @@ -315,7 +330,8 @@ enum SubSolverIndex : int { kSubSolverPdlp, kSubSolverQpAsm, kSubSolverSubMip, - kSubSolverCount + kLastSubSolver = kSubSolverSubMip, + kToSubSolver = kLastSubSolver + 1 }; // Minimum and default KKT tolerance diff --git a/highs/lp_data/HStruct.h b/highs/lp_data/HStruct.h index 14fc62e1c00..7c1de7f9ceb 100644 --- a/highs/lp_data/HStruct.h +++ b/highs/lp_data/HStruct.h @@ -15,6 +15,7 @@ #include #include "lp_data/HConst.h" +#include "util/HighsTimer.h" struct HighsSolution { bool value_valid = false; @@ -165,13 +166,45 @@ struct HighsLinearObjective { void clear(); }; -struct HighsSubSolverCallTime { - std::vector name; +struct HighsProfilingRecord { std::vector num_call; std::vector run_time; - void initialise(); - void add(const HighsSubSolverCallTime& sub_solver_call_time, - const bool analytic_centre = false); + std::vector start_time; +}; + +struct HighsProfiling { + HighsTimer* timer = nullptr; + bool multi_threaded = true; + std::string model_name_ = ""; + bool sub_solver_ = false; + bool mip_ = false; + HighsInt num_profiling_clock_ = -1; + std::vector name; + // These vectors are over threads + std::vector submip; + std::vector record; + std::vector submip_record; + bool initialized = false; + + void initialize(HighsTimer& timer_, const bool subsolver_profiling, + const bool mip_profiling = false); + void clear(); + HighsInt numThread(); + HighsInt myThread(); + void setSubMip(const bool submip); + bool isSubMip(); + HighsProfilingRecord* getHighsProfilingRecord( + const HighsInt record_type = kChooseRecord); + void start(const HighsInt profiling_clock, const bool restart = false); + void stop(const HighsInt profiling_clock); + double read(const HighsInt profiling_clock, + const HighsInt record_type = kChooseRecord); + bool running(const HighsInt profiling_clock, + const HighsInt record_type = kChooseRecord); + HighsInt numCall(const HighsInt profiling_clock, + const HighsInt record_type = kChooseRecord); + void solveCall(const std::string& model, const bool submip); + // HighsInt getSepaClockIndex(const std::string& name); }; struct HighsSimplexStats { diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index e5ff245b658..1ad864196aa 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -851,7 +851,6 @@ HighsStatus Highs::presolve() { return HighsStatus::kError; } HighsStatus return_status = HighsStatus::kOk; - this->reportModelStats(); clearPresolve(); if (model_.isEmpty()) { @@ -860,17 +859,12 @@ HighsStatus Highs::presolve() { const bool force_presolve = true; // make sure global scheduler is initialized before calling presolve, since // MIP presolve may use parallelism - highs::parallel::initialize_scheduler(options_.threads); - max_threads = highs::parallel::num_threads(); - if (options_.threads != 0 && max_threads != options_.threads) { - highsLogUser( - log_options, HighsLogType::kError, - "Option 'threads' is set to %d but global scheduler has already been " - "initialized to use %d threads. The previous scheduler instance can " - "be destroyed by calling Highs::resetGlobalScheduler().\n", - (int)options_.threads, max_threads); - return HighsStatus::kError; - } + // + return_status = this->initializeMultiThreading(); + if (return_status != HighsStatus::kOk) return return_status; + HighsProfiling profiling; + const bool already_profiling = this->profiling_; + if (!already_profiling) this->initializeProfiling(&profiling); // If problem is a MIP and solve_relaxation is true, it's natural // to force LP presolve. It means that someone wanting the // presolved relaxation of a MIP just has to set solve_relaxation @@ -878,6 +872,7 @@ HighsStatus Highs::presolve() { // #2234) const bool force_lp_presolve = options_.solve_relaxation; model_presolve_status_ = runPresolve(force_lp_presolve, force_presolve); + if (!already_profiling) this->clearProfiling(); } bool using_reduced_lp = false; @@ -999,19 +994,26 @@ HighsStatus Highs::optimizeHighs() { HighsStatus Highs::optimizeLp() { // Solve what's in the HighsLp instance Highs::model_.lp_ - assert(!model_.isQp()); - assert(!model_.lp_.hasSemiVariables()); + assert(!this->model_.isQp()); + assert(!this->model_.lp_.hasSemiVariables()); assert(!this->multi_linear_objective_.size()); - return optimizeModel(); + return this->calledOptimizeModel(); } HighsStatus Highs::optimizeModel() { // Level 2a of Highs::run() // - if (!options_.use_warm_start) this->clearSolver(); - this->sub_solver_call_time_.initialise(); - HighsStatus status = this->calledOptimizeModel(); - if (this->options_.log_dev_level > 0) this->reportSubSolverCallTime(); + HighsStatus status = this->initializeMultiThreading(); + if (status != HighsStatus::kOk) return status; + + const bool already_profiling = this->profiling_; + HighsProfiling profiling; + if (!already_profiling) this->initializeProfiling(&profiling); + status = this->calledOptimizeModel(); + if (!already_profiling) { + this->reportProfiling(); + this->clearProfiling(); + } return status; } @@ -1051,29 +1053,10 @@ HighsStatus Highs::calledOptimizeModel() { } } + if (!options_.use_warm_start) this->clearSolver(); if (ekk_instance_.status_.has_nla) assert(ekk_instance_.lpFactorRowCompatible(model_.lp_.num_row_)); - highs::parallel::initialize_scheduler(options_.threads); - - max_threads = highs::parallel::num_threads(); - if (options_.threads != 0 && max_threads != options_.threads) { - highsLogUser( - options_.log_options, HighsLogType::kError, - "Option 'threads' is set to %d but global scheduler has already been " - "initialized to use %d threads. The previous scheduler instance can " - "be destroyed by calling Highs::resetGlobalScheduler().\n", - (int)options_.threads, max_threads); - return HighsStatus::kError; - } - assert(max_threads > 0); - if (max_threads <= 0) - highsLogDev(options_.log_options, HighsLogType::kWarning, - "WARNING: max_threads() returns %" HIGHSINT_FORMAT "\n", - max_threads); - highsLogDev(options_.log_options, HighsLogType::kDetailed, - "Running with %" HIGHSINT_FORMAT " thread(s)\n", max_threads); - // returnFromOptimizeModel() is a common exit method to ensure // consistency of values set by optimizeModel() and many other // things. It's important to be able to check that it's been called, @@ -1249,10 +1232,7 @@ HighsStatus Highs::calledOptimizeModel() { // Solve model as a MIP if (!solverValidForMip(options_.solver)) warnSolverInvalid(options_, "MIP"); - sub_solver_call_time_.num_call[kSubSolverMip]++; - sub_solver_call_time_.run_time[kSubSolverMip] = -timer_.read(); call_status = callSolveMip(); - sub_solver_call_time_.run_time[kSubSolverMip] += timer_.read(); return_status = interpretCallStatus(options_.log_options, call_status, return_status, "callSolveMip"); return returnFromOptimizeModel(return_status, undo_mods); @@ -2044,7 +2024,11 @@ HighsStatus Highs::getPrimalRay(bool& has_primal_ray, } HighsStatus Highs::getRanging(HighsRanging& ranging) { + HighsProfiling profiling; + const bool already_profiling = this->profiling_; + if (!already_profiling) this->initializeSingleThreadedProfiling(&profiling); HighsStatus return_status = getRangingInterface(); + if (!already_profiling) this->clearProfiling(); ranging = this->ranging_; return return_status; } @@ -2112,7 +2096,12 @@ HighsStatus Highs::getBasicVariables(HighsInt* basic_variables) { "getBasicVariables: basic_variables is NULL\n"); return HighsStatus::kError; } - return getBasicVariablesInterface(basic_variables); + HighsProfiling profiling; + const bool already_profiling = this->profiling_; + if (!already_profiling) this->initializeSingleThreadedProfiling(&profiling); + HighsStatus status = getBasicVariablesInterface(basic_variables); + if (!already_profiling) this->clearProfiling(); + return status; } HighsStatus Highs::getBasisInverseRowSparse(const HighsInt row, @@ -2603,10 +2592,19 @@ HighsStatus Highs::setBasis(const HighsBasis& basis, } HighsBasis modifiable_basis = basis; modifiable_basis.was_alien = true; - HighsLpSolverObject solver_object( - model_.lp_, modifiable_basis, solution_, info_, ekk_instance_, - callback_, options_, timer_, sub_solver_call_time_); + // May not have profiling set up - if Highs::setBasis is called + // before Highs::run, so have to do it here, and has to be + // single-threaded + HighsProfiling profiling; + const bool already_profiling = this->profiling_; + if (!already_profiling) + this->initializeSingleThreadedProfiling(&profiling); + HighsLpSolverObject solver_object(model_.lp_, modifiable_basis, solution_, + info_, ekk_instance_, callback_, + options_, timer_); + solver_object.setProfiling(this->profiling_); HighsStatus return_status = formSimplexLpBasisAndFactor(solver_object); + if (!already_profiling) this->clearProfiling(); if (return_status != HighsStatus::kOk) return HighsStatus::kError; // Update the HiGHS basis basis_ = std::move(modifiable_basis); @@ -3455,7 +3453,11 @@ HighsStatus Highs::postsolve(const HighsSolution& solution, presolveStatusToString(model_presolve_status_).c_str()); return HighsStatus::kWarning; } + HighsProfiling profiling; + const bool already_profiling = this->profiling_; + if (!already_profiling) this->initializeSingleThreadedProfiling(&profiling); HighsStatus return_status = callRunPostsolve(solution, basis); + if (!already_profiling) this->clearProfiling(); return returnFromHighs(return_status); } @@ -3629,6 +3631,7 @@ HighsPresolveStatus Highs::runPresolve(const bool force_lp_presolve, // Presolved model is extracted now since it's part of solver, // which is lost on return HighsMipSolver solver(callback_, options_, original_lp, solution_); + solver.setProfiling(this->profiling_); // Start the MIP solver's timer so that timeout in presolve can be // identified solver.timer_.start(); @@ -3921,19 +3924,14 @@ HighsStatus Highs::completeSolutionFromDiscreteAssignment() { options_.mip_max_nodes = options_.mip_max_start_nodes; // Solve the model basis_.clear(); - HighsSubSolverCallTime sub_solver_call_time = this->sub_solver_call_time_; - double mip_solve_time = -sub_solver_call_time_.run_time[kSubSolverMip]; - return_status = this->optimizeModel(); - if (model_.lp_.isMip()) { - // If a MIP was solved, it counts as a sub-MIP, but the MIP and - // LP call-time data will be recorded as if it were a MIP so, - // extract the MIP solve time, revert - // this->sub_solver_call_time_ and update the sub-MIP record - mip_solve_time += sub_solver_call_time_.run_time[kSubSolverMip]; - this->sub_solver_call_time_ = sub_solver_call_time; - this->sub_solver_call_time_.num_call[kSubSolverSubMip]++; - this->sub_solver_call_time_.run_time[kSubSolverSubMip] += mip_solve_time; + if (this->profiling_) { + // Should not already be a sub-MIP, as handling a user-supplied + // solution + assert(!this->profiling_->isSubMip()); } + return_status = this->optimizeModel(); + // Reset any profiling data + if (this->profiling_) this->resetProfiling(); // ... remembering to recover the original value of mip_max_nodes options_.mip_max_nodes = mip_max_nodes; } @@ -3956,8 +3954,8 @@ HighsStatus Highs::callSolveLp(HighsLp& lp, const std::string& message) { HighsStatus return_status = HighsStatus::kOk; HighsLpSolverObject solver_object(lp, basis_, solution_, info_, ekk_instance_, - callback_, options_, timer_, - sub_solver_call_time_); + callback_, options_, timer_); + solver_object.setProfiling(this->profiling_); // Check that the model is column-wise assert(model_.lp_.a_matrix_.isColwise()); @@ -3994,17 +3992,15 @@ HighsStatus Highs::callSolveQp() { HighsExternalApi::isAvailable(); if (use_hipo) { - sub_solver_call_time_.num_call[kSubSolverHipo]++; - sub_solver_call_time_.run_time[kSubSolverHipo] = -timer_.read(); + if (this->profiling_) this->profiling_->start(kSubSolverHipo); return_status = solveHipo(options_, timer_, lp, hessian, basis_, solution_, model_status_, info_, callback_); - sub_solver_call_time_.run_time[kSubSolverHipo] += timer_.read(); + if (this->profiling_) this->profiling_->stop(kSubSolverHipo); if (return_status == HighsStatus::kError) return return_status; } else { // // Run the QP solver - sub_solver_call_time_.num_call[kSubSolverQpAsm]++; - sub_solver_call_time_.run_time[kSubSolverQpAsm] = -timer_.read(); + if (this->profiling_) this->profiling_->start(kSubSolverQpAsm); Instance instance(lp.num_col_, lp.num_row_); @@ -4134,7 +4130,7 @@ HighsStatus Highs::callSolveQp() { QpAsmStatus status = solveqp(instance, settings, stats, model_status_, basis_, solution_, timer_); - sub_solver_call_time_.run_time[kSubSolverQpAsm] += timer_.read(); + if (this->profiling_) this->profiling_->stop(kSubSolverQpAsm); // QP solver can fail, so should return something other than // QpAsmStatus::kOk @@ -4191,14 +4187,16 @@ HighsStatus Highs::callSolveMip() { } HighsLp& lp = has_semi_variables ? use_lp : model_.lp_; HighsMipSolver solver(callback_, options_, lp, solution_); + solver.setProfiling(this->profiling_); + profiling_->start(kSubSolverMip); solver.run(); + profiling_->stop(kSubSolverMip); options_.log_dev_level = log_dev_level; // Set the return_status, model status and, for completeness, scaled // model status HighsStatus return_status = highsStatusFromHighsModelStatus(solver.modelstatus_); model_status_ = solver.modelstatus_; - this->sub_solver_call_time_.add(solver.sub_solver_call_time_); // Extract the solution if (solver.solution_objective_ != kHighsInf) { // There is a primal solution @@ -4436,9 +4434,6 @@ HighsStatus Highs::callRunPostsolve(const HighsSolution& solution, // Scrap the EKK data from solving the presolved LP ekk_instance_.invalidate(); ekk_instance_.lp_name_ = "Postsolve LP"; - // Set up the timing record so that adding the corresponding - // values after callSolveLp gives difference - this->sub_solver_call_time_.initialise(); timer_.start(timer_.solve_clock); call_status = callSolveLp( incumbent_lp, @@ -4940,6 +4935,70 @@ HighsStatus Highs::closeLogFile() { return HighsStatus::kOk; } +HighsStatus Highs::initializeMultiThreading() { + highs::parallel::initialize_scheduler(this->options_.threads); + this->max_threads_ = highs::parallel::num_threads(); + HighsLogOptions& log_options = this->options_.log_options; + if (this->options_.threads != 0 && + this->max_threads_ != this->options_.threads) { + highsLogUser( + log_options, HighsLogType::kError, + "Option 'threads' is set to %d but global scheduler has already been " + "initialized to use %d threads. The previous scheduler instance can " + "be destroyed by calling Highs::resetGlobalScheduler().\n", + int(options_.threads), int(this->max_threads_)); + return HighsStatus::kError; + } + if (this->max_threads_ <= 0) { + highsLogDev(log_options, HighsLogType::kError, "max_threads() returns %d\n", + int(this->max_threads_)); + assert(this->max_threads_ > 0); + return HighsStatus::kError; + } + highsLogDev(log_options, HighsLogType::kDetailed, + "Running with %d thread(s)\n", int(max_threads_)); + + return HighsStatus::kOk; +} + void Highs::resetGlobalScheduler(bool blocking) { HighsTaskExecutor::shutdown(blocking); } + +void Highs::initializeSingleThreadedProfiling(HighsProfiling* profiling) { + if (!profiling) return; + profiling->multi_threaded = false; + this->initializeProfiling(profiling); +} + +void Highs::initializeProfiling(HighsProfiling* profiling) { + if (!profiling) return; + assert(!this->profiling_); + if (this->profiling_) return; + // Only initialize profiling if this->profiling_ is nullptr + const bool sub_solver = this->options_.log_dev_level > 0; // true; // + // Cannot perform MIP profiling without sub-solver profiling + const bool mip = sub_solver && kHighsAnalysisLevelMipTime & + this->options_.highs_analysis_level; + profiling->initialize(this->timer_, sub_solver, mip); + profiling->model_name_ = this->model_.lp_.model_name_; + this->setProfiling(profiling); +} + +void Highs::resetProfiling() { + if (!this->profiling_) return; + HighsProfiling* profiling = this->profiling_; + this->clearProfiling(); + this->initializeProfiling(profiling); +} + +void Highs::clearProfiling() { + if (!this->profiling_) return; + this->profiling_->clear(); + this->profiling_ = nullptr; +} + +void Highs::setProfiling(HighsProfiling* profiling) { + assert(profiling); + this->profiling_ = profiling; +} diff --git a/highs/lp_data/HighsInterface.cpp b/highs/lp_data/HighsInterface.cpp index fcac195de12..c5259ea2ee4 100644 --- a/highs/lp_data/HighsInterface.cpp +++ b/highs/lp_data/HighsInterface.cpp @@ -15,7 +15,9 @@ #include "lp_data/HighsLpUtils.h" #include "lp_data/HighsModelUtils.h" #include "mip/HighsMipSolver.h" // For getGapString +#include "mip/MipTimer.h" #include "model/HighsHessianUtils.h" +#include "parallel/HighsParallel.h" #include "simplex/HSimplex.h" #include "util/HighsMatrixUtils.h" #include "util/HighsSort.h" @@ -1451,7 +1453,8 @@ HighsStatus Highs::getBasicVariablesInterface(HighsInt* basic_variables) { // for the current basis, so return_value is the rank deficiency. HighsLpSolverObject solver_object(lp, basis_, solution_, info_, ekk_instance_, callback_, options_, - timer_, sub_solver_call_time_); + timer_); + solver_object.setProfiling(this->profiling_); const bool only_from_known_basis = true; return_status = interpretCallStatus( options_.log_options, @@ -1821,8 +1824,8 @@ HighsStatus Highs::getPrimalRayInterface(bool& has_primal_ray, HighsStatus Highs::getRangingInterface() { HighsLpSolverObject solver_object(model_.lp_, basis_, solution_, info_, - ekk_instance_, callback_, options_, timer_, - sub_solver_call_time_); + ekk_instance_, callback_, options_, timer_); + solver_object.setProfiling(this->profiling_); solver_object.model_status_ = model_status_; return getRangingData(this->ranging_, solver_object); } @@ -4281,75 +4284,381 @@ void HighsLinearObjective::clear() { this->priority = 0; } -void HighsSubSolverCallTime::initialise() { - this->num_call.assign(kSubSolverCount, 0); - this->run_time.assign(kSubSolverCount, 0); - this->name.assign(kSubSolverCount, ""); - this->name[kSubSolverDuSimplexBasis] = "Du simplex (basis)"; - this->name[kSubSolverDuSimplexNoBasis] = "Du simplex (no basis)"; - this->name[kSubSolverPrSimplexBasis] = "Pr simplex (basis)"; - this->name[kSubSolverPrSimplexNoBasis] = "Pr simplex (no basis)"; - this->name[kSubSolverHipo] = "HiPO"; - this->name[kSubSolverIpx] = "IPX"; - this->name[kSubSolverHipoAc] = "HiPO (AC)"; - this->name[kSubSolverIpxAc] = "IPX (AC)"; - this->name[kSubSolverPdlp] = "PDLP"; - this->name[kSubSolverQpAsm] = "QP ASM"; - this->name[kSubSolverMip] = "MIP"; - this->name[kSubSolverSubMip] = "Sub-MIP"; +void HighsProfiling::initialize(HighsTimer& timer_, const bool sub_solver, + const bool mip) { + // NB this->multi_threaded is set externally: + // + // * true in HighsProfiling::clear() + // + // * false in initializeSingleThreadedProfiling + // + // Hence don't call this->clear() here! + this->timer = &timer_; + this->num_profiling_clock_ = kToPresolveSolvePostsolve; + this->name.assign(this->num_profiling_clock_, ""); + this->name[kPresolveTime] = "Presolve"; + this->name[kSolveTime] = "Solve"; + this->name[kPostsolveTime] = "Postsolve"; + // Now add clocks if performing subsolver profiling + this->sub_solver_ = sub_solver; + if (this->sub_solver_) { + this->num_profiling_clock_ = kToSubSolver; + this->name.resize(this->num_profiling_clock_); + this->name[kSubSolverDuSimplexBasis] = "Du simplex (basis)"; + this->name[kSubSolverDuSimplexNoBasis] = "Du simplex (no basis)"; + this->name[kSubSolverPrSimplexBasis] = "Pr simplex (basis)"; + this->name[kSubSolverPrSimplexNoBasis] = "Pr simplex (no basis)"; + this->name[kSubSolverHipo] = "HiPO"; + this->name[kSubSolverIpx] = "IPX"; + this->name[kSubSolverHipoAc] = "HiPO (AC)"; + this->name[kSubSolverIpxAc] = "IPX (AC)"; + this->name[kSubSolverPdlp] = "PDLP"; + this->name[kSubSolverQpAsm] = "QP ASM"; + this->name[kSubSolverMip] = "MIP"; + this->name[kSubSolverSubMip] = "Sub-MIP"; + } + // Now add clocks if also performing MIP profiling + // Cannot perform MIP profiling without subsolver profiling + if (mip) assert(sub_solver); + this->mip_ = sub_solver && mip; + if (this->mip_) { + this->num_profiling_clock_ = kToMipClock; + this->name.resize(this->num_profiling_clock_); + initialiseMipProfilingNames(this->name); + } + HighsProfilingRecord thread_record; + thread_record.num_call.assign(this->num_profiling_clock_, 0); + thread_record.run_time.assign(this->num_profiling_clock_, 0); + thread_record.start_time.assign(this->num_profiling_clock_, 1); + HighsInt num_thread = this->numThread(); + assert(num_thread > 0); + this->submip.assign(num_thread, false); + this->record.assign(num_thread, thread_record); + this->submip_record.assign(num_thread, thread_record); + this->initialized = true; +} + +void HighsProfiling::clear() { + this->timer = nullptr; + this->multi_threaded = true; + this->model_name_ = ""; + this->sub_solver_ = false; + this->mip_ = false; + this->num_profiling_clock_ = -1; + this->name.clear(); + this->submip.clear(); + this->record.clear(); + this->submip_record.clear(); + this->initialized = false; +} + +HighsInt HighsProfiling::numThread() { + return this->multi_threaded ? highs::parallel::num_threads() : 1; +} + +HighsInt HighsProfiling::myThread() { + return this->multi_threaded ? highs::parallel::thread_num() : 0; +} + +void HighsProfiling::setSubMip(const bool submip) { + this->submip[this->myThread()] = submip; +} + +bool HighsProfiling::isSubMip() { return this->submip[this->myThread()]; } + +// Gets the MIP or sub-MIP record according to record_type which, by +// default is kChooseRecord so this->submip is used +HighsProfilingRecord* HighsProfiling::getHighsProfilingRecord( + const HighsInt record_type) { + HighsInt thread = this->myThread(); + if (record_type == kSubMipRecord || + (record_type == kChooseRecord && this->submip[thread])) + return &this->submip_record[thread]; + return &this->record[thread]; +} + +void HighsProfiling::start(const HighsInt profiling_clock, const bool restart) { + assert(profiling_clock >= 0); + if (profiling_clock >= this->num_profiling_clock_) return; + // For a sub-MIP, don't start the clock for anything but a + // sub-solver + if (this->isSubMip() && profiling_clock >= kToSubSolver) return; + // Start timing sub-solver profiling_clock + HighsInt thread = this->myThread(); + HighsProfilingRecord* thread_record = this->getHighsProfilingRecord(); + double time_start = timer->read(); + + if (profiling_clock == kMipClockSubMipSolve) { + printf("HighsProfiling::start SubMipSolve on thread %2d with submip = %s\n", + int(thread), this->submip[thread] ? "T" : "F"); + } + const bool clock_running = + std::signbit(thread_record->start_time[profiling_clock]); + if (clock_running && profiling_clock != kSubSolverHipoAc && + profiling_clock != kSubSolverIpxAc) { + // Check for starting a clock that's currently running - except + // for analytic centre calculation that may by stopped by + // terminating the task + printf( + "HighsProfiling: clock running for thread %d when starting clock %d " + "(%s) and subMip = %s\n", + int(thread), int(profiling_clock), this->name[profiling_clock].c_str(), + this->submip[thread] ? "T" : "F"); + assert(!clock_running); + } + thread_record->start_time[profiling_clock] = -time_start; + if (restart) thread_record->num_call[profiling_clock]--; +} + +void HighsProfiling::stop(const HighsInt profiling_clock) { + assert(profiling_clock >= 0); + if (profiling_clock >= this->num_profiling_clock_) return; + // For a sub-MIP, don't start the clock for anything but a + // sub-solver + if (this->isSubMip() && profiling_clock >= kToSubSolver) return; + HighsInt thread = this->myThread(); + HighsProfilingRecord* thread_record = this->getHighsProfilingRecord(); + double time_stop = timer->read(); + double time_start = thread_record->start_time[profiling_clock]; + const bool clock_running = std::signbit(time_start); + if (!clock_running) { + // Check for stopping a clock that's currently stopped + printf( + "HighsProfiling: clock not running for thread %d when stopping clock " + "%d (%s) \n", + int(thread), int(profiling_clock), this->name[profiling_clock].c_str()); + assert(clock_running); + } else { + thread_record->num_call[profiling_clock]++; + thread_record->run_time[profiling_clock] += (time_stop + time_start); + } + thread_record->start_time[profiling_clock] = time_stop; +} + +double HighsProfiling::read(const HighsInt profiling_clock, + const HighsInt record_type) { + assert(profiling_clock >= 0); + if (profiling_clock >= this->num_profiling_clock_) return -kHighsInf; + HighsProfilingRecord* thread_record = + this->getHighsProfilingRecord(record_type); + // If the clock is running, work out current running time + const double current_running_time = + this->running(profiling_clock, record_type) + ? thread_record->start_time[profiling_clock] + timer->read() + : 0; + return thread_record->run_time[profiling_clock] + current_running_time; +} + +HighsInt HighsProfiling::numCall(const HighsInt profiling_clock, + const HighsInt record_type) { + assert(profiling_clock >= 0); + if (profiling_clock >= this->num_profiling_clock_) return -kHighsIInf; + HighsProfilingRecord* thread_record = + this->getHighsProfilingRecord(record_type); + const HighsInt this_call_counts = + this->running(profiling_clock, record_type) ? 1 : 0; + return thread_record->num_call[profiling_clock] + this_call_counts; +} + +bool HighsProfiling::running(const HighsInt profiling_clock, + const HighsInt record_type) { + assert(profiling_clock >= 0); + if (profiling_clock >= this->num_profiling_clock_) return false; + HighsProfilingRecord* thread_record = + this->getHighsProfilingRecord(record_type); + double time_start = thread_record->start_time[profiling_clock]; + const bool clock_running = std::signbit(time_start); + return clock_running; } -void HighsSubSolverCallTime::add( - const HighsSubSolverCallTime& sub_solver_call_time, - const bool analytic_centre) { - for (HighsInt Ix = 0; Ix < kSubSolverCount; Ix++) { - HighsInt ToIx = Ix; - if (Ix == kSubSolverHipo) { - if (analytic_centre) ToIx = kSubSolverHipoAc; - } else if (Ix == kSubSolverIpx) { - if (analytic_centre) ToIx = kSubSolverIpxAc; +void HighsProfiling::solveCall(const std::string& model, const bool submip) { + const bool printing = false; + if (this->num_profiling_clock_ <= kToPresolveSolvePostsolve) return; + const bool local_submip_ok = this->isSubMip() == submip; + HighsInt thread = this->myThread(); + if (!local_submip_ok) { + printf("Solving %3s for %4sMIP on thread %d with isSubMip() = %4sMIP\n", + model.c_str(), submip ? "sub-" : "", int(thread), + isSubMip() ? "sub-" : ""); + } + assert(local_submip_ok); + if (thread != 0 || submip) { + if (model == "MIP") { + if (printing) + printf("Solving MIP for %4sMIP on thread %d\n", submip ? "sub-" : "", + int(thread)); + } else { + if (printing) + printf("Solving %3s for %4sMIP on thread %d\n", model.c_str(), + submip ? "sub-" : "", int(thread)); } - this->num_call[ToIx] += sub_solver_call_time.num_call[Ix]; - this->run_time[ToIx] += sub_solver_call_time.run_time[Ix]; } } -void Highs::reportSubSolverCallTime() const { - double mip_time = this->sub_solver_call_time_.run_time[kSubSolverMip]; +// HighsInt HighsProfiling::getSepaClockIndex(const std::string& name) { +// assert(1==4); return 0;} + +void Highs::reportProfiling() const { + if (!this->profiling_->sub_solver_) return; + HighsInt num_thread = this->profiling_->numThread(); + double mip_time = 0; + double max_sumip_time = 0; + const std::vector& record = this->profiling_->record; + const std::vector& submip_record = + this->profiling_->submip_record; + for (HighsInt thread_num = 0; thread_num < num_thread; thread_num++) { + mip_time = std::max(record[thread_num].run_time[kSubSolverMip], mip_time); + max_sumip_time = + std::max(record[thread_num].run_time[kSubSolverSubMip], max_sumip_time); + } + + std::vector used_thread; + for (HighsInt thread_num = 0; thread_num < num_thread; thread_num++) { + bool used = false; + for (HighsInt Ix = kFromSubSolver; Ix < kToSubSolver; Ix++) + if (record[thread_num].num_call[Ix]) used = true; + for (HighsInt Ix = kFromSubSolver; Ix < kToSubSolver; Ix++) + if (submip_record[thread_num].num_call[Ix]) used = true; + if (!used) continue; + used_thread.push_back(thread_num); + } + const double num_threads_used = used_thread.size(); std::stringstream ss; - ss.str(std::string()); - ss << highsFormatToString( - "\nSub-solver timing\nSolver Calls Time " - "Time/call"); - if (mip_time > 0) ss << " MIP%"; - highsLogUser(options_.log_options, HighsLogType::kInfo, "%s\n", - ss.str().c_str()); - - double sum_mip_sub_solve_time = 0; - for (HighsInt Ix = 0; Ix < kSubSolverCount; Ix++) { - if (this->sub_solver_call_time_.num_call[Ix]) { + std::vector mip_used_sub_solver(kToSubSolver, false); + std::vector submip_used_sub_solver(kToSubSolver, false); + const HighsInt to_k = max_sumip_time > 0 ? 2 : 1; + const std::vector& name = this->profiling_->name; + double sum_sum_mip_sub_solve_time = 0; + for (HighsInt k = 0; k < to_k; k++) { + if (k == 0) { + highsLogUser(options_.log_options, HighsLogType::kInfo, + "\nMIP sub-solver profiling: number of threads used = %d\n", + int(num_threads_used)); + } else { + highsLogUser(options_.log_options, HighsLogType::kInfo, + "\nSub-MIP sub-solver profiling\n"); + } + for (HighsInt thread_ix = 0; thread_ix < HighsInt(num_threads_used); + thread_ix++) { + HighsInt thread_num = used_thread[thread_ix]; + double ideal_time = + k == 0 + ? mip_time + : this->profiling_->record[thread_num].run_time[kSubSolverSubMip]; + if (ideal_time <= 0) continue; + const std::vector& record = + k == 0 ? this->profiling_->record : this->profiling_->submip_record; + std::vector& used_sub_solver = + k == 0 ? mip_used_sub_solver : submip_used_sub_solver; + const std::vector& num_call = record[thread_num].num_call; + const std::vector& run_time = record[thread_num].run_time; ss.str(std::string()); ss << highsFormatToString( - "%-21s %9d %11.4e %11.4e", - this->sub_solver_call_time_.name[Ix].c_str(), - int(this->sub_solver_call_time_.num_call[Ix]), - this->sub_solver_call_time_.run_time[Ix], - this->sub_solver_call_time_.run_time[Ix] / - (1.0 * this->sub_solver_call_time_.num_call[Ix])); - if (mip_time > 0 && Ix != kSubSolverMip) { - if (Ix != kSubSolverHipoAc && Ix != kSubSolverIpxAc) - sum_mip_sub_solve_time += this->sub_solver_call_time_.run_time[Ix]; - ss << highsFormatToString( - " %5.1f", - 1e2 * this->sub_solver_call_time_.run_time[Ix] / mip_time); + "\nThread %d\n" + "Solver Calls Time " + "Time/call", + int(thread_num)); + if (ideal_time > 0) ss << (k == 0 ? " MIP%" : " Sub-MIP%"); + highsLogUser(options_.log_options, HighsLogType::kInfo, "%s\n", + ss.str().c_str()); + double sum_mip_sub_solve_time = 0; + for (HighsInt Ix = kFromSubSolver; Ix < kToSubSolver; Ix++) { + double pct = 0; + if (!num_call[Ix]) continue; + used_sub_solver[Ix] = true; + ss.str(std::string()); + ss << highsFormatToString("%-21s %9d %11.4e %11.4e", name[Ix].c_str(), + int(num_call[Ix]), run_time[Ix], + run_time[Ix] / (1.0 * num_call[Ix])); + if (ideal_time > 0 && Ix != kSubSolverMip) { + sum_mip_sub_solve_time += run_time[Ix]; + ss << highsFormatToString(" %5.1f", + 1e2 * run_time[Ix] / ideal_time); + } + highsLogUser(options_.log_options, HighsLogType::kInfo, "%s\n", + ss.str().c_str()); + } + sum_sum_mip_sub_solve_time += sum_mip_sub_solve_time; + if (ideal_time > 0 && sum_mip_sub_solve_time > 0) + highsLogUser( + options_.log_options, HighsLogType::kInfo, + "TOTAL %11.4e %5.1f\n", + sum_mip_sub_solve_time, 1e2 * sum_mip_sub_solve_time / ideal_time); + } + } + if (mip_time <= 0) return; + if (sum_sum_mip_sub_solve_time <= 0) return; + // Lambda for horizontal rule + auto hrule = [&]() { + ss.str(std::string()); + ss << "====================="; + for (HighsInt thread_ix = 0; thread_ix < HighsInt(num_threads_used); + thread_ix++) + ss << "======"; + highsLogUser(options_.log_options, HighsLogType::kInfo, "%s\n", + ss.str().c_str()); + }; + // Determine the sub-solver percentage breakdown over all threads + // when solving MIPs + highsLogUser(options_.log_options, HighsLogType::kInfo, + "\nPercent (sub-)MIP time by thread\n"); + for (HighsInt k = 0; k < to_k; k++) { + if (k == 0) { + ss.str(std::string()); + ss << highsFormatToString("\nMIP sub-solver "); + } else { + if (max_sumip_time <= 0) continue; + ss.str(std::string()); + ss << highsFormatToString("\nSub-MIP sub-solver "); + } + for (HighsInt thread_ix = 0; thread_ix < HighsInt(num_threads_used); + thread_ix++) { + HighsInt thread_num = used_thread[thread_ix]; + ss << highsFormatToString("%6d", int(thread_num)); + } + highsLogUser(options_.log_options, HighsLogType::kInfo, "%s\n", + ss.str().c_str()); + std::vector& used_sub_solver = + k == 0 ? mip_used_sub_solver : submip_used_sub_solver; + const std::vector& record = + k == 0 ? this->profiling_->record : this->profiling_->submip_record; + std::vector totalPct(num_threads_used, 0); + for (HighsInt Ix = kFromSubSolver + 1; Ix < kToSubSolver; Ix++) { + if (!used_sub_solver[Ix]) continue; + ss.str(std::string()); + ss << highsFormatToString("%-21s", name[Ix].c_str()); + for (HighsInt thread_ix = 0; thread_ix < HighsInt(num_threads_used); + thread_ix++) { + HighsInt thread_num = used_thread[thread_ix]; + double ideal_time = k == 0 ? mip_time + : this->profiling_->record[thread_num] + .run_time[kSubSolverSubMip]; + HighsInt num_call = record[thread_num].num_call[Ix]; + double run_time = record[thread_num].run_time[Ix]; + if (num_call && ideal_time > 0) { + double pct = 1e2 * run_time / ideal_time; + totalPct[thread_ix] += pct; + ss << highsFormatToString(" %5.1f", pct); + } else { + ss << " "; + } } highsLogUser(options_.log_options, HighsLogType::kInfo, "%s\n", ss.str().c_str()); } + hrule(); + ss.str(std::string()); + ss << "Total "; + for (HighsInt thread_ix = 0; thread_ix < HighsInt(num_threads_used); + thread_ix++) { + if (totalPct[thread_ix]) { + ss << highsFormatToString(" %5.1f", totalPct[thread_ix]); + } else { + ss << " "; + } + } + highsLogUser(options_.log_options, HighsLogType::kInfo, "%s\n", + ss.str().c_str()); + hrule(); } - if (mip_time > 0) - highsLogUser(options_.log_options, HighsLogType::kInfo, - "TOTAL (excluding AC) %11.4e %5.1f\n", - sum_mip_sub_solve_time, - 1e2 * sum_mip_sub_solve_time / mip_time); } diff --git a/highs/lp_data/HighsLpSolverObject.h b/highs/lp_data/HighsLpSolverObject.h index b48aaa6280b..8952b66830f 100644 --- a/highs/lp_data/HighsLpSolverObject.h +++ b/highs/lp_data/HighsLpSolverObject.h @@ -20,8 +20,7 @@ class HighsLpSolverObject { HighsLpSolverObject(HighsLp& lp, HighsBasis& basis, HighsSolution& solution, HighsInfo& highs_info, HEkk& ekk_instance, HighsCallback& callback, HighsOptions& options, - HighsTimer& timer, - HighsSubSolverCallTime& sub_solver_call_time) + HighsTimer& timer) : lp_(lp), basis_(basis), solution_(solution), @@ -29,8 +28,7 @@ class HighsLpSolverObject { ekk_instance_(ekk_instance), callback_(callback), options_(options), - timer_(timer), - sub_solver_call_time_(sub_solver_call_time) {} + timer_(timer) {} HighsLp& lp_; HighsBasis& basis_; @@ -40,8 +38,12 @@ class HighsLpSolverObject { HighsCallback& callback_; HighsOptions& options_; HighsTimer& timer_; - HighsSubSolverCallTime& sub_solver_call_time_; + HighsProfiling* profiling_ = nullptr; HighsModelStatus model_status_ = HighsModelStatus::kNotset; + void setProfiling(HighsProfiling* profiling) { + assert(profiling); + profiling_ = profiling; + } }; #endif // LP_DATA_HIGHS_LP_SOLVER_OBJECT_H_ diff --git a/highs/lp_data/HighsOptions.h b/highs/lp_data/HighsOptions.h index 5562deee868..dba5fc56dac 100644 --- a/highs/lp_data/HighsOptions.h +++ b/highs/lp_data/HighsOptions.h @@ -504,6 +504,8 @@ struct HighsOptionsStruct { std::string mip_improving_solution_file; bool mip_root_presolve_only; HighsInt mip_lifting_for_probing; + HighsInt mip_search_concurrency; + bool mip_search_simulate_concurrency; bool mip_allow_cut_separation_at_nodes; // Logging callback identifiers @@ -667,6 +669,8 @@ struct HighsOptionsStruct { mip_improving_solution_file(""), mip_root_presolve_only(false), mip_lifting_for_probing(-1), + mip_search_concurrency(0), + mip_search_simulate_concurrency(false), // clang-format off mip_allow_cut_separation_at_nodes(true) {}; // clang-format on @@ -1284,6 +1288,18 @@ class HighsOptions : public HighsOptionsStruct { kHighsInf); records.push_back(record_double); + record_int = new OptionRecordInt( + "mip_search_concurrency", + "Number of workers to create per thread for concurrent MIP search", + advanced, &mip_search_concurrency, 0, 0, kMipSearchConcurrencyLimit); + records.push_back(record_int); + + record_bool = new OptionRecordBool( + "mip_search_simulate_concurrency", + "Simulate MIP search concurrency on a single thread", advanced, + &mip_search_simulate_concurrency, false); + records.push_back(record_bool); + record_int = new OptionRecordInt( "ipm_iteration_limit", "Iteration limit for IPM solver", advanced, &ipm_iteration_limit, 0, kHighsIInf, kHighsIInf); diff --git a/highs/lp_data/HighsSolve.cpp b/highs/lp_data/HighsSolve.cpp index 050e62f29f6..aaa86f63c11 100644 --- a/highs/lp_data/HighsSolve.cpp +++ b/highs/lp_data/HighsSolve.cpp @@ -21,8 +21,7 @@ HighsStatus solveLp(HighsLpSolverObject& solver_object, const string message) { HighsStatus return_status = HighsStatus::kOk; HighsStatus call_status; HighsOptions& options = solver_object.options_; - HighsSubSolverCallTime& sub_solver_call_time = - solver_object.sub_solver_call_time_; + HighsProfiling* profiling = solver_object.profiling_; // Reset unscaled model status and solution params - except for // iteration counts resetModelStatusAndHighsInfo(solver_object); @@ -72,9 +71,6 @@ HighsStatus solveLp(HighsLpSolverObject& solver_object, const string message) { // Use IPM to solve the LP if (use_hipo) { // Use HIPO to solve the LP - sub_solver_call_time.num_call[kSubSolverHipo]++; - sub_solver_call_time.run_time[kSubSolverHipo] = - -solver_object.timer_.read(); try { call_status = solveLpHipo(solver_object); } catch (const std::exception& exception) { @@ -82,14 +78,9 @@ HighsStatus solveLp(HighsLpSolverObject& solver_object, const string message) { "Exception %s in solveLpHipo\n", exception.what()); call_status = HighsStatus::kError; } - sub_solver_call_time.run_time[kSubSolverHipo] += - solver_object.timer_.read(); return_status = interpretCallStatus(options.log_options, call_status, return_status, "solveLpHipo"); } else if (use_ipx) { - sub_solver_call_time.num_call[kSubSolverIpx]++; - sub_solver_call_time.run_time[kSubSolverIpx] = - -solver_object.timer_.read(); try { call_status = solveLpIpx(solver_object); } catch (const std::exception& exception) { @@ -97,16 +88,12 @@ HighsStatus solveLp(HighsLpSolverObject& solver_object, const string message) { "Exception %s in solveLpIpx\n", exception.what()); call_status = HighsStatus::kError; } - sub_solver_call_time.run_time[kSubSolverIpx] += - solver_object.timer_.read(); return_status = interpretCallStatus(options.log_options, call_status, return_status, "solveLpIpx"); } } else { // Use cuPDLP-C or HiPDLP to solve the LP - sub_solver_call_time.num_call[kSubSolverPdlp]++; - sub_solver_call_time.run_time[kSubSolverPdlp] = - -solver_object.timer_.read(); + profiling->start(kSubSolverPdlp); if (options.solver == kPdlpString) { try { call_status = solveLpCupdlp(solver_object); @@ -124,8 +111,7 @@ HighsStatus solveLp(HighsLpSolverObject& solver_object, const string message) { call_status = HighsStatus::kError; } } - sub_solver_call_time.run_time[kSubSolverPdlp] += - solver_object.timer_.read(); + profiling->stop(kSubSolverPdlp); return_status = interpretCallStatus(options.log_options, call_status, return_status, "solveLp-Pdlp"); } diff --git a/highs/meson.build b/highs/meson.build index 123afaf937d..897cf73f106 100644 --- a/highs/meson.build +++ b/highs/meson.build @@ -268,9 +268,9 @@ _srcs = [ 'mip/HighsImplications.cpp', 'mip/HighsLpAggregator.cpp', 'mip/HighsLpRelaxation.cpp', - 'mip/HighsMipAnalysis.cpp', 'mip/HighsMipSolver.cpp', 'mip/HighsMipSolverData.cpp', + 'mip/HighsMipWorker.cpp', 'mip/HighsModkSeparator.cpp', 'mip/HighsNodeQueue.cpp', 'mip/HighsObjectiveFunction.cpp', diff --git a/highs/mip/HighsCliqueTable.cpp b/highs/mip/HighsCliqueTable.cpp index 7907cf90e83..40a90902dc2 100644 --- a/highs/mip/HighsCliqueTable.cpp +++ b/highs/mip/HighsCliqueTable.cpp @@ -484,7 +484,8 @@ void HighsCliqueTable::queryNeighbourhood( if (numCliques(v) == 0) return; - if (numEntries - sizeTwoCliques.size() * 2 < minEntriesForParallelism) { + if (!allowParallel || + numEntries - sizeTwoCliques.size() * 2 < minEntriesForParallelism) { for (HighsInt i = 0; i < N; ++i) { if (haveCommonClique(numQueries, v, q[i])) neighbourhoodInds.push_back(i); } @@ -647,7 +648,7 @@ bool HighsCliqueTable::processNewEdge(HighsDomain& globaldom, CliqueVar v1, void HighsCliqueTable::addClique(const HighsMipSolver& mipsolver, CliqueVar* cliquevars, HighsInt numcliquevars, bool equality, HighsInt origin) { - HighsDomain& globaldom = mipsolver.mipdata_->domain; + HighsDomain& globaldom = mipsolver.mipdata_->getDomain(); mipsolver.mipdata_->debugSolution.checkClique(cliquevars, numcliquevars); const HighsInt maxNumCliqueVars = 100; @@ -841,7 +842,7 @@ void HighsCliqueTable::extractCliques( HighsInt nbin, std::vector& perm, std::vector& clique, double feastol) { HighsImplications& implics = mipsolver.mipdata_->implications; - HighsDomain& globaldom = mipsolver.mipdata_->domain; + HighsDomain& globaldom = mipsolver.mipdata_->getDomain(); perm.resize(inds.size()); std::iota(perm.begin(), perm.end(), 0); @@ -1081,7 +1082,7 @@ void HighsCliqueTable::extractCliquesFromCut(const HighsMipSolver& mipsolver, if (isFull()) return; HighsImplications& implics = mipsolver.mipdata_->implications; - HighsDomain& globaldom = mipsolver.mipdata_->domain; + HighsDomain& globaldom = mipsolver.mipdata_->getDomain(); const double feastol = mipsolver.mipdata_->feastol; @@ -1276,7 +1277,7 @@ void HighsCliqueTable::extractCliques(HighsMipSolver& mipsolver, double rhs; - HighsDomain& globaldom = mipsolver.mipdata_->domain; + HighsDomain& globaldom = mipsolver.mipdata_->getDomain(); for (HighsInt i = 0; i != mipsolver.numRow(); ++i) { HighsInt start = mipsolver.mipdata_->ARstart_[i]; @@ -1380,7 +1381,7 @@ void HighsCliqueTable::extractObjCliques(HighsMipSolver& mipsolver) { HighsInt nbin = mipsolver.mipdata_->objectiveFunction.getNumBinariesInObjective(); if (nbin <= 1) return; - HighsDomain& globaldom = mipsolver.mipdata_->domain; + HighsDomain& globaldom = mipsolver.mipdata_->getDomain(); if (globaldom.getObjectiveLowerBound() == -kHighsInf) return; const double* vals; @@ -1603,7 +1604,9 @@ void HighsCliqueTable::vertexInfeasible(HighsDomain& globaldom, HighsInt col, void HighsCliqueTable::separateCliques(const HighsMipSolver& mipsolver, const std::vector& sol, - HighsCutPool& cutpool, double feastol) { + HighsCutPool& cutpool, double feastol, + HighsRandom& randgen, + int64_t& localNumNeighbourhoodQueries) { BronKerboschData data(sol); data.feastol = feastol; data.maxNeighbourhoodQueries = 1000000 + @@ -1611,7 +1614,7 @@ void HighsCliqueTable::separateCliques(const HighsMipSolver& mipsolver, mipsolver.mipdata_->total_lp_iterations * 1000; if (numNeighbourhoodQueries > data.maxNeighbourhoodQueries) return; data.maxNeighbourhoodQueries -= numNeighbourhoodQueries; - const HighsDomain& globaldom = mipsolver.mipdata_->domain; + const HighsDomain& globaldom = mipsolver.mipdata_->getDomain(); for (HighsInt i : mipsolver.mipdata_->integral_cols) { if (colsubstituted[i] || colDeleted[i]) continue; @@ -1698,9 +1701,9 @@ void HighsCliqueTable::separateCliques(const HighsMipSolver& mipsolver, false, false); } - numNeighbourhoodQueries += data.numNeighbourhoodQueries; + localNumNeighbourhoodQueries += data.numNeighbourhoodQueries; - if (runcliquesubsumption) { + if (runcliquesubsumption && &randgen == &this->randgen) { for (std::vector& clique : data.cliques) { HighsInt nremoved = runCliqueSubsumption(globaldom, clique); @@ -1841,7 +1844,7 @@ void HighsCliqueTable::cleanupFixed(HighsDomain& globaldom) { if (nfixings != oldnfixings) propagateAndCleanup(globaldom); } -HighsInt HighsCliqueTable::getNumImplications(HighsInt col) { +HighsInt HighsCliqueTable::getNumImplications(HighsInt col) const { // first count all cliques as one implication, so that cliques of size two // are accounted for already HighsInt i0 = CliqueVar(col, 0).index(); @@ -1860,7 +1863,7 @@ HighsInt HighsCliqueTable::getNumImplications(HighsInt col) { return numimplics; } -HighsInt HighsCliqueTable::getNumImplications(HighsInt col, bool val) { +HighsInt HighsCliqueTable::getNumImplications(HighsInt col, bool val) const { HighsInt iVal = CliqueVar(col, val).index(); // each size two clique is one implication @@ -2196,6 +2199,7 @@ void HighsCliqueTable::rebuild( numvars != oldnumvars ? false : cliques[i].equality, origin); } + newCliqueTable.setAllowParallel(allowParallel); *this = std::move(newCliqueTable); } @@ -2232,5 +2236,7 @@ void HighsCliqueTable::buildFrom(const HighsLp* origModel, newCliqueTable.colsubstituted = init.colsubstituted; newCliqueTable.substitutions = init.substitutions; + // Currently assume buildFrom is always used for sub-mips + newCliqueTable.setAllowParallel(false); *this = std::move(newCliqueTable); } diff --git a/highs/mip/HighsCliqueTable.h b/highs/mip/HighsCliqueTable.h index 55d46126a08..3bfca902a5f 100644 --- a/highs/mip/HighsCliqueTable.h +++ b/highs/mip/HighsCliqueTable.h @@ -96,6 +96,7 @@ class HighsCliqueTable { HighsInt maxEntries; HighsInt minEntriesForParallelism; bool inPresolve; + bool allowParallel; void unlink(HighsInt pos, HighsInt cliqueid); @@ -174,6 +175,7 @@ class HighsCliqueTable { maxEntries = kHighsIInf; minEntriesForParallelism = kHighsIInf; inPresolve = false; + allowParallel = true; } void setPresolveFlag(bool inPresolve) { this->inPresolve = inPresolve; } @@ -182,6 +184,10 @@ class HighsCliqueTable { HighsInt getNumEntries() const { return numEntries; } + HighsRandom& getRandgen() { return randgen; } + + int64_t& getNumNeighbourhoodQueries() { return numNeighbourhoodQueries; } + HighsInt partitionNeighbourhood(std::vector& neighbourhoodInds, int64_t& numNeighbourhoodqueries, CliqueVar v, CliqueVar* q, HighsInt N) const; @@ -287,7 +293,8 @@ class HighsCliqueTable { void separateCliques(const HighsMipSolver& mipsolver, const std::vector& sol, HighsCutPool& cutpool, - double feastol); + double feastol, HighsRandom& randgen, + int64_t& localNumNeighbourhoodQueries); std::vector> separateCliques( const std::vector& sol, const HighsDomain& globaldom, @@ -300,9 +307,9 @@ class HighsCliqueTable { void addImplications(HighsDomain& domain, HighsInt col, HighsInt val); - HighsInt getNumImplications(HighsInt col); + HighsInt getNumImplications(HighsInt col) const; - HighsInt getNumImplications(HighsInt col, bool val); + HighsInt getNumImplications(HighsInt col, bool val) const; void runCliqueMerging(HighsDomain& globaldomain); @@ -324,6 +331,10 @@ class HighsCliqueTable { HighsInt numCliques(HighsInt col, bool val) const { return numcliquesvar[CliqueVar(col, val).index()]; } + + void setAllowParallel(const bool allowParallel) { + this->allowParallel = allowParallel; + } }; #endif diff --git a/highs/mip/HighsConflictPool.cpp b/highs/mip/HighsConflictPool.cpp index 7e1778eec9c..b4c37d1591d 100644 --- a/highs/mip/HighsConflictPool.cpp +++ b/highs/mip/HighsConflictPool.cpp @@ -45,6 +45,7 @@ void HighsConflictPool::addConflictCut( conflictRanges_.emplace_back(start, end); ages_.resize(conflictRanges_.size()); modification_.resize(conflictRanges_.size()); + ageResetWhileLocked_.resize(conflictRanges_.size()); } else { conflictIndex = deletedConflicts_.back(); deletedConflicts_.pop_back(); @@ -52,6 +53,7 @@ void HighsConflictPool::addConflictCut( conflictRanges_[conflictIndex].second = end; } + ageResetWhileLocked_[conflictIndex].store(0, std::memory_order_relaxed); modification_[conflictIndex] += 1; ages_[conflictIndex] = 0; ageDistribution_[ages_[conflictIndex]] += 1; @@ -117,6 +119,7 @@ void HighsConflictPool::addReconvergenceCut( conflictRanges_.emplace_back(start, end); ages_.resize(conflictRanges_.size()); modification_.resize(conflictRanges_.size()); + ageResetWhileLocked_.resize(conflictRanges_.size()); } else { conflictIndex = deletedConflicts_.back(); deletedConflicts_.pop_back(); @@ -124,6 +127,7 @@ void HighsConflictPool::addReconvergenceCut( conflictRanges_[conflictIndex].second = end; } + ageResetWhileLocked_[conflictIndex].store(0, std::memory_order_relaxed); modification_[conflictIndex] += 1; ages_[conflictIndex] = 0; ageDistribution_[ages_[conflictIndex]] += 1; @@ -177,7 +181,8 @@ void HighsConflictPool::removeConflict(HighsInt conflict) { ++modification_[conflict]; } -void HighsConflictPool::performAging() { +void HighsConflictPool::performAging(const bool thread_safe) { + if (age_lock_) return; HighsInt conflictMaxIndex = conflictRanges_.size(); HighsInt agelim = agelim_; HighsInt numActiveConflicts = getNumConflicts(); @@ -188,9 +193,13 @@ void HighsConflictPool::performAging() { for (HighsInt i = 0; i != conflictMaxIndex; ++i) { if (ages_[i] < 0) continue; + if (thread_safe && + ageResetWhileLocked_[i].load(std::memory_order_relaxed) == 1) + resetAge(i); ageDistribution_[ages_[i]] -= 1; ages_[i] += 1; + ageResetWhileLocked_[i].store(0, std::memory_order_relaxed); if (ages_[i] > agelim) { ages_[i] = -1; @@ -199,3 +208,77 @@ void HighsConflictPool::performAging() { ageDistribution_[ages_[i]] += 1; } } + +void HighsConflictPool::addConflictFromOtherPool( + const HighsDomainChange* conflictEntries, const HighsInt conflictLen) { + HighsInt conflictIndex; + HighsInt start; + HighsInt end; + std::set>::iterator it; + if (freeSpaces_.empty() || + (it = freeSpaces_.lower_bound( + std::make_pair(conflictLen, HighsInt{-1}))) == freeSpaces_.end()) { + start = conflictEntries_.size(); + end = start + conflictLen; + + conflictEntries_.resize(end); + } else { + std::pair freeslot = *it; + freeSpaces_.erase(it); + + start = freeslot.second; + end = start + conflictLen; + // if the space was not completely occupied, we register the remainder of + // it again in the priority queue + if (freeslot.first > conflictLen) { + freeSpaces_.emplace(freeslot.first - conflictLen, end); + } + } + + // register the range of entries for this conflict with a reused or a new + // index + if (deletedConflicts_.empty()) { + conflictIndex = conflictRanges_.size(); + conflictRanges_.emplace_back(start, end); + ages_.resize(conflictRanges_.size()); + modification_.resize(conflictRanges_.size()); + ageResetWhileLocked_.resize(conflictRanges_.size()); + } else { + conflictIndex = deletedConflicts_.back(); + deletedConflicts_.pop_back(); + conflictRanges_[conflictIndex].first = start; + conflictRanges_[conflictIndex].second = end; + } + + ageResetWhileLocked_[conflictIndex].store(0, std::memory_order_relaxed); + modification_[conflictIndex] += 1; + ages_[conflictIndex] = 0; + ageDistribution_[ages_[conflictIndex]] += 1; + + for (HighsInt i = 0; i != conflictLen; ++i) { + assert(start + i < end); + conflictEntries_[start + i] = conflictEntries[i]; + } + + for (HighsDomain::ConflictPoolPropagation* conflictProp : propagationDomains) + conflictProp->conflictAdded(conflictIndex); +} + +void HighsConflictPool::syncConflictPool(HighsConflictPool& syncpool) { + HighsInt conflictMaxIndex = conflictRanges_.size(); + for (HighsInt i = 0; i != conflictMaxIndex; ++i) { + if (ages_[i] < 0) continue; + HighsInt start = conflictRanges_[i].first; + HighsInt end = conflictRanges_[i].second; + assert(start >= 0 && end >= 0); + syncpool.addConflictFromOtherPool(&conflictEntries_[start], end - start); + removeConflict(i); + } + deletedConflicts_.clear(); + freeSpaces_.clear(); + conflictRanges_.clear(); + conflictEntries_.clear(); + modification_.clear(); + ages_.clear(); + ageResetWhileLocked_.clear(); +} diff --git a/highs/mip/HighsConflictPool.h b/highs/mip/HighsConflictPool.h index 9411824e3d5..c13ac077314 100644 --- a/highs/mip/HighsConflictPool.h +++ b/highs/mip/HighsConflictPool.h @@ -8,6 +8,7 @@ #ifndef HIGHS_CONFLICTPOOL_H_ #define HIGHS_CONFLICTPOOL_H_ +#include #include #include @@ -18,9 +19,11 @@ class HighsConflictPool { private: HighsInt agelim_; HighsInt softlimit_; + bool age_lock_; std::vector ageDistribution_; std::vector ages_; std::vector modification_; + std::deque> ageResetWhileLocked_; std::vector conflictEntries_; std::vector> conflictRanges_; @@ -38,9 +41,11 @@ class HighsConflictPool { HighsConflictPool(HighsInt agelim, HighsInt softlimit) : agelim_(agelim), softlimit_(softlimit), + age_lock_(false), ageDistribution_(), ages_(), modification_(), + ageResetWhileLocked_(), conflictEntries_(), conflictRanges_(), freeSpaces_(), @@ -61,10 +66,19 @@ class HighsConflictPool { void removeConflict(HighsInt conflict); - void performAging(); + void performAging(bool thread_safe = false); + + void addConflictFromOtherPool(const HighsDomainChange* conflictEntries, + HighsInt conflictLen); + + void syncConflictPool(HighsConflictPool& syncpool); void resetAge(HighsInt conflict) { if (ages_[conflict] > 0) { + if (age_lock_) { + ageResetWhileLocked_[conflict].store(1, std::memory_order_relaxed); + return; + } ageDistribution_[ages_[conflict]] -= 1; ageDistribution_[0] += 1; ages_[conflict] = 0; @@ -104,6 +118,8 @@ class HighsConflictPool { HighsInt getNumConflicts() const { return conflictRanges_.size() - deletedConflicts_.size(); } + + void setAgeLock(const bool ageLock) { age_lock_ = ageLock; } }; #endif diff --git a/highs/mip/HighsCutGeneration.cpp b/highs/mip/HighsCutGeneration.cpp index 706438371d2..604c404ea75 100644 --- a/highs/mip/HighsCutGeneration.cpp +++ b/highs/mip/HighsCutGeneration.cpp @@ -8,6 +8,7 @@ #include "mip/HighsCutGeneration.h" #include "../extern/pdqsort/pdqsort.h" +#include "mip/HighsDomain.h" #include "mip/HighsMipSolverData.h" #include "mip/HighsTransformedLp.h" #include "util/HighsIntegers.h" @@ -753,7 +754,7 @@ double HighsCutGeneration::scale(double val) { return std::ldexp(1.0, expshift); } -bool HighsCutGeneration::postprocessCut() { +bool HighsCutGeneration::postprocessCut(const HighsDomain& globaldom) { // right hand sides slightly below zero are likely due to numerical errors and // can cause numerical troubles with scaling, so set them to zero if (rhs < 0 && rhs > -epsilon) rhs = 0; @@ -771,7 +772,6 @@ bool HighsCutGeneration::postprocessCut() { return true; } - HighsDomain& globaldomain = lpRelaxation.getMipSolver().mipdata_->domain; // determine maximal absolute coefficient double maxAbsValue = 0.0; for (HighsInt i = 0; i != rowlen; ++i) @@ -787,13 +787,13 @@ bool HighsCutGeneration::postprocessCut() { if (vals[i] == 0) continue; if (std::abs(vals[i]) <= minCoefficientValue) { if (vals[i] < 0) { - double ub = globaldomain.col_upper_[inds[i]]; + double ub = globaldom.col_upper_[inds[i]]; if (ub == kHighsInf) return false; else rhs -= ub * vals[i]; } else { - double lb = globaldomain.col_lower_[inds[i]]; + double lb = globaldom.col_lower_[inds[i]]; if (lb == -kHighsInf) return false; else @@ -852,12 +852,12 @@ bool HighsCutGeneration::postprocessCut() { // upperbound constraint to make it exactly integral instead and // therefore weaken the right hand side if (delta < 0.0) { - double ub = globaldomain.col_upper_[inds[i]]; + double ub = globaldom.col_upper_[inds[i]]; if (ub == kHighsInf) return false; rhs -= delta * ub; } else { - double lb = globaldomain.col_lower_[inds[i]]; + double lb = globaldom.col_lower_[inds[i]]; if (lb == -kHighsInf) return false; rhs -= delta * lb; @@ -1165,7 +1165,7 @@ bool HighsCutGeneration::generateCut(HighsTransformedLp& transLp, rowlen, rhs_); // apply cut postprocessing including scaling and removal of small // coefficients - if (!postprocessCut()) return false; + if (!postprocessCut(transLp.getGlobaldom())) return false; rhs_ = (double)rhs; vals_.resize(rowlen); inds_.resize(rowlen); @@ -1180,8 +1180,7 @@ bool HighsCutGeneration::generateCut(HighsTransformedLp& transLp, if (violation <= 10 * feastol) return false; - lpRelaxation.getMipSolver().mipdata_->domain.tightenCoefficients( - inds, vals, rowlen, rhs_); + transLp.getGlobaldom().tightenCoefficients(inds, vals, rowlen, rhs_); // if the cut is violated by a small factor above the feasibility // tolerance, add it to the cutpool @@ -1194,7 +1193,8 @@ bool HighsCutGeneration::generateCut(HighsTransformedLp& transLp, return cutindex != -1; } -bool HighsCutGeneration::generateConflict(HighsDomain& localdomain, +bool HighsCutGeneration::generateConflict(const HighsDomain& localdomain, + const HighsDomain& globaldom, std::vector& proofinds, std::vector& proofvals, double& proofrhs) { @@ -1211,27 +1211,26 @@ bool HighsCutGeneration::generateConflict(HighsDomain& localdomain, upper.resize(rowlen); solval.resize(rowlen); - HighsDomain& globaldomain = lpRelaxation.getMipSolver().mipdata_->domain; double activity = 0.0; for (HighsInt i = 0; i != rowlen; ++i) { HighsInt col = inds[i]; - upper[i] = globaldomain.col_upper_[col] - globaldomain.col_lower_[col]; + upper[i] = globaldom.col_upper_[col] - globaldom.col_lower_[col]; - solval[i] = vals[i] < 0 ? std::min(globaldomain.col_upper_[col], - localdomain.col_upper_[col]) - : std::max(globaldomain.col_lower_[col], - localdomain.col_lower_[col]); - if (vals[i] < 0 && globaldomain.col_upper_[col] != kHighsInf) { - rhs -= globaldomain.col_upper_[col] * vals[i]; + solval[i] = + vals[i] < 0 + ? std::min(globaldom.col_upper_[col], localdomain.col_upper_[col]) + : std::max(globaldom.col_lower_[col], localdomain.col_lower_[col]); + if (vals[i] < 0 && globaldom.col_upper_[col] != kHighsInf) { + rhs -= globaldom.col_upper_[col] * vals[i]; vals[i] = -vals[i]; complementation[i] = 1; - solval[i] = globaldomain.col_upper_[col] - solval[i]; + solval[i] = globaldom.col_upper_[col] - solval[i]; } else { - rhs -= globaldomain.col_lower_[col] * vals[i]; + rhs -= globaldom.col_lower_[col] * vals[i]; complementation[i] = 0; - solval[i] = solval[i] - globaldomain.col_lower_[col]; + solval[i] = solval[i] - globaldom.col_lower_[col]; } activity += solval[i] * vals[i]; @@ -1259,16 +1258,16 @@ bool HighsCutGeneration::generateConflict(HighsDomain& localdomain, if (!complementation.empty()) { for (HighsInt i = 0; i != rowlen; ++i) { if (complementation[i]) { - rhs -= globaldomain.col_upper_[inds[i]] * vals[i]; + rhs -= globaldom.col_upper_[inds[i]] * vals[i]; vals[i] = -vals[i]; } else - rhs += globaldomain.col_lower_[inds[i]] * vals[i]; + rhs += globaldom.col_lower_[inds[i]] * vals[i]; } } // apply cut postprocessing including scaling and removal of small // coefficients - if (!postprocessCut()) return false; + if (!postprocessCut(globaldom)) return false; proofvals.resize(rowlen); proofinds.resize(rowlen); @@ -1276,8 +1275,8 @@ bool HighsCutGeneration::generateConflict(HighsDomain& localdomain, bool cutintegral = integralSupport && integralCoefficients; - lpRelaxation.getMipSolver().mipdata_->domain.tightenCoefficients( - proofinds.data(), proofvals.data(), rowlen, proofrhs); + globaldom.tightenCoefficients(proofinds.data(), proofvals.data(), rowlen, + proofrhs); HighsInt cutindex = cutpool.addCut(lpRelaxation.getMipSolver(), proofinds.data(), proofvals.data(), rowlen, @@ -1288,7 +1287,8 @@ bool HighsCutGeneration::generateConflict(HighsDomain& localdomain, return cutindex != -1; } -bool HighsCutGeneration::finalizeAndAddCut(std::vector& inds_, +bool HighsCutGeneration::finalizeAndAddCut(const HighsDomain& globaldom, + std::vector& inds_, std::vector& vals_, double& rhs_) { complementation.clear(); @@ -1317,7 +1317,7 @@ bool HighsCutGeneration::finalizeAndAddCut(std::vector& inds_, rowlen, rhs_); // apply cut postprocessing including scaling and removal of small // coefficients - if (!postprocessCut()) return false; + if (!postprocessCut(globaldom)) return false; rhs_ = (double)rhs; vals_.resize(rowlen); inds_.resize(rowlen); @@ -1332,8 +1332,7 @@ bool HighsCutGeneration::finalizeAndAddCut(std::vector& inds_, if (violation <= 10 * feastol) return false; - lpRelaxation.getMipSolver().mipdata_->domain.tightenCoefficients( - inds, vals, rowlen, rhs_); + globaldom.tightenCoefficients(inds, vals, rowlen, rhs_); // if the cut is violated by a small factor above the feasibility // tolerance, add it to the cutpool diff --git a/highs/mip/HighsCutGeneration.h b/highs/mip/HighsCutGeneration.h index b1644d6129f..39e728db6cc 100644 --- a/highs/mip/HighsCutGeneration.h +++ b/highs/mip/HighsCutGeneration.h @@ -72,7 +72,7 @@ class HighsCutGeneration { double scale(double val); - bool postprocessCut(); + bool postprocessCut(const HighsDomain& globaldom); bool preprocessBaseInequality(bool& hasUnboundedInts, bool& hasGeneralInts, bool& hasContinuous); @@ -101,12 +101,15 @@ class HighsCutGeneration { /// generate a conflict from the given proof constraint which cuts of the /// given local domain - bool generateConflict(HighsDomain& localdom, std::vector& proofinds, + bool generateConflict(const HighsDomain& localdom, + const HighsDomain& globaldom, + std::vector& proofinds, std::vector& proofvals, double& proofrhs); /// applies postprocessing to an externally generated cut and adds it to the /// cutpool if it is violated enough - bool finalizeAndAddCut(std::vector& inds, std::vector& vals, + bool finalizeAndAddCut(const HighsDomain& globaldom, + std::vector& inds, std::vector& vals, double& rhs); }; diff --git a/highs/mip/HighsCutPool.cpp b/highs/mip/HighsCutPool.cpp index 067c6bc7c2c..52c2a3726e6 100644 --- a/highs/mip/HighsCutPool.cpp +++ b/highs/mip/HighsCutPool.cpp @@ -115,9 +115,43 @@ double HighsCutPool::getParallelism(HighsInt row1, HighsInt row2) const { return dotprod * rownormalization_[row1] * rownormalization_[row2]; } -void HighsCutPool::lpCutRemoved(HighsInt cut) { +double HighsCutPool::getParallelism(HighsInt row1, HighsInt row2, + const HighsCutPool& pool2) const { + HighsInt i1 = matrix_.getRowStart(row1); + const HighsInt end1 = matrix_.getRowEnd(row1); + + HighsInt i2 = pool2.matrix_.getRowStart(row2); + const HighsInt end2 = pool2.matrix_.getRowEnd(row2); + + const HighsInt* ARindex1 = matrix_.getARindex(); + const double* ARvalue1 = matrix_.getARvalue(); + const HighsInt* ARindex2 = pool2.matrix_.getARindex(); + const double* ARvalue2 = pool2.matrix_.getARvalue(); + + double dotprod = 0.0; + while (i1 != end1 && i2 != end2) { + HighsInt col1 = ARindex1[i1]; + HighsInt col2 = ARindex2[i2]; + + if (col1 < col2) + ++i1; + else if (col2 < col1) + ++i2; + else { + dotprod += ARvalue1[i1] * ARvalue2[i2]; + ++i1; + ++i2; + } + } + + return dotprod * rownormalization_[row1] * pool2.rownormalization_[row2]; +} + +void HighsCutPool::lpCutRemoved(HighsInt cut, bool thread_safe) { + const HighsInt n = numLps_[cut].fetch_add(-1, std::memory_order_relaxed); + if (thread_safe || n > 1) return; if (matrix_.columnsLinked(cut)) { - propRows.erase(std::make_pair(-1, cut)); + propRows.erase(std::make_pair(ages_[cut], cut)); propRows.emplace(1, cut); } ages_[cut] = 1; @@ -136,6 +170,31 @@ void HighsCutPool::performAging() { } for (HighsInt i = 0; i != cutIndexEnd; ++i) { + // Catch buffered changes (should only occur in parallel case) + if (numLps_[i] > 0 && ages_[i] >= 0) { + // Cut has been added to the LP, but age changes haven't been made + --ageDistribution[ages_[i]]; + if (matrix_.columnsLinked(i)) { + propRows.erase(std::make_pair(ages_[i], i)); + propRows.emplace(-1, i); + } + ages_[i] = -1; + ++numLpCuts; + ageResetWhileLocked_[i].store(0, std::memory_order_relaxed); + } else if (numLps_[i] == 0 && ages_[i] == -1 && rhs_[i] != kHighsInf) { + // Cut was removed from the LP, but age changes haven't been made + if (matrix_.columnsLinked(i)) { + propRows.erase(std::make_pair(ages_[i], i)); + propRows.emplace(1, i); + } + ages_[i] = 1; + --numLpCuts; + ++ageDistribution[1]; + ageResetWhileLocked_[i].store(0, std::memory_order_relaxed); + } else if (ageResetWhileLocked_[i].load(std::memory_order_relaxed) == 1) { + resetAge(i); + } + ageResetWhileLocked_[i].store(0, std::memory_order_relaxed); if (ages_[i] < 0) continue; bool isPropagated = matrix_.columnsLinked(i); @@ -156,6 +215,7 @@ void HighsCutPool::performAging() { matrix_.removeRow(i); ages_[i] = -1; rhs_[i] = kHighsInf; + hasSynced_[i] = false; } else { if (isPropagated) propRows.emplace(ages_[i], i); ageDistribution[ages_[i]] += 1; @@ -165,14 +225,15 @@ void HighsCutPool::performAging() { assert((HighsInt)propRows.size() == numPropRows); } -void HighsCutPool::separate(const std::vector& sol, HighsDomain& domain, - HighsCutSet& cutset, double feastol) { +void HighsCutPool::separate(const std::vector& sol, + const HighsDomain& domain, HighsCutSet& cutset, + double feastol, + const std::deque& cutpools, + bool thread_safe) { HighsInt nrows = matrix_.getNumRows(); const HighsInt* ARindex = matrix_.getARindex(); const double* ARvalue = matrix_.getARvalue(); - assert(cutset.empty()); - std::vector> efficacious_cuts; HighsInt agelim = agelim_; @@ -185,6 +246,9 @@ void HighsCutPool::separate(const std::vector& sol, HighsDomain& domain, for (HighsInt i = 0; i < nrows; ++i) { // cuts with an age of -1 are already in the LP and are therefore skipped + // TODO MT: Parallel case tries to add cuts already in current LP. + // TODO MT: Inefficient. Not sure what happens if added twice. + // TODO MT: The cut shouldn't have enough violation to be added though. if (ages_[i] < 0) continue; HighsInt start = matrix_.getRowStart(i); @@ -201,10 +265,15 @@ void HighsCutPool::separate(const std::vector& sol, HighsDomain& domain, // if the cut is not violated more than feasibility tolerance // we skip it and increase its age, otherwise we reset its age - ageDistribution[ages_[i]] -= 1; bool isPropagated = matrix_.columnsLinked(i); - if (isPropagated) propRows.erase(std::make_pair(ages_[i], i)); + if (!thread_safe) { + ageDistribution[ages_[i]] -= 1; + if (isPropagated) { + propRows.erase(std::make_pair(ages_[i], i)); + } + } if (double(viol) <= feastol) { + if (thread_safe) continue; ++ages_[i]; if (ages_[i] >= agelim) { uint64_t h = compute_cut_hash(&ARindex[start], &ARvalue[start], @@ -221,7 +290,9 @@ void HighsCutPool::separate(const std::vector& sol, HighsDomain& domain, matrix_.removeRow(i); ages_[i] = -1; - rhs_[i] = 0; + rhs_[i] = kHighsInf; + ageResetWhileLocked_[i].store(0, std::memory_order_relaxed); + hasSynced_[i] = false; auto range = hashToCutMap.equal_range(h); for (auto it = range.first; it != range.second; ++it) { @@ -262,9 +333,11 @@ void HighsCutPool::separate(const std::vector& sol, HighsDomain& domain, } } - ages_[i] = 0; - ++ageDistribution[0]; - if (isPropagated) propRows.emplace(ages_[i], i); + if (!thread_safe) { + ages_[i] = 0; + ++ageDistribution[0]; + if (isPropagated) propRows.emplace(ages_[i], i); + } double score = viol / (numActiveNzs * sqrt(double(rownorm))); efficacious_cuts.emplace_back(score, i); @@ -287,8 +360,13 @@ void HighsCutPool::separate(const std::vector& sol, HighsDomain& domain, b.second); }); - bestObservedScore = std::max(efficacious_cuts[0].first, bestObservedScore); - double minScore = minScoreFactor * bestObservedScore; + double bestObservedScoreCopy = bestObservedScore; + double& bestObservedScore_ = + thread_safe ? bestObservedScoreCopy : bestObservedScore; + bestObservedScore_ = std::max(efficacious_cuts[0].first, bestObservedScore); + double minScoreFactorCopy = minScoreFactor; + double& minScoreFactor_ = thread_safe ? minScoreFactorCopy : minScoreFactor; + double minScore = minScoreFactor_ * bestObservedScore; HighsInt numefficacious = std::upper_bound(efficacious_cuts.begin(), efficacious_cuts.end(), @@ -303,38 +381,55 @@ void HighsCutPool::separate(const std::vector& sol, HighsDomain& domain, if (numefficacious <= lowerThreshold) { numefficacious = std::max(efficacious_cuts.size() / 2, size_t{1}); - minScoreFactor = + minScoreFactor_ = efficacious_cuts[numefficacious - 1].first / bestObservedScore; } else if (numefficacious > upperThreshold) { - minScoreFactor = efficacious_cuts[upperThreshold].first / bestObservedScore; + minScoreFactor_ = + efficacious_cuts[upperThreshold].first / bestObservedScore; } efficacious_cuts.resize(numefficacious); - HighsInt selectednnz = 0; - - assert(cutset.empty()); + HighsInt orignumcuts = cutset.numCuts(); + HighsInt origselectednnz = cutset.ARindex_.size(); + HighsInt selectednnz = origselectednnz; for (const std::pair& p : efficacious_cuts) { bool discard = false; double maxpar = 0.1; - for (HighsInt k : cutset.cutindices) { - if (getParallelism(k, p.second) > maxpar) { - discard = true; - break; + for (HighsInt i = 0; i != static_cast(cutset.cutindices.size()); + ++i) { + if (cutset.cutpools[i] == index_) { + if (getParallelism(cutset.cutindices[i], p.second) > maxpar) { + discard = true; + break; + } + } else { + // TODO MT: This assumes the cuts in the pool are not changing during, + // TODO MT: this query, i.e., the worker's pool and the global pool. + // TODO MT: Currently safe, but doesn't generalise to all designs. + if (getParallelism(p.second, cutset.cutindices[i], + cutpools[cutset.cutpools[i]]) > maxpar) { + discard = true; + break; + } } } if (discard) continue; - --ageDistribution[ages_[p.second]]; - ++numLpCuts; - if (matrix_.columnsLinked(p.second)) { - propRows.erase(std::make_pair(ages_[p.second], p.second)); - propRows.emplace(-1, p.second); + numLps_[p.second].fetch_add(1, std::memory_order_relaxed); + if (!thread_safe) { + --ageDistribution[ages_[p.second]]; + ++numLpCuts; + if (matrix_.columnsLinked(p.second)) { + propRows.erase(std::make_pair(ages_[p.second], p.second)); + propRows.emplace(-1, p.second); + } + ages_[p.second] = -1; } - ages_[p.second] = -1; cutset.cutindices.push_back(p.second); + cutset.cutpools.push_back(index_); selectednnz += matrix_.getRowEnd(p.second) - matrix_.getRowStart(p.second); } @@ -343,8 +438,8 @@ void HighsCutPool::separate(const std::vector& sol, HighsDomain& domain, assert(int(cutset.ARvalue_.size()) == selectednnz); assert(int(cutset.ARindex_.size()) == selectednnz); - HighsInt offset = 0; - for (HighsInt i = 0; i != cutset.numCuts(); ++i) { + HighsInt offset = origselectednnz; + for (HighsInt i = orignumcuts; i != cutset.numCuts(); ++i) { cutset.ARstart_[i] = offset; HighsInt cut = cutset.cutindices[i]; HighsInt start = matrix_.getRowStart(cut); @@ -369,6 +464,7 @@ void HighsCutPool::separateLpCutsAfterRestart(HighsCutSet& cutset) { HighsInt numcuts = matrix_.getNumRows(); cutset.cutindices.resize(numcuts); + cutset.cutpools.resize(numcuts, index_); std::iota(cutset.cutindices.begin(), cutset.cutindices.end(), 0); cutset.resize(matrix_.nonzeroCapacity()); @@ -382,6 +478,7 @@ void HighsCutPool::separateLpCutsAfterRestart(HighsCutSet& cutset) { propRows.erase(std::make_pair(ages_[i], i)); propRows.emplace(-1, i); } + numLps_[i] = 1; ages_[i] = -1; cutset.ARstart_[i] = offset; HighsInt cut = cutset.cutindices[i]; @@ -431,6 +528,14 @@ HighsInt HighsCutPool::addCut(const HighsMipSolver& mipsolver, HighsInt* Rindex, uint64_t h = compute_cut_hash(Rindex, Rvalue, maxabscoef, Rlen); double normalization = 1.0 / double(sqrt(norm)); + // TODO MT: This global duplicate check assumes the global pool doesn't + // have cuts added or deleted during time when local pools can add a cut. + if (this != &mipsolver.mipdata_->getCutPool()) { + if (mipsolver.mipdata_->getCutPool().isDuplicate(h, normalization, Rindex, + Rvalue, Rlen, rhs)) { + return -1; + } + } if (isDuplicate(h, normalization, Rindex, Rvalue, Rlen, rhs)) return -1; // if (Rlen > 0.15 * matrix_.numCols()) @@ -495,6 +600,9 @@ HighsInt HighsCutPool::addCut(const HighsMipSolver& mipsolver, HighsInt* Rindex, if (rowindex == int(rhs_.size())) { rhs_.resize(rowindex + 1); ages_.resize(rowindex + 1); + numLps_.resize(rowindex + 1); + ageResetWhileLocked_.resize(rowindex + 1); + hasSynced_.resize(rowindex + 1); rownormalization_.resize(rowindex + 1); maxabscoef_.resize(rowindex + 1); rowintegral.resize(rowindex + 1); @@ -505,6 +613,9 @@ HighsInt HighsCutPool::addCut(const HighsMipSolver& mipsolver, HighsInt* Rindex, ages_[rowindex] = std::max(HighsInt{0}, agelim_ - 5); ++ageDistribution[ages_[rowindex]]; rowintegral[rowindex] = integral; + numLps_[rowindex] = 0; + ageResetWhileLocked_[rowindex].store(0, std::memory_order_relaxed); + hasSynced_[rowindex] = false; if (propagate) propRows.emplace(ages_[rowindex], rowindex); assert((HighsInt)propRows.size() == numPropRows); @@ -515,7 +626,7 @@ HighsInt HighsCutPool::addCut(const HighsMipSolver& mipsolver, HighsInt* Rindex, for (HighsDomain::CutpoolPropagation* propagationdomain : propagationDomains) propagationdomain->cutAdded(rowindex, propagate); - if (extractCliques && this == &mipsolver.mipdata_->cutpool) { + if (extractCliques && this == &mipsolver.mipdata_->getCutPool()) { // if this is the global cutpool extract cliques from the cut if (Rlen <= 100) mipsolver.mipdata_->cliquetable.extractCliquesFromCut(mipsolver, Rindex, @@ -524,3 +635,28 @@ HighsInt HighsCutPool::addCut(const HighsMipSolver& mipsolver, HighsInt* Rindex, return rowindex; } + +void HighsCutPool::syncCutPool(const HighsMipSolver& mipsolver, + HighsCutPool& syncpool) { + HighsInt cutIndexEnd = matrix_.getNumRows(); + + for (HighsInt i = 0; i != cutIndexEnd; ++i) { + // Only sync cuts in the LP that are not already synced + if ((numLps_[i] > 0 || + ageResetWhileLocked_[i].load(std::memory_order_relaxed) == 1) && + !hasSynced_[i]) { + HighsInt Rlen; + const HighsInt* Rindex; + const double* Rvalue; + getCut(i, Rlen, Rindex, Rvalue); + // copy cut into something mutable (addCut reorders so can't take const) + std::vector idxs(Rindex, Rindex + Rlen); + std::vector vals(Rvalue, Rvalue + Rlen); + syncpool.addCut(mipsolver, idxs.data(), vals.data(), Rlen, rhs_[i], + rowintegral[i]); + hasSynced_[i] = true; + } + } + + assert((HighsInt)propRows.size() == numPropRows); +} \ No newline at end of file diff --git a/highs/mip/HighsCutPool.h b/highs/mip/HighsCutPool.h index 372aefe2135..cae731fe212 100644 --- a/highs/mip/HighsCutPool.h +++ b/highs/mip/HighsCutPool.h @@ -8,6 +8,7 @@ #ifndef HIGHS_CUTPOOL_H_ #define HIGHS_CUTPOOL_H_ +#include #include #include #include @@ -20,6 +21,7 @@ class HighsLpRelaxation; struct HighsCutSet { std::vector cutindices; + std::vector cutpools; std::vector ARstart_; std::vector ARindex_; std::vector ARvalue_; @@ -39,6 +41,7 @@ struct HighsCutSet { void clear() { cutindices.clear(); + cutpools.clear(); upper_.clear(); ARstart_.clear(); ARindex_.clear(); @@ -53,6 +56,10 @@ class HighsCutPool { HighsDynamicRowMatrix matrix_; std::vector rhs_; std::vector ages_; + std::deque> numLps_; + std::deque> + ageResetWhileLocked_; // Was the cut propagated? + std::vector hasSynced_; // Has the cut been globally synced? std::vector rownormalization_; std::vector maxabscoef_; std::vector rowintegral; @@ -72,17 +79,18 @@ class HighsCutPool { std::vector ageDistribution; std::vector> sortBuffer; - bool isDuplicate(size_t hash, double norm, const HighsInt* Rindex, - const double* Rvalue, HighsInt Rlen, double rhs); - public: - HighsCutPool(HighsInt ncols, HighsInt agelim, HighsInt softlimit) + HighsInt index_; + + HighsCutPool(HighsInt ncols, HighsInt agelim, HighsInt softlimit, + HighsInt index) : matrix_(ncols), agelim_(agelim), softlimit_(softlimit), numLpCuts(0), numPropNzs(0), - numPropRows(0) { + numPropRows(0), + index_(index) { ageDistribution.resize(agelim_ + 1); minScoreFactor = 0.9; bestObservedScore = 0.0; @@ -92,8 +100,15 @@ class HighsCutPool { const std::vector& getRhs() const { return rhs_; } - void resetAge(HighsInt cut) { + bool isDuplicate(size_t hash, double norm, const HighsInt* Rindex, + const double* Rvalue, HighsInt Rlen, double rhs); + + void resetAge(HighsInt cut, bool thread_safe = false) { if (ages_[cut] > 0) { + if (thread_safe) { + ageResetWhileLocked_[cut].store(1, std::memory_order_relaxed); + return; + } if (matrix_.columnsLinked(cut)) { propRows.erase(std::make_pair(ages_[cut], cut)); propRows.emplace(0, cut); @@ -101,14 +116,18 @@ class HighsCutPool { ageDistribution[ages_[cut]] -= 1; ageDistribution[0] += 1; ages_[cut] = 0; + ageResetWhileLocked_[cut].store(0, std::memory_order_relaxed); } } double getParallelism(HighsInt row1, HighsInt row2) const; + double getParallelism(HighsInt row1, HighsInt row2, + const HighsCutPool& pool2) const; + void performAging(); - void lpCutRemoved(HighsInt cut); + void lpCutRemoved(HighsInt cut, bool thread_safe = false); void addPropagationDomain(HighsDomain::CutpoolPropagation* domain) { propagationDomains.push_back(domain); @@ -128,8 +147,15 @@ class HighsCutPool { ageDistribution.resize(agelim_ + 1); } - void separate(const std::vector& sol, HighsDomain& domprop, - HighsCutSet& cutset, double feastol); + void increaseNumLps(HighsInt cut, HighsInt n) { + assert(numLps_[cut] >= 1); + numLps_[cut].fetch_add(n, std::memory_order_relaxed); + }; + + void separate(const std::vector& sol, const HighsDomain& domprop, + HighsCutSet& cutset, double feastol, + const std::deque& cutpools, + bool thread_safe = false); void separateLpCutsAfterRestart(HighsCutSet& cutset); @@ -163,6 +189,8 @@ class HighsCutPool { cutinds = matrix_.getARindex() + start; cutvals = matrix_.getARvalue() + start; } + + void syncCutPool(const HighsMipSolver& mipsolver, HighsCutPool& syncpool); }; #endif diff --git a/highs/mip/HighsDebugSol.cpp b/highs/mip/HighsDebugSol.cpp index c7e194efe53..f9d84325478 100644 --- a/highs/mip/HighsDebugSol.cpp +++ b/highs/mip/HighsDebugSol.cpp @@ -74,7 +74,7 @@ void HighsDebugSol::activate() { debugSolObjective = double(debugsolobj + mipsolver->orig_model_->offset_); debugSolActive = true; printf("debug sol active\n"); - registerDomain(mipsolver->mipdata_->domain); + registerDomain(mipsolver->mipdata_->getDomain()); } else { highsLogUser(mipsolver->options_mip_->log_options, HighsLogType::kWarning, "debug solution: could not open file '%s'\n", @@ -83,8 +83,8 @@ void HighsDebugSol::activate() { model.lp_ = *mipsolver->model_; model.lp_.col_names_.clear(); model.lp_.row_names_.clear(); - model.lp_.col_lower_ = mipsolver->mipdata_->domain.col_lower_; - model.lp_.col_upper_ = mipsolver->mipdata_->domain.col_upper_; + model.lp_.col_lower_ = mipsolver->mipdata_->getDomain().col_lower_; + model.lp_.col_upper_ = mipsolver->mipdata_->getDomain().col_upper_; FilereaderMps().writeModelToFile(*mipsolver->options_mip_, "debug_mip.mps", model); } @@ -297,7 +297,7 @@ void HighsDebugSol::checkConflictReconvergenceFrontier( } } - auto reconvChg = mipsolver->mipdata_->domain.flip(reconvDomchg.domchg); + auto reconvChg = mipsolver->mipdata_->getDomain().flip(reconvDomchg.domchg); if (reconvChg.boundtype == HighsBoundType::kLower) { if (debugSolution[reconvChg.column] >= reconvChg.boundval) diff --git a/highs/mip/HighsDomain.cpp b/highs/mip/HighsDomain.cpp index 02671365629..d1156ab13ff 100644 --- a/highs/mip/HighsDomain.cpp +++ b/highs/mip/HighsDomain.cpp @@ -143,7 +143,26 @@ HighsDomain::ConflictPoolPropagation::ConflictPoolPropagation( conflictFlag_(other.conflictFlag_), propagateConflictInds_(other.propagateConflictInds_), watchedLiterals_(other.watchedLiterals_) { - conflictpool_->addPropagationDomain(this); + if (!domain->mipsolver->mipdata_->parallelLockActive() || + conflictpool_ != &domain->mipsolver->mipdata_->getConflictPool()) + conflictpool_->addPropagationDomain(this); +} + +HighsDomain::ConflictPoolPropagation& +HighsDomain::ConflictPoolPropagation::operator=( + const ConflictPoolPropagation& other) { + if (this == &other) return *this; + if (conflictpool_) conflictpool_->removePropagationDomain(this); + conflictpoolindex = other.conflictpoolindex; + domain = other.domain; + conflictpool_ = other.conflictpool_; + colLowerWatched_ = other.colLowerWatched_; + colUpperWatched_ = other.colUpperWatched_; + conflictFlag_ = other.conflictFlag_; + propagateConflictInds_ = other.propagateConflictInds_; + watchedLiterals_ = other.watchedLiterals_; + if (conflictpool_) conflictpool_->addPropagationDomain(this); + return *this; } HighsDomain::ConflictPoolPropagation::~ConflictPoolPropagation() { @@ -372,7 +391,31 @@ HighsDomain::CutpoolPropagation::CutpoolPropagation( propagatecutflags_(other.propagatecutflags_), propagatecutinds_(other.propagatecutinds_), capacityThreshold_(other.capacityThreshold_) { - cutpool->addPropagationDomain(this); + if (!domain->mipsolver->mipdata_->parallelLockActive() || + cutpool != &domain->mipsolver->mipdata_->getCutPool()) + cutpool->addPropagationDomain(this); +} + +// Warning: When a domain is copy-assigned, e.g., in `resetLocalDomain`, +// with the line `localdom = getDomain()`, then it is going to notify +// all cut / conflict pools that the original was propagating. +// This would be non-deterministic in the order in which the global +// pool gets notified. Currently, such code is only run in serial. + +HighsDomain::CutpoolPropagation& HighsDomain::CutpoolPropagation::operator=( + const CutpoolPropagation& other) { + if (this == &other) return *this; + if (cutpool) cutpool->removePropagationDomain(this); + cutpoolindex = other.cutpoolindex; + domain = other.domain; + cutpool = other.cutpool; + activitycuts_ = other.activitycuts_; + activitycutsinf_ = other.activitycutsinf_; + propagatecutflags_ = other.propagatecutflags_; + propagatecutinds_ = other.propagatecutinds_; + capacityThreshold_ = other.capacityThreshold_; + if (cutpool) cutpool->addPropagationDomain(this); + return *this; } HighsDomain::CutpoolPropagation::~CutpoolPropagation() { @@ -402,7 +445,7 @@ void HighsDomain::CutpoolPropagation::recomputeCapacityThreshold(HighsInt cut) { void HighsDomain::CutpoolPropagation::cutAdded(HighsInt cut, bool propagate) { if (!propagate) { - if (domain != &domain->mipsolver->mipdata_->domain) return; + if (domain != &domain->mipsolver->mipdata_->getDomain()) return; HighsInt start = cutpool->getMatrix().getRowStart(cut); HighsInt end = cutpool->getMatrix().getRowEnd(cut); const HighsInt* arindex = cutpool->getMatrix().getARindex(); @@ -443,7 +486,7 @@ void HighsDomain::CutpoolPropagation::cutAdded(HighsInt cut, bool propagate) { void HighsDomain::CutpoolPropagation::cutDeleted( HighsInt cut, bool deletedOnlyForPropagation) { if (deletedOnlyForPropagation && - domain == &domain->mipsolver->mipdata_->domain) { + domain == &domain->mipsolver->mipdata_->getDomain()) { assert(domain->branchPos_.empty()); return; } @@ -461,12 +504,12 @@ void HighsDomain::CutpoolPropagation::markPropagateCut(HighsInt cut) { } } -void HighsDomain::CutpoolPropagation::updateActivityLbChange(HighsInt col, - double oldbound, - double newbound) { - assert(!domain->infeasible_); +void HighsDomain::CutpoolPropagation::updateActivityLbChange( + HighsInt col, double oldbound, double newbound, bool threshold, + bool activity, bool infeasdomain) { + if (!infeasdomain) assert(!domain->infeasible_); - if (newbound < oldbound) { + if (newbound < oldbound && threshold) { cutpool->getMatrix().forEachNegativeColumnEntry( col, [&](HighsInt row, double val) { domain->updateThresholdLbChange(col, newbound, val, @@ -475,38 +518,42 @@ void HighsDomain::CutpoolPropagation::updateActivityLbChange(HighsInt col, }); } - cutpool->getMatrix().forEachPositiveColumnEntry( - col, [&](HighsInt row, double val) { - assert(val > 0); - HighsCDouble deltamin = computeDelta(val, oldbound, newbound, - -kHighsInf, activitycutsinf_[row]); - activitycuts_[row] += deltamin; + if (!activity) return; - if (deltamin <= 0) { - domain->updateThresholdLbChange(col, newbound, val, - capacityThreshold_[row]); - return true; - } + if (!infeasdomain) { + cutpool->getMatrix().forEachPositiveColumnEntry( + col, [&](HighsInt row, double val) { + assert(val > 0); + HighsCDouble deltamin = computeDelta( + val, oldbound, newbound, -kHighsInf, activitycutsinf_[row]); + activitycuts_[row] += deltamin; + + if (deltamin <= 0) { + domain->updateThresholdLbChange(col, newbound, val, + capacityThreshold_[row]); + return true; + } - if (activitycutsinf_[row] == 0 && - activitycuts_[row] - cutpool->getRhs()[row] > - domain->mipsolver->mipdata_->feastol) { - // todo, now that multiple cutpools are possible the index needs to be - // encoded differently - domain->mipsolver->mipdata_->debugSolution.nodePruned(*domain); - domain->infeasible_ = true; - domain->infeasible_pos = domain->domchgstack_.size(); - domain->infeasible_reason = Reason::cut(cutpoolindex, row); - return false; - } + if (activitycutsinf_[row] == 0 && + activitycuts_[row] - cutpool->getRhs()[row] > + domain->mipsolver->mipdata_->feastol) { + // todo, now that multiple cutpools are possible the index needs to + // be encoded differently + domain->mipsolver->mipdata_->debugSolution.nodePruned(*domain); + domain->infeasible_ = true; + domain->infeasible_pos = domain->domchgstack_.size(); + domain->infeasible_reason = Reason::cut(cutpoolindex, row); + return false; + } - markPropagateCut(row); + markPropagateCut(row); - return true; - }); + return true; + }); + } if (domain->infeasible_) { - assert(domain->infeasible_reason.type == cutpoolindex); + // assert(domain->infeasible_reason.type == cutpoolindex); assert(domain->infeasible_reason.index >= 0); std::swap(oldbound, newbound); cutpool->getMatrix().forEachPositiveColumnEntry( @@ -515,19 +562,21 @@ void HighsDomain::CutpoolPropagation::updateActivityLbChange(HighsInt col, activitycuts_[row] += computeDelta(val, oldbound, newbound, -kHighsInf, activitycutsinf_[row]); - if (domain->infeasible_reason.index == row) return false; + if (domain->infeasible_reason.index == row && + domain->infeasible_reason.type == cutpoolindex) + return false; return true; }); } } -void HighsDomain::CutpoolPropagation::updateActivityUbChange(HighsInt col, - double oldbound, - double newbound) { - assert(!domain->infeasible_); +void HighsDomain::CutpoolPropagation::updateActivityUbChange( + HighsInt col, double oldbound, double newbound, bool threshold, + bool activity, bool infeasdomain) { + if (!infeasdomain) assert(!domain->infeasible_); - if (newbound > oldbound) { + if (newbound > oldbound && threshold) { cutpool->getMatrix().forEachPositiveColumnEntry( col, [&](HighsInt row, double val) { domain->updateThresholdUbChange(col, newbound, val, @@ -536,36 +585,43 @@ void HighsDomain::CutpoolPropagation::updateActivityUbChange(HighsInt col, }); } - cutpool->getMatrix().forEachNegativeColumnEntry( - col, [&](HighsInt row, double val) { - assert(val < 0); - HighsCDouble deltamin = computeDelta(val, oldbound, newbound, kHighsInf, - activitycutsinf_[row]); - activitycuts_[row] += deltamin; + if (!activity) return; - if (deltamin <= 0) { - domain->updateThresholdUbChange(col, newbound, val, - capacityThreshold_[row]); - return true; - } + if (!infeasdomain) { + cutpool->getMatrix().forEachNegativeColumnEntry( + col, [&](HighsInt row, double val) { + assert(val < 0); + HighsCDouble deltamin = computeDelta( + val, oldbound, newbound, kHighsInf, activitycutsinf_[row]); - if (activitycutsinf_[row] == 0 && - activitycuts_[row] - cutpool->getRhs()[row] > - domain->mipsolver->mipdata_->feastol) { - domain->mipsolver->mipdata_->debugSolution.nodePruned(*domain); - domain->infeasible_ = true; - domain->infeasible_pos = domain->domchgstack_.size(); - domain->infeasible_reason = Reason::cut(cutpoolindex, row); - return false; - } + // std::cout << activitycuts_.size() << std::endl; + + activitycuts_[row] += deltamin; + + if (deltamin <= 0) { + domain->updateThresholdUbChange(col, newbound, val, + capacityThreshold_[row]); + return true; + } + + if (activitycutsinf_[row] == 0 && + activitycuts_[row] - cutpool->getRhs()[row] > + domain->mipsolver->mipdata_->feastol) { + domain->mipsolver->mipdata_->debugSolution.nodePruned(*domain); + domain->infeasible_ = true; + domain->infeasible_pos = domain->domchgstack_.size(); + domain->infeasible_reason = Reason::cut(cutpoolindex, row); + return false; + } - markPropagateCut(row); + markPropagateCut(row); - return true; - }); + return true; + }); + } if (domain->infeasible_) { - assert(domain->infeasible_reason.type == cutpoolindex); + // assert(domain->infeasible_reason.type == cutpoolindex); assert(domain->infeasible_reason.index >= 0); std::swap(oldbound, newbound); cutpool->getMatrix().forEachNegativeColumnEntry( @@ -574,7 +630,9 @@ void HighsDomain::CutpoolPropagation::updateActivityUbChange(HighsInt col, activitycuts_[row] += computeDelta(val, oldbound, newbound, kHighsInf, activitycutsinf_[row]); - if (domain->infeasible_reason.index == row) return false; + if (domain->infeasible_reason.index == row && + domain->infeasible_reason.type == cutpoolindex) + return false; return true; }); @@ -1586,13 +1644,31 @@ void HighsDomain::updateActivityLbChange(HighsInt col, double oldbound, } } - if (!infeasible_) { - for (CutpoolPropagation& cutpoolprop : cutpoolpropagation) - cutpoolprop.updateActivityLbChange(col, oldbound, newbound); - } else { + if (infeasible_) { assert(infeasible_reason.type == Reason::kModelRowLower || infeasible_reason.type == Reason::kModelRowUpper); assert(infeasible_reason.index == mip->a_matrix_.index_[end - 1]); + } else { + // Explanation: If cutpoolpropagation[i] returns infeasible, + // we still need to update the domain threshold values using + // cutpoolpropagation[j], j > i, and reverse the activity + // changes made by cutpoolpropagation[j] j < i. + HighsInt infeascutpool = -1; + HighsInt ncutpoolprop = static_cast(cutpoolpropagation.size()); + for (HighsInt i = 0; i != ncutpoolprop; ++i) { + if (!infeasible_) { + cutpoolpropagation[i].updateActivityLbChange(col, oldbound, newbound, + true, true, infeasible_); + if (infeasible_) infeascutpool = i; + } else { + cutpoolpropagation[i].updateActivityLbChange(col, oldbound, newbound, + true, false, infeasible_); + } + } + for (HighsInt i = 0; i < infeascutpool; ++i) { + cutpoolpropagation[i].updateActivityLbChange(col, oldbound, newbound, + false, true, infeasible_); + } } if (infeasible_) { @@ -1741,13 +1817,31 @@ void HighsDomain::updateActivityUbChange(HighsInt col, double oldbound, } } - if (!infeasible_) { - for (CutpoolPropagation& cutpoolprop : cutpoolpropagation) - cutpoolprop.updateActivityUbChange(col, oldbound, newbound); - } else { + if (infeasible_) { assert(infeasible_reason.type == Reason::kModelRowLower || infeasible_reason.type == Reason::kModelRowUpper); assert(infeasible_reason.index == mip->a_matrix_.index_[end - 1]); + } else { + // Explanation: If cutpoolpropagation[i] returns infeasible, + // we still need to update the domain threshold values using + // cutpoolpropagation[j], j > i, and reverse the activity + // changes made by cutpoolpropagation[j] j < i. + HighsInt infeascutpool = -1; + HighsInt ncutpoolprop = static_cast(cutpoolpropagation.size()); + for (HighsInt i = 0; i != ncutpoolprop; ++i) { + if (!infeasible_) { + cutpoolpropagation[i].updateActivityUbChange(col, oldbound, newbound, + true, true, infeasible_); + if (infeasible_) infeascutpool = i; + } else { + cutpoolpropagation[i].updateActivityUbChange(col, oldbound, newbound, + true, false, infeasible_); + } + } + for (HighsInt i = 0; i < infeascutpool; ++i) { + cutpoolpropagation[i].updateActivityUbChange(col, oldbound, newbound, + false, true, infeasible_); + } } if (infeasible_) { @@ -2448,7 +2542,8 @@ bool HighsDomain::propagate() { for (HighsInt k = 0; k != numproprows; ++k) { HighsInt i = propagateinds[k]; if (propRowNumChangedBounds_[k].first != 0) { - cutpoolprop.cutpool->resetAge(i); + cutpoolprop.cutpool->resetAge( + i, mipsolver->mipdata_->parallelLockActive()); HighsInt start = cutpoolprop.cutpool->getMatrix().getRowStart(i); HighsInt end = start + propRowNumChangedBounds_[k].first; for (HighsInt j = start; j != end && !infeasible_; ++j) @@ -2489,51 +2584,56 @@ double HighsDomain::getColUpperPos(HighsInt col, HighsInt stackpos, return ub; } -void HighsDomain::conflictAnalysis(HighsConflictPool& conflictPool) { - if (&mipsolver->mipdata_->domain == this) return; - if (mipsolver->mipdata_->domain.infeasible() || !infeasible_) return; +void HighsDomain::conflictAnalysis(HighsConflictPool& conflictPool, + HighsDomain& globaldom, + HighsPseudocost& pseudocost) { + if (&globaldom == this) return; + if (globaldom.infeasible() || !infeasible_) return; - mipsolver->mipdata_->domain.propagate(); - if (mipsolver->mipdata_->domain.infeasible()) return; + // Not sure how this should be modified for the workers. + globaldom.propagate(); + if (globaldom.infeasible()) return; - ConflictSet conflictSet(*this); + ConflictSet conflictSet(*this, globaldom); - conflictSet.conflictAnalysis(conflictPool); + conflictSet.conflictAnalysis(conflictPool, pseudocost); } void HighsDomain::conflictAnalysis(const HighsInt* proofinds, const double* proofvals, HighsInt prooflen, double proofrhs, - HighsConflictPool& conflictPool) { - if (&mipsolver->mipdata_->domain == this) return; + HighsConflictPool& conflictPool, + HighsDomain& globaldom, + HighsPseudocost& pseudocost) { + if (&globaldom == this) return; - if (mipsolver->mipdata_->domain.infeasible()) return; + if (globaldom.infeasible()) return; - mipsolver->mipdata_->domain.propagate(); - if (mipsolver->mipdata_->domain.infeasible()) return; + globaldom.propagate(); + if (globaldom.infeasible()) return; - ConflictSet conflictSet(*this); + ConflictSet conflictSet(*this, globaldom); conflictSet.conflictAnalysis(proofinds, proofvals, prooflen, proofrhs, - conflictPool); + conflictPool, pseudocost); } void HighsDomain::conflictAnalyzeReconvergence( const HighsDomainChange& domchg, const HighsInt* proofinds, const double* proofvals, HighsInt prooflen, double proofrhs, - HighsConflictPool& conflictPool) { - if (&mipsolver->mipdata_->domain == this) return; + HighsConflictPool& conflictPool, HighsDomain& globaldom) { + if (&globaldom == this) return; - if (mipsolver->mipdata_->domain.infeasible()) return; + if (globaldom.infeasible()) return; - mipsolver->mipdata_->domain.propagate(); - if (mipsolver->mipdata_->domain.infeasible()) return; + globaldom.propagate(); + if (globaldom.infeasible()) return; - ConflictSet conflictSet(*this); + ConflictSet conflictSet(*this, globaldom); HighsInt ninfmin; HighsCDouble activitymin; - mipsolver->mipdata_->domain.computeMinActivity( - 0, prooflen, proofinds, proofvals, ninfmin, activitymin); + globaldom.computeMinActivity(0, prooflen, proofinds, proofvals, ninfmin, + activitymin); if (ninfmin != 0) return; if (!conflictSet.explainBoundChangeLeq( @@ -2652,9 +2752,10 @@ HighsDomainChange HighsDomain::flip(const HighsDomainChange& domchg) const { double HighsDomain::feastol() const { return mipsolver->mipdata_->feastol; } -HighsDomain::ConflictSet::ConflictSet(HighsDomain& localdom_) +HighsDomain::ConflictSet::ConflictSet(HighsDomain& localdom_, + const HighsDomain& globaldom_) : localdom(localdom_), - globaldom(localdom.mipsolver->mipdata_->domain), + globaldom(globaldom_), reasonSideFrontier(), reconvergenceFrontier(), resolveQueue(), @@ -3642,13 +3743,16 @@ HighsInt HighsDomain::ConflictSet::resolveDepth(std::set& frontier, for (const LocalDomChg& i : resolvedDomainChanges) { auto insertResult = frontier.insert(i); if (insertResult.second) { - if (increaseConflictScore) { + // TODO MT: Currently this conflict score update is suppressed during + // concurrent search + if (increaseConflictScore && + !localdom.mipsolver->mipdata_->parallelLockActive()) { if (localdom.domchgstack_[i.pos].boundtype == HighsBoundType::kLower) - localdom.mipsolver->mipdata_->pseudocost.increaseConflictScoreUp( - localdom.domchgstack_[i.pos].column); + localdom.mipsolver->mipdata_->getPseudoCost() + .increaseConflictScoreUp(localdom.domchgstack_[i.pos].column); else - localdom.mipsolver->mipdata_->pseudocost.increaseConflictScoreDown( - localdom.domchgstack_[i.pos].column); + localdom.mipsolver->mipdata_->getPseudoCost() + .increaseConflictScoreDown(localdom.domchgstack_[i.pos].column); } if (i.pos >= startPos.pos && resolvable(i.pos)) pushQueue(insertResult.first); @@ -3720,20 +3824,33 @@ HighsInt HighsDomain::ConflictSet::computeCuts( return numConflicts; } -void HighsDomain::ConflictSet::conflictAnalysis( - HighsConflictPool& conflictPool) { +void HighsDomain::ConflictSet::conflictAnalysis(HighsConflictPool& conflictPool, + HighsPseudocost& pseudocost) { resolvedDomainChanges.reserve(localdom.domchgstack_.size()); if (!explainInfeasibility()) return; - localdom.mipsolver->mipdata_->pseudocost.increaseConflictWeight(); + if (!localdom.mipsolver->mipdata_->parallelLockActive()) { + localdom.mipsolver->mipdata_->getPseudoCost().increaseConflictWeight(); + } else { + pseudocost.increaseConflictWeight(); + } for (const LocalDomChg& locdomchg : resolvedDomainChanges) { - if (locdomchg.domchg.boundtype == HighsBoundType::kLower) - localdom.mipsolver->mipdata_->pseudocost.increaseConflictScoreUp( - locdomchg.domchg.column); - else - localdom.mipsolver->mipdata_->pseudocost.increaseConflictScoreDown( - locdomchg.domchg.column); + if (locdomchg.domchg.boundtype == HighsBoundType::kLower) { + if (!localdom.mipsolver->mipdata_->parallelLockActive()) { + localdom.mipsolver->mipdata_->getPseudoCost().increaseConflictScoreUp( + locdomchg.domchg.column); + } else { + pseudocost.increaseConflictScoreUp(locdomchg.domchg.column); + } + } else { + if (!localdom.mipsolver->mipdata_->parallelLockActive()) { + localdom.mipsolver->mipdata_->getPseudoCost().increaseConflictScoreDown( + locdomchg.domchg.column); + } else { + pseudocost.increaseConflictScoreDown(locdomchg.domchg.column); + } + } } if (10 * resolvedDomainChanges.size() > @@ -3785,9 +3902,12 @@ void HighsDomain::ConflictSet::conflictAnalysis( conflictPool.addConflictCut(localdom, reasonSideFrontier); } -void HighsDomain::ConflictSet::conflictAnalysis( - const HighsInt* proofinds, const double* proofvals, HighsInt prooflen, - double proofrhs, HighsConflictPool& conflictPool) { +void HighsDomain::ConflictSet::conflictAnalysis(const HighsInt* proofinds, + const double* proofvals, + HighsInt prooflen, + double proofrhs, + HighsConflictPool& conflictPool, + HighsPseudocost& pseudocost) { resolvedDomainChanges.reserve(localdom.domchgstack_.size()); HighsInt ninfmin; @@ -3800,14 +3920,15 @@ void HighsDomain::ConflictSet::conflictAnalysis( double(activitymin))) return; - localdom.mipsolver->mipdata_->pseudocost.increaseConflictWeight(); + HighsPseudocost& ps = localdom.mipsolver->mipdata_->parallelLockActive() + ? pseudocost + : localdom.mipsolver->mipdata_->getPseudoCost(); + ps.increaseConflictWeight(); for (const LocalDomChg& locdomchg : resolvedDomainChanges) { if (locdomchg.domchg.boundtype == HighsBoundType::kLower) - localdom.mipsolver->mipdata_->pseudocost.increaseConflictScoreUp( - locdomchg.domchg.column); + ps.increaseConflictScoreUp(locdomchg.domchg.column); else - localdom.mipsolver->mipdata_->pseudocost.increaseConflictScoreDown( - locdomchg.domchg.column); + ps.increaseConflictScoreDown(locdomchg.domchg.column); } if (10 * resolvedDomainChanges.size() > diff --git a/highs/mip/HighsDomain.h b/highs/mip/HighsDomain.h index ec16516d3e4..0648524d052 100644 --- a/highs/mip/HighsDomain.h +++ b/highs/mip/HighsDomain.h @@ -14,6 +14,7 @@ #include #include +#include "HighsPseudocost.h" #include "mip/HighsDomainChange.h" #include "mip/HighsMipSolver.h" #include "util/HighsCDouble.h" @@ -61,7 +62,7 @@ class HighsDomain { class ConflictSet { friend class HighsDomain; HighsDomain& localdom; - HighsDomain& globaldom; + const HighsDomain& globaldom; public: struct LocalDomChg { @@ -71,12 +72,14 @@ class HighsDomain { bool operator<(const LocalDomChg& other) const { return pos < other.pos; } }; - ConflictSet(HighsDomain& localdom); + ConflictSet(HighsDomain& localdom, const HighsDomain& globaldom); - void conflictAnalysis(HighsConflictPool& conflictPool); + void conflictAnalysis(HighsConflictPool& conflictPool, + HighsPseudocost& pseudocost); void conflictAnalysis(const HighsInt* proofinds, const double* proofvals, HighsInt prooflen, double proofrhs, - HighsConflictPool& conflictPool); + HighsConflictPool& conflictPool, + HighsPseudocost& pseudocost); private: std::set reasonSideFrontier; @@ -166,6 +169,8 @@ class HighsDomain { CutpoolPropagation(const CutpoolPropagation& other); + CutpoolPropagation& operator=(const CutpoolPropagation& other); + ~CutpoolPropagation(); void recomputeCapacityThreshold(HighsInt cut); @@ -176,9 +181,13 @@ class HighsDomain { void markPropagateCut(HighsInt cut); - void updateActivityLbChange(HighsInt col, double oldbound, double newbound); + void updateActivityLbChange(HighsInt col, double oldbound, double newbound, + bool threshold, bool activity, + bool infeasdomain); - void updateActivityUbChange(HighsInt col, double oldbound, double newbound); + void updateActivityUbChange(HighsInt col, double oldbound, double newbound, + bool threshold, bool activity, + bool infeasdomain); }; struct ConflictPoolPropagation { @@ -203,6 +212,8 @@ class HighsDomain { ConflictPoolPropagation(const ConflictPoolPropagation& other); + ConflictPoolPropagation& operator=(const ConflictPoolPropagation& other); + ~ConflictPoolPropagation(); void linkWatchedLiteral(HighsInt linkPos); @@ -587,17 +598,20 @@ class HighsDomain { double getColUpperPos(HighsInt col, HighsInt stackpos, HighsInt& pos) const; - void conflictAnalysis(HighsConflictPool& conflictPool); + void conflictAnalysis(HighsConflictPool& conflictPool, HighsDomain& globaldom, + HighsPseudocost& pseudocost); void conflictAnalysis(const HighsInt* proofinds, const double* proofvals, HighsInt prooflen, double proofrhs, - HighsConflictPool& conflictPool); + HighsConflictPool& conflictPool, HighsDomain& globaldom, + HighsPseudocost& pseudocost); void conflictAnalyzeReconvergence(const HighsDomainChange& domchg, const HighsInt* proofinds, const double* proofvals, HighsInt prooflen, double proofrhs, - HighsConflictPool& conflictPool); + HighsConflictPool& conflictPool, + HighsDomain& globaldom); void tightenCoefficients(HighsInt* inds, double* vals, HighsInt len, double& rhs) const; diff --git a/highs/mip/HighsImplications.cpp b/highs/mip/HighsImplications.cpp index 18d30c1d69b..906c2349c4e 100644 --- a/highs/mip/HighsImplications.cpp +++ b/highs/mip/HighsImplications.cpp @@ -13,7 +13,7 @@ #include "mip/MipTimer.h" bool HighsImplications::computeImplications(HighsInt col, bool val) { - HighsDomain& globaldomain = mipsolver.mipdata_->domain; + HighsDomain& globaldomain = mipsolver.mipdata_->getDomain(); HighsCliqueTable& cliquetable = mipsolver.mipdata_->cliquetable; globaldomain.propagate(); if (globaldomain.infeasible() || globaldomain.isFixed(col)) return true; @@ -67,8 +67,8 @@ bool HighsImplications::computeImplications(HighsInt col, bool val) { HighsInt stackimplicend = domchgstack.size(); numImplications += stackimplicend; - mipsolver.mipdata_->pseudocost.addInferenceObservation(col, numImplications, - val); + mipsolver.mipdata_->getPseudoCost().addInferenceObservation( + col, numImplications, val); std::vector implics; implics.reserve(numImplications); @@ -155,7 +155,8 @@ static constexpr bool kSkipBadVbds = true; static constexpr bool kUseDualsForBreakingTies = true; std::pair HighsImplications::getBestVub( - HighsInt col, const HighsSolution& lpSolution, double& bestUb) const { + HighsInt col, const HighsSolution& lpSolution, double& bestUb, + const HighsDomain& globaldom) const { std::pair bestVub = std::make_pair(-1, VarBound{0.0, kHighsInf}); double minbestUb = bestUb; @@ -179,8 +180,7 @@ std::pair HighsImplications::getBestVub( return false; }; - double scale = mipsolver.mipdata_->domain.col_upper_[col] - - mipsolver.mipdata_->domain.col_lower_[col]; + double scale = globaldom.col_upper_[col] - globaldom.col_lower_[col]; if (scale == kHighsInf) scale = 1.0; else @@ -188,8 +188,8 @@ std::pair HighsImplications::getBestVub( vubs[col].for_each([&](HighsInt vubCol, const VarBound& vub) { if (vub.coef == kHighsInf) return; - if (mipsolver.mipdata_->domain.isFixed(vubCol)) return; - assert(mipsolver.mipdata_->domain.isBinary(vubCol)); + if (globaldom.isFixed(vubCol)) return; + assert(globaldom.isBinary(vubCol)); double vubval = lpSolution.col_value[vubCol] * vub.coef + vub.constant; double ubDist = std::max(0.0, vubval - lpSolution.col_value[col]); @@ -228,7 +228,8 @@ std::pair HighsImplications::getBestVub( } std::pair HighsImplications::getBestVlb( - HighsInt col, const HighsSolution& lpSolution, double& bestLb) const { + HighsInt col, const HighsSolution& lpSolution, double& bestLb, + const HighsDomain& globaldom) const { std::pair bestVlb = std::make_pair(-1, VarBound{0.0, -kHighsInf}); double maxbestlb = bestLb; @@ -252,8 +253,7 @@ std::pair HighsImplications::getBestVlb( return false; }; - double scale = mipsolver.mipdata_->domain.col_upper_[col] - - mipsolver.mipdata_->domain.col_lower_[col]; + double scale = globaldom.col_upper_[col] - globaldom.col_lower_[col]; if (scale == kHighsInf) scale = 1.0; else @@ -261,8 +261,8 @@ std::pair HighsImplications::getBestVlb( vlbs[col].for_each([&](HighsInt vlbCol, const VarBound& vlb) { if (vlb.coef == -kHighsInf) return; - if (mipsolver.mipdata_->domain.isFixed(vlbCol)) return; - assert(mipsolver.mipdata_->domain.isBinary(vlbCol)); + if (globaldom.isFixed(vlbCol)) return; + assert(globaldom.isBinary(vlbCol)); assert(vlbCol >= 0 && vlbCol < mipsolver.numCol()); double vlbval = lpSolution.col_value[vlbCol] * vlb.coef + vlb.constant; double lbDist = std::max(0.0, lpSolution.col_value[col] - vlbval); @@ -296,7 +296,7 @@ std::pair HighsImplications::getBestVlb( } bool HighsImplications::runProbing(HighsInt col, HighsInt& numReductions) { - HighsDomain& globaldomain = mipsolver.mipdata_->domain; + HighsDomain& globaldomain = mipsolver.mipdata_->getDomain(); if (globaldomain.isBinary(col) && !implicationsCached(col, 1) && !implicationsCached(col, 0) && mipsolver.mipdata_->cliquetable.getSubstitution(col) == nullptr) { @@ -363,7 +363,7 @@ bool HighsImplications::runProbing(HighsInt col, HighsInt& numReductions) { substitutions.push_back(substitution); colsubstituted[implcol] = true; ++numReductions; - } else { + } else if (!mipsolver.mipdata_->parallelLockActive()) { double lb = std::min(lbDown, lbUp); double ub = std::max(ubDown, ubUp); @@ -411,7 +411,7 @@ void HighsImplications::strengthenVarBound(VarBound& vbnd, void HighsImplications::addVUB(HighsInt col, HighsInt vubcol, double vubcoef, double vubconstant) { addVUB(col, vubcol, vubcoef, vubconstant, - mipsolver.mipdata_->domain.col_upper_[col], + mipsolver.mipdata_->getDomain().col_upper_[col], mipsolver.isColIntegral(col)); } @@ -452,7 +452,7 @@ void HighsImplications::addVUB(HighsInt col, HighsInt vubcol, double vubcoef, void HighsImplications::addVLB(HighsInt col, HighsInt vlbcol, double vlbcoef, double vlbconstant) { addVLB(col, vlbcol, vlbcoef, vlbconstant, - mipsolver.mipdata_->domain.col_lower_[col], + mipsolver.mipdata_->getDomain().col_lower_[col], mipsolver.isColIntegral(col)); } @@ -531,7 +531,7 @@ void HighsImplications::rebuild(HighsInt ncols, HighsInt newVubCol = orig2reducedcol[vubCol]; if (newVubCol == -1) return; - if (!mipsolver.mipdata_->domain.isBinary(newVubCol) || + if (!mipsolver.mipdata_->getDomain().isBinary(newVubCol) || !mipsolver.mipdata_->postSolveStack.isColLinearlyTransformable( newVubCol)) return; @@ -543,7 +543,7 @@ void HighsImplications::rebuild(HighsInt ncols, HighsInt newVlbCol = orig2reducedcol[vlbCol]; if (newVlbCol == -1) return; - if (!mipsolver.mipdata_->domain.isBinary(newVlbCol) || + if (!mipsolver.mipdata_->getDomain().isBinary(newVlbCol) || !mipsolver.mipdata_->postSolveStack.isColLinearlyTransformable( newVlbCol)) return; @@ -564,12 +564,12 @@ void HighsImplications::buildFrom(const HighsImplications& init) { for (HighsInt i = 0; i != numcol; ++i) { init.vubs[i].for_each([&](HighsInt vubCol, VarBound vub) { - if (!mipsolver.mipdata_->domain.isBinary(vubCol)) return; + if (!mipsolver.mipdata_->getDomain().isBinary(vubCol)) return; addVUB(i, vubCol, vub.coef, vub.constant); }); init.vlbs[i].for_each([&](HighsInt vlbCol, VarBound vlb) { - if (!mipsolver.mipdata_->domain.isBinary(vlbCol)) return; + if (!mipsolver.mipdata_->getDomain().isBinary(vlbCol)) return; addVLB(i, vlbCol, vlb.coef, vlb.constant); }); @@ -582,9 +582,8 @@ void HighsImplications::buildFrom(const HighsImplications& init) { void HighsImplications::separateImpliedBounds( const HighsLpRelaxation& lpRelaxation, const std::vector& sol, - HighsCutPool& cutpool, double feastol) { - HighsDomain& globaldomain = mipsolver.mipdata_->domain; - + HighsCutPool& cutpool, double feastol, HighsDomain& globaldom, + bool thread_safe) { std::array inds; std::array vals; double rhs; @@ -592,7 +591,7 @@ void HighsImplications::separateImpliedBounds( HighsInt numboundchgs = 0; // first do probing on all candidates that have not been probed yet - if (!mipsolver.mipdata_->cliquetable.isFull()) { + if (!mipsolver.mipdata_->cliquetable.isFull() && !thread_safe) { auto oldNumQueries = mipsolver.mipdata_->cliquetable.numNeighbourhoodQueries; HighsInt oldNumEntries = mipsolver.mipdata_->cliquetable.getNumEntries(); @@ -600,16 +599,16 @@ void HighsImplications::separateImpliedBounds( for (std::pair fracint : lpRelaxation.getFractionalIntegers()) { HighsInt col = fracint.first; - if (globaldomain.col_lower_[col] != 0.0 || - globaldomain.col_upper_[col] != 1.0 || + if (globaldom.col_lower_[col] != 0.0 || + globaldom.col_upper_[col] != 1.0 || (implicationsCached(col, 0) && implicationsCached(col, 1))) continue; - mipsolver.analysis_.mipTimerStart(kMipClockProbingImplications); + mipsolver.profiling_->start(kMipClockProbingImplications); const bool probing_result = runProbing(col, numboundchgs); - mipsolver.analysis_.mipTimerStop(kMipClockProbingImplications); + mipsolver.profiling_->stop(kMipClockProbingImplications); if (probing_result) { - if (globaldomain.infeasible()) return; + if (globaldom.infeasible()) return; } if (mipsolver.mipdata_->cliquetable.isFull()) break; @@ -626,7 +625,9 @@ void HighsImplications::separateImpliedBounds( if (nextCleanupCall < 0) { // HighsInt oldNumEntries = // mipsolver.mipdata_->cliquetable.getNumEntries(); - mipsolver.mipdata_->cliquetable.runCliqueMerging(globaldomain); + if (!mipsolver.mipdata_->parallelLockActive()) + mipsolver.mipdata_->cliquetable.runCliqueMerging(globaldom); + // printf("numEntries: %d, beforeMerging: %d\n", // mipsolver.mipdata_->cliquetable.getNumEntries(), oldNumEntries); nextCleanupCall = @@ -635,22 +636,22 @@ void HighsImplications::separateImpliedBounds( // printf("nextCleanupCall: %d\n", nextCleanupCall); } - mipsolver.mipdata_->cliquetable.numNeighbourhoodQueries = oldNumQueries; + if (!mipsolver.mipdata_->parallelLockActive()) + mipsolver.mipdata_->cliquetable.numNeighbourhoodQueries = oldNumQueries; } for (std::pair fracint : lpRelaxation.getFractionalIntegers()) { HighsInt col = fracint.first; // skip non binary variables - if (globaldomain.col_lower_[col] != 0.0 || - globaldomain.col_upper_[col] != 1.0) + if (globaldom.col_lower_[col] != 0.0 || globaldom.col_upper_[col] != 1.0) continue; bool infeas; if (implicationsCached(col, 1)) { const std::vector& implics = getImplications(col, 1, infeas); - if (globaldomain.infeasible()) return; + if (globaldom.infeasible()) return; if (infeas) { vals[0] = 1.0; @@ -664,27 +665,27 @@ void HighsImplications::separateImpliedBounds( for (HighsInt i = 0; i < nimplics; ++i) { if (implics[i].boundtype == HighsBoundType::kUpper) { if (implics[i].boundval + feastol >= - globaldomain.col_upper_[implics[i].column]) + globaldom.col_upper_[implics[i].column]) continue; vals[0] = 1.0; inds[0] = implics[i].column; vals[1] = - globaldomain.col_upper_[implics[i].column] - implics[i].boundval; + globaldom.col_upper_[implics[i].column] - implics[i].boundval; inds[1] = col; - rhs = globaldomain.col_upper_[implics[i].column]; + rhs = globaldom.col_upper_[implics[i].column]; } else { if (implics[i].boundval - feastol <= - globaldomain.col_lower_[implics[i].column]) + globaldom.col_lower_[implics[i].column]) continue; vals[0] = -1.0; inds[0] = implics[i].column; vals[1] = - implics[i].boundval - globaldomain.col_lower_[implics[i].column]; + implics[i].boundval - globaldom.col_lower_[implics[i].column]; inds[1] = col; - rhs = -globaldomain.col_lower_[implics[i].column]; + rhs = -globaldom.col_lower_[implics[i].column]; } double viol = sol[inds[0]] * vals[0] + sol[inds[1]] * vals[1] - rhs; @@ -701,7 +702,7 @@ void HighsImplications::separateImpliedBounds( if (implicationsCached(col, 0)) { const std::vector& implics = getImplications(col, 0, infeas); - if (globaldomain.infeasible()) return; + if (globaldom.infeasible()) return; if (infeas) { vals[0] = -1.0; @@ -715,24 +716,24 @@ void HighsImplications::separateImpliedBounds( for (HighsInt i = 0; i < nimplics; ++i) { if (implics[i].boundtype == HighsBoundType::kUpper) { if (implics[i].boundval + feastol >= - globaldomain.col_upper_[implics[i].column]) + globaldom.col_upper_[implics[i].column]) continue; vals[0] = 1.0; inds[0] = implics[i].column; vals[1] = - implics[i].boundval - globaldomain.col_upper_[implics[i].column]; + implics[i].boundval - globaldom.col_upper_[implics[i].column]; inds[1] = col; rhs = implics[i].boundval; } else { if (implics[i].boundval - feastol <= - globaldomain.col_lower_[implics[i].column]) + globaldom.col_lower_[implics[i].column]) continue; vals[0] = -1.0; inds[0] = implics[i].column; vals[1] = - globaldomain.col_lower_[implics[i].column] - implics[i].boundval; + globaldom.col_lower_[implics[i].column] - implics[i].boundval; inds[1] = col; rhs = -implics[i].boundval; } @@ -751,8 +752,8 @@ void HighsImplications::separateImpliedBounds( } void HighsImplications::cleanupVarbounds(HighsInt col) { - double ub = mipsolver.mipdata_->domain.col_upper_[col]; - double lb = mipsolver.mipdata_->domain.col_lower_[col]; + double ub = mipsolver.mipdata_->getDomain().col_upper_[col]; + double lb = mipsolver.mipdata_->getDomain().col_lower_[col]; if (ub == lb) { HighsInt numVubs = 0; @@ -825,10 +826,10 @@ void HighsImplications::cleanupVlb(HighsInt col, HighsInt vlbCol, mipsolver.mipdata_->debugSolution.checkVlb(col, vlbCol, vlb.coef, vlb.constant); } else if (allowBoundChanges && minlb > lb + mipsolver.mipdata_->epsilon) { - mipsolver.mipdata_->domain.changeBound(HighsBoundType::kLower, col, - static_cast(minlb), - HighsDomain::Reason::unspecified()); - infeasible = mipsolver.mipdata_->domain.infeasible(); + mipsolver.mipdata_->getDomain().changeBound( + HighsBoundType::kLower, col, static_cast(minlb), + HighsDomain::Reason::unspecified()); + infeasible = mipsolver.mipdata_->getDomain().infeasible(); } } @@ -866,10 +867,10 @@ void HighsImplications::cleanupVub(HighsInt col, HighsInt vubCol, mipsolver.mipdata_->debugSolution.checkVub(col, vubCol, vub.coef, vub.constant); } else if (allowBoundChanges && maxub < ub - mipsolver.mipdata_->epsilon) { - mipsolver.mipdata_->domain.changeBound(HighsBoundType::kUpper, col, - static_cast(maxub), - HighsDomain::Reason::unspecified()); - infeasible = mipsolver.mipdata_->domain.infeasible(); + mipsolver.mipdata_->getDomain().changeBound( + HighsBoundType::kUpper, col, static_cast(maxub), + HighsDomain::Reason::unspecified()); + infeasible = mipsolver.mipdata_->getDomain().infeasible(); } } diff --git a/highs/mip/HighsImplications.h b/highs/mip/HighsImplications.h index 6a7c4121538..a82cc1d1a9b 100644 --- a/highs/mip/HighsImplications.h +++ b/highs/mip/HighsImplications.h @@ -161,11 +161,13 @@ class HighsImplications { std::pair getBestVub(HighsInt col, const HighsSolution& lpSolution, - double& bestUb) const; + double& bestUb, + const HighsDomain& globaldom) const; std::pair getBestVlb(HighsInt col, const HighsSolution& lpSolution, - double& bestLb) const; + double& bestLb, + const HighsDomain& globaldom) const; bool runProbing(HighsInt col, HighsInt& numReductions); @@ -176,7 +178,8 @@ class HighsImplications { void separateImpliedBounds(const HighsLpRelaxation& lpRelaxation, const std::vector& sol, - HighsCutPool& cutpool, double feastol); + HighsCutPool& cutpool, double feastol, + HighsDomain& globaldom, bool thread_safe); void cleanupVarbounds(HighsInt col); diff --git a/highs/mip/HighsLpRelaxation.cpp b/highs/mip/HighsLpRelaxation.cpp index 99736731d44..58dc28d2bbd 100644 --- a/highs/mip/HighsLpRelaxation.cpp +++ b/highs/mip/HighsLpRelaxation.cpp @@ -14,11 +14,17 @@ #include "mip/HighsDomain.h" #include "mip/HighsMipSolver.h" #include "mip/HighsMipSolverData.h" +#include "mip/HighsMipWorker.h" #include "mip/HighsPseudocost.h" #include "mip/MipTimer.h" #include "util/HighsCDouble.h" #include "util/HighsHash.h" +void HighsLpRelaxation::setProfiling(HighsProfiling* profiling) { + assert(profiling); + lpsolver.setProfiling(profiling); +} + void HighsLpRelaxation::getCutPool(HighsInt& num_col, HighsInt& num_cut, std::vector& cut_lower, std::vector& cut_upper, @@ -85,7 +91,7 @@ void HighsLpRelaxation::LpRow::get(const HighsMipSolver& mipsolver, const double*& vals) const { switch (origin) { case kCutPool: - mipsolver.mipdata_->cutpool.getCut(index, len, inds, vals); + mipsolver.mipdata_->cutpools[cutpoolindex].getCut(index, len, inds, vals); break; case kModel: mipsolver.mipdata_->getRow(index, len, inds, vals); @@ -96,7 +102,7 @@ HighsInt HighsLpRelaxation::LpRow::getRowLen( const HighsMipSolver& mipsolver) const { switch (origin) { case kCutPool: - return mipsolver.mipdata_->cutpool.getRowLength(index); + return mipsolver.mipdata_->cutpools[cutpoolindex].getRowLength(index); case kModel: return mipsolver.mipdata_->ARstart_[index + 1] - mipsolver.mipdata_->ARstart_[index]; @@ -110,7 +116,7 @@ bool HighsLpRelaxation::LpRow::isIntegral( const HighsMipSolver& mipsolver) const { switch (origin) { case kCutPool: - return mipsolver.mipdata_->cutpool.cutIsIntegral(index); + return mipsolver.mipdata_->cutpools[cutpoolindex].cutIsIntegral(index); case kModel: return (mipsolver.mipdata_->rowintegral[index] != 0); }; @@ -123,7 +129,7 @@ double HighsLpRelaxation::LpRow::getMaxAbsVal( const HighsMipSolver& mipsolver) const { switch (origin) { case kCutPool: - return mipsolver.mipdata_->cutpool.getMaxAbsCutCoef(index); + return mipsolver.mipdata_->cutpools[cutpoolindex].getMaxAbsCutCoef(index); case kModel: return mipsolver.mipdata_->maxAbsRowCoef[index]; }; @@ -132,29 +138,32 @@ double HighsLpRelaxation::LpRow::getMaxAbsVal( return 0.0; } -double HighsLpRelaxation::slackLower(HighsInt row) const { +double HighsLpRelaxation::slackLower(HighsInt row, + const HighsDomain& globaldom) const { switch (lprows[row].origin) { case LpRow::kCutPool: - return mipsolver.mipdata_->domain.getMinCutActivity( - mipsolver.mipdata_->cutpool, lprows[row].index); + return globaldom.getMinCutActivity( + mipsolver.mipdata_->cutpools[lprows[row].cutpoolindex], + lprows[row].index); case LpRow::kModel: double rowlower = rowLower(row); if (rowlower != -kHighsInf) return rowlower; - return mipsolver.mipdata_->domain.getMinActivity(lprows[row].index); + return globaldom.getMinActivity(lprows[row].index); }; assert(false); return -kHighsInf; } -double HighsLpRelaxation::slackUpper(HighsInt row) const { +double HighsLpRelaxation::slackUpper(HighsInt row, + const HighsDomain& globaldom) const { double rowupper = rowUpper(row); switch (lprows[row].origin) { case LpRow::kCutPool: return rowupper; case LpRow::kModel: if (rowupper != kHighsInf) return rowupper; - return mipsolver.mipdata_->domain.getMaxActivity(lprows[row].index); + return globaldom.getMaxActivity(lprows[row].index); }; assert(false); @@ -168,7 +177,7 @@ double HighsLpRelaxation::slackUpper(HighsInt row) const { // set true when the HighsLpRelaxation instance is created as part of // a new HighsMipSolverData instance HighsLpRelaxation::HighsLpRelaxation(const HighsMipSolver& mipsolver) - : mipsolver(mipsolver) { + : mipsolver(mipsolver), worker_(nullptr) { lpsolver.setOptionValue("output_flag", false); lpsolver.setOptionValue("random_seed", mipsolver.options_mip_->random_seed); // Set primal feasibility tolerance for LP solves according to @@ -206,7 +215,8 @@ HighsLpRelaxation::HighsLpRelaxation(const HighsLpRelaxation& other) objective(other.objective), basischeckpoint(other.basischeckpoint), currentbasisstored(other.currentbasisstored), - adjustSymBranchingCol(other.adjustSymBranchingCol) { + adjustSymBranchingCol(other.adjustSymBranchingCol), + worker_(nullptr) { lpsolver.setOptionValue("output_flag", false); lpsolver.passOptions(other.lpsolver.getOptions()); lpsolver.passModel(other.lpsolver.getLp()); @@ -227,8 +237,10 @@ HighsLpRelaxation::HighsLpRelaxation(const HighsLpRelaxation& other) void HighsLpRelaxation::loadModel() { HighsLp lpmodel = *mipsolver.model_; - lpmodel.col_lower_ = mipsolver.mipdata_->domain.col_lower_; - lpmodel.col_upper_ = mipsolver.mipdata_->domain.col_upper_; + lpmodel.col_lower_ = worker_ ? worker_->getGlobalDomain().col_lower_ + : mipsolver.mipdata_->getDomain().col_lower_; + lpmodel.col_upper_ = worker_ ? worker_->getGlobalDomain().col_upper_ + : mipsolver.mipdata_->getDomain().col_upper_; lpmodel.offset_ = 0; lprows.clear(); lprows.reserve(lpmodel.num_row_); @@ -243,14 +255,15 @@ void HighsLpRelaxation::loadModel() { colUbBuffer.resize(num_col); } -void HighsLpRelaxation::resetToGlobalDomain() { +void HighsLpRelaxation::resetToGlobalDomain(const HighsDomain& globaldom) { lpsolver.changeColsBounds(0, mipsolver.numCol() - 1, - mipsolver.mipdata_->domain.col_lower_.data(), - mipsolver.mipdata_->domain.col_upper_.data()); + globaldom.col_lower_.data(), + globaldom.col_upper_.data()); } -void HighsLpRelaxation::computeBasicDegenerateDuals(double threshold, - HighsDomain* localdom) { +void HighsLpRelaxation::computeBasicDegenerateDuals( + double threshold, HighsDomain& localdom, HighsDomain& globaldom, + HighsConflictPool& conflictpool, bool getdualproof) { if (!lpsolver.hasInvert()) return; HighsInt k = 0; @@ -368,7 +381,7 @@ void HighsLpRelaxation::computeBasicDegenerateDuals(double threshold, if (degenerateColDual < threshold) continue; - if (degenerateColDual == kHighsInf && localdom) { + if (degenerateColDual == kHighsInf && getdualproof) { HighsCDouble rhs = 0; for (HighsInt i = 0; i < row_ep.count; ++i) { HighsInt iRow = row_ep.index[i]; @@ -399,10 +412,10 @@ void HighsLpRelaxation::computeBasicDegenerateDuals(double threshold, domchg.boundval = lp.col_upper_[var]; } - localdom->conflictAnalyzeReconvergence( + localdom.conflictAnalyzeReconvergence( domchg, row_ap.nonzeroinds.data(), dualproofvals.data(), - row_ap.nonzeroinds.size(), double(rhs), - mipsolver.mipdata_->conflictPool); + static_cast(row_ap.nonzeroinds.size()), + static_cast(rhs), conflictpool, globaldom); continue; } @@ -504,7 +517,7 @@ void HighsLpRelaxation::addCuts(HighsCutSet& cutset) { lprows.reserve(lprows.size() + numcuts); for (HighsInt i = 0; i != numcuts; ++i) - lprows.push_back(LpRow::cut(cutset.cutindices[i])); + lprows.push_back(LpRow::cut(cutset.cutindices[i], cutset.cutpools[i])); bool success = lpsolver.addRows(numcuts, cutset.lower_.data(), cutset.upper_.data(), @@ -531,10 +544,37 @@ void HighsLpRelaxation::removeObsoleteRows(bool notifyPool) { if (ndelcuts == 0) deletemask.resize(nlprows); ++ndelcuts; deletemask[i] = 1; - if (notifyPool) mipsolver.mipdata_->cutpool.lpCutRemoved(lprows[i].index); + if (notifyPool) { + mipsolver.mipdata_->cutpools[lprows[i].cutpoolindex].lpCutRemoved( + lprows[i].index, mipsolver.mipdata_->parallelLockActive()); + } + } + } + + removeCuts(ndelcuts, deletemask); +} + +void HighsLpRelaxation::removeWorkerSpecificRows() { + HighsInt nlprows = numRows(); + HighsInt nummodelrows = getNumModelRows(); + std::vector deletemask; + + HighsInt ndelcuts = 0; + for (HighsInt i = nummodelrows; i != nlprows; ++i) { + assert(lprows[i].origin == LpRow::Origin::kCutPool); + if (lprows[i].cutpoolindex > 0) { + if (ndelcuts == 0) deletemask.resize(nlprows); + ++ndelcuts; + deletemask[i] = 1; } } + if (ndelcuts > 0) { + HighsBasis root_basis = mipsolver.mipdata_->firstrootbasis; + root_basis.row_status.resize(numRows(), HighsBasisStatus::kBasic); + getLpSolver().setBasis(root_basis); + } + removeCuts(ndelcuts, deletemask); } @@ -562,17 +602,8 @@ void HighsLpRelaxation::removeCuts(HighsInt ndelcuts, assert(lpsolver.getLp().num_row_ == (HighsInt)lprows.size()); basis.debug_origin_name = "HighsLpRelaxation::removeCuts"; lpsolver.setBasis(basis); + mipsolver.profiling_->solveCall("LP0", mipsolver.submip); lpsolver.optimizeLp(); - if (!mipsolver.submip) { - const HighsSubSolverCallTime& sub_solver_call_time = - lpsolver.getSubSolverCallTime(); - mipsolver.analysis_.addSubSolverCallTime(sub_solver_call_time); - // Go through sub_solver_call_time to update any MIP clocks - const bool valid_basis = true; - const bool use_presolve = false; - mipsolver.analysis_.mipTimerUpdate(sub_solver_call_time, valid_basis, - use_presolve); - } } } @@ -584,8 +615,10 @@ void HighsLpRelaxation::removeCuts() { lpsolver.deleteRows(modelrows, nlprows - 1); for (HighsInt i = modelrows; i != nlprows; ++i) { - if (lprows[i].origin == LpRow::Origin::kCutPool) - mipsolver.mipdata_->cutpool.lpCutRemoved(lprows[i].index); + if (lprows[i].origin == LpRow::Origin::kCutPool) { + mipsolver.mipdata_->cutpools[lprows[i].cutpoolindex].lpCutRemoved( + lprows[i].index, mipsolver.mipdata_->parallelLockActive()); + } } lprows.resize(modelrows); assert(lpsolver.getLp().num_row_ == @@ -630,7 +663,8 @@ void HighsLpRelaxation::performAging(bool deleteRows) { if (ndelcuts == 0) deletemask.resize(nlprows); ++ndelcuts; deletemask[i] = 1; - mipsolver.mipdata_->cutpool.lpCutRemoved(lprows[i].index); + mipsolver.mipdata_->cutpools[lprows[i].cutpoolindex].lpCutRemoved( + lprows[i].index, mipsolver.mipdata_->parallelLockActive()); } } else if (std::abs(lpsolver.getSolution().row_dual[i]) > lpsolver.getOptions().dual_feasibility_tolerance) { @@ -662,9 +696,20 @@ void HighsLpRelaxation::resetAges() { } } +void HighsLpRelaxation::notifyCutPoolsLpCopied(HighsInt n) { + HighsInt nlprows = numRows(); + HighsInt modelrows = mipsolver.numRow(); + for (HighsInt i = modelrows; i != nlprows; ++i) { + if (lprows[i].origin == LpRow::Origin::kCutPool) { + mipsolver.mipdata_->cutpools[lprows[i].cutpoolindex].increaseNumLps( + lprows[i].index, n); + } + } +} + void HighsLpRelaxation::flushDomain(HighsDomain& domain, bool continuous) { if (!domain.getChangedCols().empty()) { - if (&domain == &mipsolver.mipdata_->domain) continuous = true; + if (&domain == &mipsolver.mipdata_->getDomain()) continuous = true; currentbasisstored = false; if (!continuous) domain.removeContinuousChangedCols(); HighsInt numChgCols = domain.getChangedCols().size(); @@ -871,7 +916,7 @@ bool HighsLpRelaxation::computeDualProof(const HighsDomain& globaldomain, mipsolver.mipdata_->debugSolution.checkCut(inds.data(), vals.data(), inds.size(), rhs); - if (extractCliques) + if (extractCliques && !mipsolver.mipdata_->parallelLockActive()) mipsolver.mipdata_->cliquetable.extractCliquesFromCut( mipsolver, inds.data(), vals.data(), inds.size(), rhs); @@ -936,7 +981,10 @@ void HighsLpRelaxation::storeDualInfProof() { for (HighsInt j = 0; j < len; ++j) row_ap.add(inds[j], weight * vals[j]); } - const HighsDomain& globaldomain = mipsolver.mipdata_->domain; + const HighsDomain& globaldomain = + (worker_ && mipsolver.mipdata_->parallelLockActive()) + ? worker_->getGlobalDomain() + : mipsolver.mipdata_->getDomain(); for (HighsInt i : row_ap.getNonzeros()) { double val = row_ap.getValue(i); @@ -981,17 +1029,18 @@ void HighsLpRelaxation::storeDualInfProof() { } dualproofrhs = double(upper); - mipsolver.mipdata_->domain.tightenCoefficients( - dualproofinds.data(), dualproofvals.data(), dualproofinds.size(), - dualproofrhs); + globaldomain.tightenCoefficients(dualproofinds.data(), dualproofvals.data(), + dualproofinds.size(), dualproofrhs); mipsolver.mipdata_->debugSolution.checkCut( dualproofinds.data(), dualproofvals.data(), dualproofinds.size(), dualproofrhs); - mipsolver.mipdata_->cliquetable.extractCliquesFromCut( - mipsolver, dualproofinds.data(), dualproofvals.data(), - dualproofinds.size(), dualproofrhs); + if (!mipsolver.mipdata_->parallelLockActive()) { + mipsolver.mipdata_->cliquetable.extractCliquesFromCut( + mipsolver, dualproofinds.data(), dualproofvals.data(), + dualproofinds.size(), dualproofrhs); + } } void HighsLpRelaxation::storeDualUBProof() { @@ -1000,12 +1049,17 @@ void HighsLpRelaxation::storeDualUBProof() { dualproofinds.clear(); dualproofvals.clear(); - if (lpsolver.getSolution().dual_valid) - hasdualproof = computeDualProof(mipsolver.mipdata_->domain, - mipsolver.mipdata_->upper_limit, - dualproofinds, dualproofvals, dualproofrhs); - else + if (lpsolver.getSolution().dual_valid) { + bool use_worker_info = worker_ && mipsolver.mipdata_->parallelLockActive(); + hasdualproof = + computeDualProof(use_worker_info ? worker_->getGlobalDomain() + : mipsolver.mipdata_->getDomain(), + use_worker_info ? worker_->upper_limit + : mipsolver.mipdata_->upper_limit, + dualproofinds, dualproofvals, dualproofrhs); + } else { hasdualproof = false; + } if (!hasdualproof) dualproofrhs = kHighsInf; } @@ -1077,7 +1131,7 @@ HighsLpRelaxation::Status HighsLpRelaxation::run(bool resolve_on_error) { // lpsolver.setOptionValue("output_flag", true); const bool valid_basis = lpsolver.getBasis().valid; - if (mipsolver.analysis_.analyse_mip_time && !mipsolver.submip && + if (mipsolver.profiling_->mip_ && !mipsolver.submip && !this->solved_first_lp) { highsLogUser(mipsolver.options_mip_->log_options, HighsLogType::kInfo, "MIP-Timing: %11.2g - start first LP solve (with%s basis)\n", @@ -1134,6 +1188,7 @@ HighsLpRelaxation::Status HighsLpRelaxation::run(bool resolve_on_error) { fflush(stdout); exit(1); } + mipsolver.profiling_->solveCall("LP1", mipsolver.submip); callstatus = lpsolver.optimizeLp(); if (ipm_logging) lpsolver.setOptionValue("output_flag", false); if (callstatus == HighsStatus::kError) { @@ -1146,22 +1201,21 @@ HighsLpRelaxation::Status HighsLpRelaxation::run(bool resolve_on_error) { } } if (use_simplex) { + const bool profiling_submip = mipsolver.profiling_->isSubMip(); + mipsolver.profiling_->setSubMip(mipsolver.submip); + if (mipsolver.profiling_->running(kSubSolverSubMip)) + printf( + "HighsLpRelaxation::run Sub-MIP sub-solver clock running on thread " + "%2d and this is %sMIP\n", + int(mipsolver.profiling_->myThread()), + mipsolver.submip ? "sub-" : ""); + mipsolver.profiling_->setSubMip(profiling_submip); + mipsolver.profiling_->solveCall("LP2", mipsolver.submip); callstatus = lpsolver.optimizeLp(); } // Revert the value of lpsolver.options_.solver lpsolver.setOptionValue("solver", solver); - if (!mipsolver.submip) { - const HighsSubSolverCallTime& sub_solver_call_time = - lpsolver.getSubSolverCallTime(); - mipsolver.analysis_.addSubSolverCallTime(sub_solver_call_time); - // Go through sub_solver_call_time to update any MIP clocks - std::string presolve; - lpsolver.getOptionValue("presolve", presolve); - const bool use_presolve = presolve != kHighsOffString; - mipsolver.analysis_.mipTimerUpdate(sub_solver_call_time, valid_basis, - use_presolve); - } - if (mipsolver.analysis_.analyse_mip_time && !mipsolver.submip && + if (mipsolver.profiling_->mip_ && !mipsolver.submip && !this->solved_first_lp) { highsLogUser(mipsolver.options_mip_->log_options, HighsLogType::kInfo, "MIP-Timing: %11.2g - finish first LP solve\n", @@ -1265,9 +1319,15 @@ HighsLpRelaxation::Status HighsLpRelaxation::run(bool resolve_on_error) { HighsLogType::kWarning, "HighsLpRelaxation::run LP is unbounded with no basis, " "but not returning Status::kError\n"); - if (info.primal_solution_status == kSolutionStatusFeasible) - mipsolver.mipdata_->trySolution(lpsolver.getSolution().col_value, - kSolutionSourceUnbounded); + if (info.primal_solution_status == kSolutionStatusFeasible) { + if (!mipsolver.mipdata_->parallelLockActive() || !worker_) { + mipsolver.mipdata_->trySolution(lpsolver.getSolution().col_value, + kSolutionSourceUnbounded); + } else { + worker_->trySolution(lpsolver.getSolution().col_value, + kSolutionSourceUnbounded); + } + } return Status::kUnbounded; case HighsModelStatus::kUnknown: @@ -1296,10 +1356,9 @@ HighsLpRelaxation::Status HighsLpRelaxation::run(bool resolve_on_error) { return Status::kError; case HighsModelStatus::kIterationLimit: { if (!mipsolver.submip && resolve_on_error) { - // printf( - // "error: lpsolver reached iteration limit, resolving with basis " - // "from ipm\n"); + // Highs instantiation Highs ipm; + ipm.setProfiling(mipsolver.profiling_); ipm.setOptionValue("output_flag", false); // check if only root presolve is allowed const bool use_presolve = @@ -1345,6 +1404,7 @@ HighsLpRelaxation::Status HighsLpRelaxation::run(bool resolve_on_error) { (void)output_flag; ipm.setOptionValue("output_flag", !mipsolver.submip); } + mipsolver.profiling_->solveCall("LP3", mipsolver.submip); ipm.optimizeLp(); if (ipm_logging) ipm.setOptionValue("output_flag", false); if (use_hipo && !ipm.getBasis().valid) { @@ -1354,16 +1414,9 @@ HighsLpRelaxation::Status HighsLpRelaxation::run(bool resolve_on_error) { "basis: status = %s Try IPX\n", ipm.modelStatusToString(ipm.getModelStatus()).c_str()); ipm.setOptionValue("solver", kIpxString); + mipsolver.profiling_->solveCall("LP4", mipsolver.submip); ipm.optimizeLp(); } - const HighsSubSolverCallTime& sub_solver_call_time = - ipm.getSubSolverCallTime(); - mipsolver.analysis_.addSubSolverCallTime(sub_solver_call_time); - // Go through sub_solver_call_time to update any MIP clocks - const bool valid_basis = false; - mipsolver.analysis_.mipTimerUpdate(sub_solver_call_time, valid_basis, - use_presolve); - lpsolver.setBasis(ipm.getBasis(), "HighsLpRelaxation::run IPM basis"); return run(false); } @@ -1404,6 +1457,10 @@ HighsLpRelaxation::Status HighsLpRelaxation::resolveLp(HighsDomain* domain) { HighsCDouble objsum = 0; bool roundable = true; + const HighsDomain& globaldom = + (worker_ && mipsolver.mipdata_->parallelLockActive()) + ? worker_->getGlobalDomain() + : mipsolver.mipdata_->getDomain(); for (HighsInt i : mipsolver.mipdata_->integral_cols) { // for the fractionality we assume that LP bounds are not violated @@ -1467,8 +1524,8 @@ HighsLpRelaxation::Status HighsLpRelaxation::resolveLp(HighsDomain* domain) { if (lpsolver.getBasis().col_status[i] == HighsBasisStatus::kBasic) continue; - const double glb = mipsolver.mipdata_->domain.col_lower_[i]; - const double gub = mipsolver.mipdata_->domain.col_upper_[i]; + const double glb = globaldom.col_lower_[i]; + const double gub = globaldom.col_upper_[i]; if (std::min(gub - sol.col_value[i], sol.col_value[i] - glb) <= mipsolver.mipdata_->feastol) @@ -1556,8 +1613,13 @@ HighsLpRelaxation::Status HighsLpRelaxation::resolveLp(HighsDomain* domain) { for (HighsInt i = 0; i != mipsolver.numCol(); ++i) objsum += roundsol[i] * mipsolver.colCost(i); - mipsolver.mipdata_->addIncumbent(roundsol, double(objsum), - kSolutionSourceSolveLp); + if (!mipsolver.mipdata_->parallelLockActive() || !worker_) { + mipsolver.mipdata_->addIncumbent( + roundsol, static_cast(objsum), kSolutionSourceSolveLp); + } else { + worker_->addIncumbent(roundsol, static_cast(objsum), + kSolutionSourceSolveLp); + } objsum = 0; } diff --git a/highs/mip/HighsLpRelaxation.h b/highs/mip/HighsLpRelaxation.h index 15a861a52da..abd7cf021ae 100644 --- a/highs/mip/HighsLpRelaxation.h +++ b/highs/mip/HighsLpRelaxation.h @@ -12,11 +12,13 @@ #include #include "Highs.h" +#include "mip/HighsConflictPool.h" #include "mip/HighsMipSolver.h" class HighsDomain; struct HighsCutSet; class HighsPseudocost; +class HighsMipWorker; class HighsLpRelaxation { public: @@ -41,6 +43,7 @@ class HighsLpRelaxation { Origin origin; HighsInt index; HighsInt age; + HighsInt cutpoolindex; void get(const HighsMipSolver& mipsolver, HighsInt& len, const HighsInt*& inds, const double*& vals) const; @@ -51,8 +54,10 @@ class HighsLpRelaxation { double getMaxAbsVal(const HighsMipSolver& mipsolver) const; - static LpRow cut(HighsInt index) { return LpRow{kCutPool, index, 0}; } - static LpRow model(HighsInt index) { return LpRow{kModel, index, 0}; } + static LpRow cut(HighsInt index, HighsInt cutpoolindex) { + return LpRow{kCutPool, index, 0, cutpoolindex}; + } + static LpRow model(HighsInt index) { return LpRow{kModel, index, 0, -1}; } }; const HighsMipSolver& mipsolver; @@ -82,6 +87,7 @@ class HighsLpRelaxation { Status status; bool adjustSymBranchingCol; bool solved_first_lp; + HighsMipWorker* worker_; void storeDualInfProof(); @@ -94,6 +100,8 @@ class HighsLpRelaxation { HighsLpRelaxation(const HighsLpRelaxation& other); + void setProfiling(HighsProfiling* profiling); + void getCutPool(HighsInt& num_col, HighsInt& num_cut, std::vector& cut_lower, std::vector& cut_upper, @@ -166,10 +174,12 @@ class HighsLpRelaxation { this->adjustSymBranchingCol = adjustSymBranchingCol; } - void resetToGlobalDomain(); + void resetToGlobalDomain(const HighsDomain& globaldom); - void computeBasicDegenerateDuals(double threshold, - HighsDomain* localdom = nullptr); + void computeBasicDegenerateDuals(double threshold, HighsDomain& localdom, + HighsDomain& globaldom, + HighsConflictPool& conflictpol, + bool getdualproof); double getAvgSolveIters() { return avgSolveIters; } @@ -185,9 +195,9 @@ class HighsLpRelaxation { const HighsSolution& getSolution() const { return lpsolver.getSolution(); } - double slackUpper(HighsInt row) const; + double slackUpper(HighsInt row, const HighsDomain& globaldom) const; - double slackLower(HighsInt row) const; + double slackLower(HighsInt row, const HighsDomain& globaldom) const; double rowLower(HighsInt row) const { return lpsolver.getLp().row_lower_[row]; @@ -197,16 +207,16 @@ class HighsLpRelaxation { return lpsolver.getLp().row_upper_[row]; } - double colLower(HighsInt col) const { + double colLower(HighsInt col, const HighsDomain& globaldom) const { return col < lpsolver.getLp().num_col_ ? lpsolver.getLp().col_lower_[col] - : slackLower(col - lpsolver.getLp().num_col_); + : slackLower(col - lpsolver.getLp().num_col_, globaldom); } - double colUpper(HighsInt col) const { + double colUpper(HighsInt col, const HighsDomain& globaldom) const { return col < lpsolver.getLp().num_col_ ? lpsolver.getLp().col_upper_[col] - : slackUpper(col - lpsolver.getLp().num_col_); + : slackUpper(col - lpsolver.getLp().num_col_, globaldom); } bool isColIntegral(HighsInt col) const { @@ -236,6 +246,8 @@ class HighsLpRelaxation { return false; } + void setMipWorker(HighsMipWorker& worker) { worker_ = &worker; }; + double computeBestEstimate(const HighsPseudocost& ps) const; double computeLPDegneracy(const HighsDomain& localdomain) const; @@ -308,8 +320,12 @@ class HighsLpRelaxation { void resetAges(); + void notifyCutPoolsLpCopied(HighsInt n); + void removeObsoleteRows(bool notifyPool = true); + void removeWorkerSpecificRows(); + void removeCuts(HighsInt ndelcuts, std::vector& deletemask); void removeCuts(); diff --git a/highs/mip/HighsMipAnalysis.cpp b/highs/mip/HighsMipAnalysis.cpp deleted file mode 100644 index 2f36e01ec78..00000000000 --- a/highs/mip/HighsMipAnalysis.cpp +++ /dev/null @@ -1,313 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -/* */ -/* This file is part of the HiGHS linear optimization suite */ -/* */ -/* Available as open-source under the MIT License */ -/* */ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -/**@file simplex/HighsMipAnalysis.cpp - * @brief - */ -#include "mip/HighsMipAnalysis.h" - -#include - -#include "mip/HighsSeparator.h" -#include "mip/MipTimer.h" -#include "util/HighsUtils.h" - -const HighsInt check_mip_clock = -4; - -void HighsMipAnalysis::setup(const HighsLp& lp, const HighsOptions& options) { - model_name = lp.model_name_; - setupMipTime(options); -} - -void HighsMipAnalysis::setupMipTime(const HighsOptions& options) { - this->sub_solver_call_time_->initialise(); - analyse_mip_time = kHighsAnalysisLevelMipTime & options.highs_analysis_level; - if (analyse_mip_time) { - HighsTimerClock clock; - clock.timer_pointer_ = timer_; - MipTimer mip_timer; - mip_timer.initialiseMipClocks(clock); - mip_clocks = clock; - sepa_name_clock.push_back( - std::make_pair(kImplboundSepaString, kMipClockImplboundSepa)); - sepa_name_clock.push_back( - std::make_pair(kCliqueSepaString, kMipClockCliqueSepa)); - sepa_name_clock.push_back( - std::make_pair(kTableauSepaString, kMipClockTableauSepa)); - sepa_name_clock.push_back( - std::make_pair(kPathAggrSepaString, kMipClockPathAggrSepa)); - sepa_name_clock.push_back( - std::make_pair(kModKSepaString, kMipClockModKSepa)); - } -} - -void HighsMipAnalysis::mipTimerStart(const HighsInt mip_clock - // , const HighsInt thread_id -) const { - if (!analyse_mip_time) return; - HighsInt highs_timer_clock = mip_clocks.clock_[mip_clock]; - if (highs_timer_clock == check_mip_clock) { - std::string clock_name = - mip_clocks.timer_pointer_->clock_names[check_mip_clock]; - printf("MipTimer: starting clock %d: %s\n", int(check_mip_clock), - clock_name.c_str()); - } - mip_clocks.timer_pointer_->start(highs_timer_clock); -} - -void HighsMipAnalysis::mipTimerStop(const HighsInt mip_clock - // , const HighsInt thread_id -) const { - if (!analyse_mip_time) return; - HighsInt highs_timer_clock = mip_clocks.clock_[mip_clock]; - if (highs_timer_clock == check_mip_clock) { - std::string clock_name = - mip_clocks.timer_pointer_->clock_names[check_mip_clock]; - printf("MipTimer: stopping clock %d: %s\n", int(check_mip_clock), - clock_name.c_str()); - } - mip_clocks.timer_pointer_->stop(highs_timer_clock); -} - -bool HighsMipAnalysis::mipTimerRunning(const HighsInt mip_clock - // , const HighsInt thread_id -) const { - if (!analyse_mip_time) return false; - HighsInt highs_timer_clock = mip_clocks.clock_[mip_clock]; - return mip_clocks.timer_pointer_->running(highs_timer_clock); -} - -double HighsMipAnalysis::mipTimerRead(const HighsInt mip_clock - // , const HighsInt thread_id -) const { - if (!analyse_mip_time) return 0; - HighsInt highs_timer_clock = mip_clocks.clock_[mip_clock]; - return mip_clocks.timer_pointer_->read(highs_timer_clock); -} - -HighsInt HighsMipAnalysis::mipTimerNumCall(const HighsInt mip_clock - // , const HighsInt thread_id -) const { - if (!analyse_mip_time) return 0; - HighsInt highs_timer_clock = mip_clocks.clock_[mip_clock]; - return mip_clocks.timer_pointer_->numCall(highs_timer_clock); -} - -void HighsMipAnalysis::mipTimerAdd(const HighsInt mip_clock, - const HighsInt num_call, const double time - // , const HighsInt thread_id -) const { - if (!analyse_mip_time) return; - if (num_call == 0) { - assert(time == 0); - return; - } - HighsInt highs_timer_clock = mip_clocks.clock_[mip_clock]; - mip_clocks.timer_pointer_->add(highs_timer_clock, num_call, time); -} - -void HighsMipAnalysis::mipTimerUpdate( - const HighsSubSolverCallTime& sub_solver_call_time, const bool valid_basis, - const bool presolve, const bool analytic_centre - // , const HighsInt thread_id -) const { - if (!analyse_mip_time) return; - // If IPM has been run first, then there may be a valid basis for - // simplex - const bool run_ipm = sub_solver_call_time.num_call[kSubSolverHipo] > 0 || - sub_solver_call_time.num_call[kSubSolverIpx] > 0; - if (!run_ipm) { - // If IPM has not been run first, then check that simplex call - // counts are consistent with valid_basis and presolve - if (valid_basis) { - assert(sub_solver_call_time.num_call[kSubSolverDuSimplexNoBasis] == 0); - } else if (!presolve) { - assert(sub_solver_call_time.num_call[kSubSolverDuSimplexBasis] == 0); - } - } else { - // IPM has been run first, then at least one of the simplex call - // counts should be zero - assert(sub_solver_call_time.num_call[kSubSolverDuSimplexBasis] == 0 || - sub_solver_call_time.num_call[kSubSolverDuSimplexNoBasis] == 0); - } - - mipTimerAdd(kMipClockDuSimplexBasisSolveLp, - sub_solver_call_time.num_call[kSubSolverDuSimplexBasis], - sub_solver_call_time.run_time[kSubSolverDuSimplexBasis]); - mipTimerAdd(kMipClockDuSimplexNoBasisSolveLp, - sub_solver_call_time.num_call[kSubSolverDuSimplexNoBasis], - sub_solver_call_time.run_time[kSubSolverDuSimplexNoBasis]); - mipTimerAdd(kMipClockPrSimplexBasisSolveLp, - sub_solver_call_time.num_call[kSubSolverPrSimplexBasis], - sub_solver_call_time.run_time[kSubSolverPrSimplexBasis]); - mipTimerAdd(kMipClockPrSimplexNoBasisSolveLp, - sub_solver_call_time.num_call[kSubSolverPrSimplexNoBasis], - sub_solver_call_time.run_time[kSubSolverPrSimplexNoBasis]); - - if (sub_solver_call_time.num_call[kSubSolverHipo]) { - const HighsInt mip_clock = analytic_centre - ? kMipClockHipoSolveAnalyticCentreLp - : kMipClockHipoSolveLp; - mipTimerAdd(mip_clock, sub_solver_call_time.num_call[kSubSolverHipo], - sub_solver_call_time.run_time[kSubSolverHipo]); - } - if (sub_solver_call_time.num_call[kSubSolverIpx]) { - const HighsInt mip_clock = analytic_centre - ? kMipClockIpxSolveAnalyticCentreLp - : kMipClockIpxSolveLp; - mipTimerAdd(mip_clock, sub_solver_call_time.num_call[kSubSolverIpx], - sub_solver_call_time.run_time[kSubSolverIpx]); - } - assert(sub_solver_call_time.num_call[kSubSolverMip] == 0); - assert(sub_solver_call_time.num_call[kSubSolverPdlp] == 0); - assert(sub_solver_call_time.num_call[kSubSolverQpAsm] == 0); - assert(sub_solver_call_time.num_call[kSubSolverSubMip] == 0); -} - -void HighsMipAnalysis::reportMipSolveLpClock(const bool header) { - if (header) { - printf( - ",simplex time,IPM time,#simplex,#IPM,simplex/total time,IPM/total " - "time,#No basis solve,simplex/#Basis solve,simplex/#No basis solve\n"); - return; - } - if (!analyse_mip_time) return; - double total_time = mip_clocks.timer_pointer_->read(0); - if (total_time < 0.01) return; - HighsInt simplex_basis_solve_iclock = - mip_clocks.clock_[kMipClockDuSimplexBasisSolveLp]; - HighsInt simplex_no_basis_solve_iclock = - mip_clocks.clock_[kMipClockDuSimplexNoBasisSolveLp]; - HighsInt ipm_solve_iclock = mip_clocks.clock_[kMipClockIpxSolveLp]; - // HighsInt num_no_basis_solve = - // mip_clocks.timer_pointer_->clock_num_call[no_basis_solve_iclock]; HighsInt - // num_basis_solve = - // mip_clocks.timer_pointer_->clock_num_call[basis_solve_iclock]; - HighsInt num_simplex_basis_solve = - mip_clocks.timer_pointer_->clock_num_call[simplex_basis_solve_iclock]; - HighsInt num_simplex_no_basis_solve = - mip_clocks.timer_pointer_->clock_num_call[simplex_no_basis_solve_iclock]; - HighsInt num_ipm_solve = - mip_clocks.timer_pointer_->clock_num_call[ipm_solve_iclock]; - HighsInt num_simplex_solve = - num_simplex_basis_solve + num_simplex_no_basis_solve; - // assert(num_no_basis_solve+num_basis_solve == num_simplex_solve); - double simplex_basis_solve_time = - mip_clocks.timer_pointer_->read(simplex_basis_solve_iclock); - double simplex_no_basis_solve_time = - mip_clocks.timer_pointer_->read(simplex_no_basis_solve_iclock); - double simplex_solve_time = - simplex_basis_solve_time + simplex_no_basis_solve_time; - double ipm_solve_time = mip_clocks.timer_pointer_->read(ipm_solve_iclock); - double frac_simplex_solve_time = simplex_solve_time / total_time; - double frac_ipm_solve_time = ipm_solve_time / total_time; - double average_simplex_basis_solve_time = - num_simplex_basis_solve > 0 - ? simplex_basis_solve_time / int(num_simplex_basis_solve) - : 0.0; - double average_simplex_no_basis_solve_time = - num_simplex_no_basis_solve > 0 - ? simplex_no_basis_solve_time / int(num_simplex_no_basis_solve) - : 0.0; - printf(",%11.2g,%11.2g,%d,%d,%11.2g,%11.2g,%d,%11.2g,%11.2g\n", - simplex_solve_time, ipm_solve_time, int(num_simplex_solve), - int(num_ipm_solve), frac_simplex_solve_time, frac_ipm_solve_time, - int(num_simplex_no_basis_solve), average_simplex_basis_solve_time, - average_simplex_no_basis_solve_time); - printf( - "LP solver analysis: %d LP with %d simplex (%11.2g CPU), %d IPM (%11.2g " - "CPU) and %d solved without basis; average simplex solve time " - "(basis/no_basis) = (%11.2g, %11.2g)\n", - int(num_simplex_solve + num_ipm_solve), int(num_simplex_solve), - simplex_solve_time, int(num_ipm_solve), ipm_solve_time, - int(num_simplex_no_basis_solve), average_simplex_basis_solve_time, - average_simplex_no_basis_solve_time); -}; - -void HighsMipAnalysis::reportMipTimer() { - if (!analyse_mip_time) return; - MipTimer mip_timer; - mip_timer.reportMipCoreClock(mip_clocks); - mip_timer.reportMipLevel1Clock(mip_clocks); - mip_timer.reportMipEvaluateRootNodeClock(mip_clocks); - // mip_timer.reportAltEvaluateRootNodeClock(mip_clocks); - // mip_timer.reportMipPresolveClock(mip_clocks); - // mip_timer.reportMipRootSeparationClock(mip_clocks); - // mip_timer.reportMipSearchClock(mip_clocks); - // mip_timer.reportMipDiveClock(mip_clocks); - // mip_timer.reportMipNodeSearchClock(mip_clocks); - // mip_timer.reportMipDivePrimalHeuristicsClock(mip_clocks); - mip_timer.reportMipSubMipSolveClock(mip_clocks); - mip_timer.reportMipSeparationClock(mip_clocks); - mip_timer.reportMipSolveLpClock(mip_clocks); - // mip_timer.csvMipClock(this->model_name, mip_clocks, true, false); - // reportMipSolveLpClock(true); - // - // mip_timer.csvMipClock(this->model_name, mip_clocks, false, false); - // reportMipSolveLpClock(false); - // - // mip_timer.csvEvaluateRootNodeClock(this->model_name, mip_clocks, true, - // true); - // - // mip_timer.csvEvaluateRootNodeClock(this->model_name, mip_clocks, false, - // true); - // - // analyseVectorValues(nullptr, "Node search time", - // HighsInt(node_search_time.size()), node_search_time); - // - // analyseVectorValues(nullptr, "Dive time", HighsInt(dive_time.size()), - // dive_time); - // mip_timer.reportFjClock(this->model_name, mip_clocks); -} - -HighsInt HighsMipAnalysis::getSepaClockIndex(const std::string& name) const { - HighsInt num_sepa_clock = this->sepa_name_clock.size(); - assert(num_sepa_clock > 0); - for (HighsInt iSepaClock = 0; iSepaClock < num_sepa_clock; iSepaClock++) { - if (this->sepa_name_clock[iSepaClock].first == name) - return this->sepa_name_clock[iSepaClock].second; - } - return -1; -} - -void HighsMipAnalysis::addSubSolverCallTime( - const HighsSubSolverCallTime& sub_solver_call_time, - const bool analytic_centre) const { - this->sub_solver_call_time_->add(sub_solver_call_time, analytic_centre); -} - -void HighsMipAnalysis::checkSubSolverCallTime( - const HighsSubSolverCallTime& sub_solver_call_time) { - if (!analyse_mip_time) return; - const bool printf_flag = mip_clocks.timer_pointer_->printf_flag; - bool error = false; - auto check = [&](const HighsInt& sub_solver_clock, - const HighsInt& mip_clock) { - HighsInt sub_solver_num_call = - sub_solver_call_time.num_call[sub_solver_clock]; - HighsInt mip_clock_num_call = - mip_clocks.timer_pointer_->numCall(mip_clocks.clock_[mip_clock]); - const bool ok = sub_solver_num_call == mip_clock_num_call; - if (!ok) { - if (printf_flag) - printf("HighsMipAnalysis::checkSubSolverCallTime: Error for %s\n", - sub_solver_call_time.name[sub_solver_clock].c_str()); - error = true; - } - }; - check(kSubSolverDuSimplexBasis, kMipClockDuSimplexBasisSolveLp); - check(kSubSolverDuSimplexNoBasis, kMipClockDuSimplexNoBasisSolveLp); - check(kSubSolverHipo, kMipClockHipoSolveLp); - check(kSubSolverIpx, kMipClockIpxSolveLp); - check(kSubSolverHipoAc, kMipClockHipoSolveAnalyticCentreLp); - check(kSubSolverIpxAc, kMipClockIpxSolveAnalyticCentreLp); - check(kSubSolverSubMip, kMipClockSubMipSolve); - if (printf_flag) - printf("\nHighsMipAnalysis::checkSubSolverCallTime: %s\n", - error ? "ERROR!" : "OK"); - assert(!error); -} diff --git a/highs/mip/HighsMipAnalysis.h b/highs/mip/HighsMipAnalysis.h deleted file mode 100644 index c7709bdb91e..00000000000 --- a/highs/mip/HighsMipAnalysis.h +++ /dev/null @@ -1,71 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -/* */ -/* This file is part of the HiGHS linear optimization suite */ -/* */ -/* Available as open-source under the MIT License */ -/* */ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -/**@file mip/HighsMipAnalysis.h - * @brief Analyse MIP iterations, both for run-time control and data - * gathering - */ -#ifndef MIP_HIGHSMIPANALYSIS_H_ -#define MIP_HIGHSMIPANALYSIS_H_ - -#include "lp_data/HighsAnalysis.h" -#include "lp_data/HighsLp.h" -#include "util/HighsTimer.h" - -class HighsMipAnalysis { - public: - HighsMipAnalysis() - : timer_(nullptr), - sub_solver_call_time_(nullptr), - analyse_mip_time(false) {} - - HighsTimer* timer_; - HighsSubSolverCallTime* sub_solver_call_time_; - void setup(const HighsLp& lp, const HighsOptions& options); - - void setupMipTime(const HighsOptions& options); - void mipTimerStart(const HighsInt mip_clock = 0 - // , const HighsInt thread_id = 0 - ) const; - void mipTimerStop(const HighsInt mip_clock = 0 - // , const HighsInt thread_id = 0 - ) const; - bool mipTimerRunning(const HighsInt mip_clock = 0 - // , const HighsInt thread_id = 0 - ) const; - double mipTimerRead(const HighsInt mip_clock = 0 - // , const HighsInt thread_id = 0 - ) const; - HighsInt mipTimerNumCall(const HighsInt mip_clock = 0 - // , const HighsInt thread_id - ) const; - void mipTimerAdd(const HighsInt mip_clock, const HighsInt num_call, - const double time - // , const HighsInt thread_id - ) const; - void mipTimerUpdate(const HighsSubSolverCallTime& sub_solver_call_time, - const bool valid_basis, const bool presolve, - const bool analytic_centre = false - // , const HighsInt thread_id - ) const; - void reportMipSolveLpClock(const bool header); - void reportMipTimer(); - - HighsInt getSepaClockIndex(const std::string& name) const; - void addSubSolverCallTime(const HighsSubSolverCallTime& sub_solver_call_time, - const bool analytic_centre = false) const; - void checkSubSolverCallTime( - const HighsSubSolverCallTime& sub_solver_call_time); - std::string model_name; - HighsTimerClock mip_clocks; - bool analyse_mip_time; - std::vector dive_time; - std::vector node_search_time; - std::vector> sepa_name_clock; -}; - -#endif /* MIP_HIGHSMIPANALYSIS_H_ */ diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index 64fa53e071e..0c63f593c0f 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -15,6 +15,7 @@ #include "mip/HighsImplications.h" #include "mip/HighsLpRelaxation.h" #include "mip/HighsMipSolverData.h" +#include "mip/HighsMipWorker.h" #include "mip/HighsPseudocost.h" #include "mip/HighsSearch.h" #include "mip/HighsSeparation.h" @@ -70,39 +71,60 @@ HighsMipSolver::HighsMipSolver(HighsCallback& callback, HighsMipSolver::~HighsMipSolver() = default; -void HighsMipSolver::run() { - modelstatus_ = HighsModelStatus::kNotset; - - if (submip) { - analysis_.analyse_mip_time = false; - } else { - analysis_.timer_ = &this->timer_; - analysis_.sub_solver_call_time_ = &this->sub_solver_call_time_; - analysis_.setup(*orig_model_, *options_mip_); +template +void HighsMipSolver::runTask(F&& f, highs::parallel::TaskGroup& tg, + bool parallel_lock, bool force_serial, + const std::vector& indices) { + if (indices.empty()) return; + setParallelLock(parallel_lock); + const bool spawn_tasks = !force_serial && indices.size() > 1 && + !options_mip_->mip_search_simulate_concurrency; + for (HighsInt i : indices) { + if (spawn_tasks) { + tg.spawn([&f, i] { f(i); }); + } else { + f(i); + } } - timer_.start(); + if (spawn_tasks) { + tg.taskWait(); + } + setParallelLock(false); +} +void HighsMipSolver::run() { + modelstatus_ = HighsModelStatus::kNotset; + max_submip_level = std::max(submip_level, max_submip_level); + // Start the timer local to HighsMipSolver - independent of the + // timer passed from Highs as a pointer that's used in + // HighsProfiling + this->timer_.start(); improving_solution_file_ = nullptr; if (!submip && options_mip_->mip_improving_solution_file != "") improving_solution_file_ = fopen(options_mip_->mip_improving_solution_file.c_str(), "w"); mipdata_ = decltype(mipdata_)(new HighsMipSolverData(*this)); - analysis_.mipTimerStart(kMipClockPresolve); - analysis_.mipTimerStart(kMipClockInit); + for (HighsInt iLp = 0; iLp < static_cast(mipdata_->lps.size()); + iLp++) + mipdata_->lps[iLp].setProfiling(this->profiling_); + assert(profiling_); + // The solve time clock shouldn't be running on entry + assert(!profiling_->running(kSolveTime)); + profiling_->start(kPresolveTime); + profiling_->start(kMipClockInit); mipdata_->init(); - analysis_.mipTimerStop(kMipClockInit); #ifdef HIGHS_DEBUGSOL mipdata_->debugSolution.activate(); bool debugSolActive = false; std::swap(mipdata_->debugSolution.debugSolActive, debugSolActive); #endif - analysis_.mipTimerStart(kMipClockRunPresolve); + profiling_->stop(kMipClockInit); + profiling_->start(kMipClockRunPresolve); mipdata_->runMipPresolve(options_mip_->presolve_reduction_limit); - analysis_.mipTimerStop(kMipClockRunPresolve); - analysis_.mipTimerStop(kMipClockPresolve); - - if (analysis_.analyse_mip_time && !submip) + profiling_->stop(kMipClockRunPresolve); + profiling_->stop(kPresolveTime); + if (profiling_->mip_ && !submip) highsLogUser(options_mip_->log_options, HighsLogType::kInfo, "MIP-Timing: %11.2g - completed presolve\n", timer_.read()); // Identify whether time limit has been reached (in presolve) @@ -124,20 +146,32 @@ void HighsMipSolver::run() { return; } - analysis_.mipTimerStart(kMipClockSolve); - - if (analysis_.analyse_mip_time && !submip) + profiling_->start(kSolveTime); + if (profiling_->mip_ && !submip) highsLogUser(options_mip_->log_options, HighsLogType::kInfo, "MIP-Timing: %11.2g - starting setup\n", timer_.read()); - analysis_.mipTimerStart(kMipClockRunSetup); + profiling_->start(kMipClockRunSetup); #ifdef HIGHS_DEBUGSOL mipdata_->debugSolution.debugSolActive = debugSolActive; #endif mipdata_->runSetup(); - analysis_.mipTimerStop(kMipClockRunSetup); - if (analysis_.analyse_mip_time && !submip) + profiling_->stop(kMipClockRunSetup); + if (profiling_->mip_ && !submip) highsLogUser(options_mip_->log_options, HighsLogType::kInfo, "MIP-Timing: %11.2g - completed setup\n", timer_.read()); + + if (mipdata_->getDomain().infeasible()) { + cleanupSolve(); + return; + } + // Initialise master worker. + mipdata_->workers.emplace_back( + *this, &mipdata_->getLp(), &mipdata_->getDomain(), + &mipdata_->getCutPool(), &mipdata_->getConflictPool(), + &mipdata_->getPseudoCost()); + + HighsMipWorker& master_worker = mipdata_->workers[0]; + restart: if (modelstatus_ == HighsModelStatus::kNotset) { // Check limits have not been reached before evaluating root node @@ -151,9 +185,9 @@ void HighsMipSolver::run() { solution_objective_, kExternalMipSolutionQueryOriginAfterSetup); // Apply the trivial heuristics - analysis_.mipTimerStart(kMipClockTrivialHeuristics); + profiling_->start(kMipClockTrivialHeuristics); HighsModelStatus returned_model_status = mipdata_->trivialHeuristics(); - analysis_.mipTimerStop(kMipClockTrivialHeuristics); + profiling_->stop(kMipClockTrivialHeuristics); if (modelstatus_ == HighsModelStatus::kNotset && returned_model_status == HighsModelStatus::kInfeasible) { // trivialHeuristics can spot trivial infeasibility, so act on it @@ -163,9 +197,9 @@ void HighsMipSolver::run() { } // Apply the feasibility jump heuristic (if enabled) if (options_mip_->mip_heuristic_run_feasibility_jump) { - analysis_.mipTimerStart(kMipClockFeasibilityJump); + profiling_->start(kMipClockFeasibilityJump); HighsModelStatus returned_model_status = mipdata_->feasibilityJump(); - analysis_.mipTimerStop(kMipClockFeasibilityJump); + profiling_->stop(kMipClockFeasibilityJump); if (modelstatus_ == HighsModelStatus::kNotset && returned_model_status == HighsModelStatus::kInfeasible) { // feasibilityJump can spot trivial infeasibility, so act on it @@ -175,54 +209,256 @@ void HighsMipSolver::run() { } } // End of pre-root-node heuristics - if (analysis_.analyse_mip_time && !submip) + if (profiling_->mip_ && !submip) highsLogUser(options_mip_->log_options, HighsLogType::kInfo, "MIP-Timing: %11.2g - starting evaluate root node\n", timer_.read()); - analysis_.mipTimerStart(kMipClockEvaluateRootNode); - mipdata_->evaluateRootNode(); - analysis_.mipTimerStop(kMipClockEvaluateRootNode); + profiling_->start(kMipClockEvaluateRootNode); + mipdata_->evaluateRootNode(master_worker); + profiling_->stop(kMipClockEvaluateRootNode); if (this->terminate()) { modelstatus_ = this->terminationStatus(); cleanupSolve(); return; } - // Sometimes the analytic centre calculation is not completed when - // evaluateRootNode returns, so stop its clock if it's running - if (analysis_.analyse_mip_time && - analysis_.mipTimerRunning(kMipClockIpxSolveLp)) - analysis_.mipTimerStop(kMipClockIpxSolveLp); - if (analysis_.analyse_mip_time && !submip) + if (profiling_->mip_ && !submip) highsLogUser(options_mip_->log_options, HighsLogType::kInfo, "MIP-Timing: %11.2g - completed evaluate root node\n", timer_.read()); // age 5 times to remove stored but never violated cuts after root // separation - analysis_.mipTimerStart(kMipClockPerformAging0); - mipdata_->cutpool.performAging(); - mipdata_->cutpool.performAging(); - mipdata_->cutpool.performAging(); - mipdata_->cutpool.performAging(); - mipdata_->cutpool.performAging(); - analysis_.mipTimerStop(kMipClockPerformAging0); + profiling_->start(kMipClockPerformAging0); + mipdata_->getCutPool().performAging(); + mipdata_->getCutPool().performAging(); + mipdata_->getCutPool().performAging(); + mipdata_->getCutPool().performAging(); + mipdata_->getCutPool().performAging(); + profiling_->stop(kMipClockPerformAging0); } if (mipdata_->nodequeue.empty() || mipdata_->checkLimits()) { cleanupSolve(); return; } - std::shared_ptr basis; - HighsSearch search{*this, mipdata_->pseudocost}; - mipdata_->debugSolution.registerDomain(search.getLocalDomain()); - HighsSeparation sepa(*this); + mipdata_->updateLowerBound(mipdata_->nodequeue.getBestLowerBound()); + mipdata_->printDisplayLine(); - search.setLpRelaxation(&mipdata_->lp); - sepa.setLpRelaxation(&mipdata_->lp); + // Calculate maximum number of workers + const HighsInt mip_search_concurrency = options_mip_->mip_search_concurrency; + const HighsInt max_num_workers = + highs::parallel::num_threads() == 1 || mip_search_concurrency <= 0 || + submip + ? 1 + : mip_search_concurrency * highs::parallel::num_threads(); + HighsInt num_workers = 1; + highs::parallel::TaskGroup tg; + + auto destroyOldWorkers = [&]() { + if (mipdata_->workers.size() <= 1) return; + while (mipdata_->domains.size() > 1) { + mipdata_->domains.pop_back(); + } + while (mipdata_->lps.size() > 1) { + mipdata_->lps.pop_back(); + } + while (mipdata_->pseudocosts.size() > 1) { + mipdata_->pseudocosts.pop_back(); + } + while (mipdata_->workers.size() > 1) { + mipdata_->workers.pop_back(); + } + while (mipdata_->cutpools.size() > 1) { + mipdata_->cutpools.pop_back(); + } + while (mipdata_->conflictpools.size() > 1) { + mipdata_->conflictpools.pop_back(); + } + }; + + auto createNewWorkers = [&](HighsInt num_new_workers) { + if (num_new_workers <= 0) return; + // Remove all cuts from non-global pool for copied LP + mipdata_->lps.emplace_back(mipdata_->getLp()); + // Have to set the global sub-solver call time pointer here for + // new worker 0, since + // HighsLpRelaxation::removeWorkerSpecificRows(); solves an LP + mipdata_->lps.back().setProfiling(this->profiling_); + mipdata_->lps.back().removeWorkerSpecificRows(); + for (HighsInt i = 0; i != num_new_workers; ++i) { + if (i != 0) { + mipdata_->lps.emplace_back(mipdata_->lps.back()); + mipdata_->lps.back().setProfiling(this->profiling_); + } + mipdata_->domains.emplace_back(mipdata_->getDomain()); + mipdata_->cutpools.emplace_back( + numCol(), options_mip_->mip_pool_age_limit, + options_mip_->mip_pool_soft_limit, mipdata_->cutpools.size()); + mipdata_->conflictpools.emplace_back(5 * options_mip_->mip_pool_age_limit, + options_mip_->mip_pool_soft_limit); + mipdata_->domains.back().addCutpool(mipdata_->cutpools.back()); + assert(mipdata_->domains.back().getDomainChangeStack().empty()); + mipdata_->domains.back().addConflictPool(mipdata_->conflictpools.back()); + mipdata_->pseudocosts.emplace_back(*this); + mipdata_->workers.emplace_back( + *this, &mipdata_->lps.back(), &mipdata_->domains.back(), + &mipdata_->cutpools.back(), &mipdata_->conflictpools.back(), + &mipdata_->pseudocosts.back()); + mipdata_->lps.back().setMipWorker(mipdata_->workers.back()); + 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()); + } + }; + + // Use case: Change pointers in master worker to local copies of global info + auto constructAdditionalWorkerData = [&](HighsMipWorker& worker) { + assert(mipdata_->cutpools.size() == 1 && + mipdata_->conflictpools.size() == 1); + assert(&worker == &mipdata_->workers[0]); + mipdata_->cutpools.emplace_back(numCol(), options_mip_->mip_pool_age_limit, + options_mip_->mip_pool_soft_limit, 1); + worker.setCutPool(&mipdata_->cutpools.back()); + mipdata_->conflictpools.emplace_back(5 * options_mip_->mip_pool_age_limit, + options_mip_->mip_pool_soft_limit); + worker.setConflictPool(&mipdata_->conflictpools.back()); + mipdata_->domains.emplace_back(mipdata_->getDomain()); + worker.setGlobalDomain(&mipdata_->domains.back()); + worker.getGlobalDomain().addCutpool(worker.getCutPool()); + assert(worker.getGlobalDomain().getDomainChangeStack().empty()); + worker.getGlobalDomain().addConflictPool(worker.getConflictPool()); + mipdata_->pseudocosts.emplace_back(*this); + worker.setPseudocost(&mipdata_->pseudocosts.back()); + worker.getLpRelaxation().setMipWorker(worker); + worker.resetSearch(); + worker.resetSepa(); + worker.nodequeue.clear(); + worker.nodequeue.setNumCol(numCol()); + }; + + auto syncSolutions = [&]() -> void { + // Note: Upper bound / limit of workers updated via addIncumbent + for (HighsMipWorker& worker : mipdata_->workers) { + for (auto& sol : worker.solutions_) { + mipdata_->addIncumbent(std::get<0>(sol), std::get<1>(sol), + std::get<2>(sol)); + } + worker.solutions_.clear(); + } + }; - mipdata_->updateLowerBound(mipdata_->nodequeue.getBestLowerBound()); + auto syncPools = [&](std::vector& indices) -> void { + if (!mipdata_->hasMultipleWorkers() || mipdata_->parallelLockActive()) + return; + for (const HighsInt i : indices) { + mipdata_->workers[i].getConflictPool().syncConflictPool( + mipdata_->getConflictPool()); + mipdata_->workers[i].getCutPool().syncCutPool(*this, + mipdata_->getCutPool()); + } + mipdata_->getCutPool().performAging(); + mipdata_->getConflictPool().performAging(); + }; + + auto syncGlobalDomain = [&](std::vector& indices) -> void { + if (!mipdata_->hasMultipleWorkers()) return; + for (const HighsInt i : indices) { + HighsMipWorker& worker = mipdata_->workers[i]; + const auto& domchgstack = worker.getGlobalDomain().getDomainChangeStack(); + for (const HighsDomainChange& domchg : domchgstack) { + if ((domchg.boundtype == HighsBoundType::kLower && + domchg.boundval > + mipdata_->getDomain().col_lower_[domchg.column]) || + (domchg.boundtype == HighsBoundType::kUpper && + domchg.boundval < + mipdata_->getDomain().col_upper_[domchg.column])) { + mipdata_->getDomain().changeBound(domchg, + HighsDomain::Reason::unspecified()); + } + } + } + }; + + auto doResetWorkerDomain = [&](HighsInt i) { + HighsMipWorker& worker = mipdata_->workers[i]; + for (const HighsDomainChange& domchg : + mipdata_->getDomain().getDomainChangeStack()) { + worker.getGlobalDomain().changeBound(domchg, + HighsDomain::Reason::unspecified()); + } +#ifndef NDEBUG + for (HighsInt col = 0; col < numCol(); ++col) { + assert(mipdata_->getDomain().col_lower_[col] == + worker.getGlobalDomain().col_lower_[col]); + assert(mipdata_->getDomain().col_upper_[col] == + worker.getGlobalDomain().col_upper_[col]); + } +#endif + worker.getGlobalDomain().setDomainChangeStack( + std::vector()); + // Warning: Resetting local domain cannot be done in parallel (changes + // propagationDomains of main pool) + worker.search_ptr_->resetLocalDomain(); + worker.getGlobalDomain().clearChangedCols(); + }; + + auto resetGlobalDomain = [&](bool force, bool resetWorkers) -> void { + // if global propagation found bound changes, we update the domain + if (!mipdata_->getDomain().getChangedCols().empty() || force) { + profiling_->start(kMipClockUpdateLocalDomain); + highsLogDev(options_mip_->log_options, HighsLogType::kInfo, + "added %" HIGHSINT_FORMAT " global bound changes\n", + (HighsInt)mipdata_->getDomain().getChangedCols().size()); + mipdata_->cliquetable.cleanupFixed(mipdata_->getDomain()); + if (mipdata_->hasMultipleWorkers() && resetWorkers) { + // Sync worker domains here. cleanupFixed might have found extra changes + std::vector indices(num_workers); + std::iota(indices.begin(), indices.end(), 0); + runTask(doResetWorkerDomain, tg, false, true, indices); + } + for (const HighsInt col : mipdata_->getDomain().getChangedCols()) + mipdata_->implications.cleanupVarbounds(col); - mipdata_->printDisplayLine(); - search.installNode(mipdata_->nodequeue.popBestBoundNode()); + mipdata_->getDomain().setDomainChangeStack( + std::vector()); + if (!mipdata_->hasMultipleWorkers()) + master_worker.search_ptr_->resetLocalDomain(); + mipdata_->getDomain().clearChangedCols(); + mipdata_->removeFixedIndices(); + profiling_->stop(kMipClockUpdateLocalDomain); + } + }; + + auto syncGlobalPseudoCost = [&]() -> void { + if (!mipdata_->hasMultipleWorkers()) return; + for (HighsMipWorker& worker : mipdata_->workers) { + mipdata_->getPseudoCost().flushPseudoCost(worker.getPseudocost()); + } + }; + + auto resetWorkerPseudoCosts = [&](std::vector& indices) { + if (!mipdata_->hasMultipleWorkers()) return; + auto doResetWorkerPseudoCost = [&](HighsInt i) -> void { + mipdata_->getPseudoCost().syncPseudoCost( + mipdata_->workers[i].getPseudocost()); + }; + runTask(doResetWorkerPseudoCost, tg, false, false, indices); + }; + + 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; + destroyOldWorkers(); + mipdata_->debugSolution.registerDomain( + master_worker.search_ptr_->getLocalDomain()); + + profiling_->start(kMipClockSearch); int64_t numStallNodes = 0; int64_t lastLbLeave = 0; int64_t numQueueLeaves = 0; @@ -232,184 +468,292 @@ void HighsMipSolver::run() { double treeweightLastCheck = 0.0; double upperLimLastCheck = mipdata_->upper_limit; double lowerBoundLastCheck = mipdata_->lower_bound; - analysis_.mipTimerStart(kMipClockSearch); - while (search.hasNode()) { - // Possibly query existence of an external solution - if (!submip) - mipdata_->queryExternalSolution( - solution_objective_, kExternalMipSolutionQueryOriginBeforeDive); - - analysis_.mipTimerStart(kMipClockPerformAging1); - mipdata_->conflictPool.performAging(); - analysis_.mipTimerStop(kMipClockPerformAging1); - // set iteration limit for each lp solve during the dive to 10 times the - // average nodes - - HighsInt iterlimit = 10 * std::max(mipdata_->lp.getAvgSolveIters(), - mipdata_->avgrootlpiters); - iterlimit = std::max({HighsInt{10000}, iterlimit, - HighsInt((3 * mipdata_->firstrootlpiters) / 2)}); - - mipdata_->lp.setIterationLimit(iterlimit); - // perform the dive and put the open nodes to the queue - size_t plungestart = mipdata_->num_nodes; - bool limit_reached = false; - bool considerHeuristics = true; - analysis_.mipTimerStart(kMipClockDive); - while (true) { - // Possibly apply primal heuristics - if (considerHeuristics && mipdata_->moreHeuristicsAllowed()) { - analysis_.mipTimerStart(kMipClockDiveEvaluateNode); - const HighsSearch::NodeResult evaluate_node_result = - search.evaluateNode(); - analysis_.mipTimerStop(kMipClockDiveEvaluateNode); - - if (evaluate_node_result == HighsSearch::NodeResult::kSubOptimal) break; - - if (search.currentNodePruned()) { - ++mipdata_->num_leaves; - search.flushStatistics(); - } else { - analysis_.mipTimerStart(kMipClockDivePrimalHeuristics); - if (mipdata_->incumbent.empty()) { - analysis_.mipTimerStart(kMipClockDiveRandomizedRounding); - mipdata_->heuristics.randomizedRounding( - mipdata_->lp.getLpSolver().getSolution().col_value); - analysis_.mipTimerStop(kMipClockDiveRandomizedRounding); - } - - if (mipdata_->incumbent.empty()) { - if (options_mip_->mip_heuristic_run_rens) { - analysis_.mipTimerStart(kMipClockDiveRens); - mipdata_->heuristics.RENS( - mipdata_->lp.getLpSolver().getSolution().col_value); - analysis_.mipTimerStop(kMipClockDiveRens); - } - } else { - if (options_mip_->mip_heuristic_run_rins) { - analysis_.mipTimerStart(kMipClockDiveRins); - mipdata_->heuristics.RINS( - mipdata_->lp.getLpSolver().getSolution().col_value); - analysis_.mipTimerStop(kMipClockDiveRins); - } - } - - mipdata_->heuristics.flushStatistics(); - analysis_.mipTimerStop(kMipClockDivePrimalHeuristics); - if (mipdata_->terminatorTerminated()) { - cleanupSolve(); - return; - } - } + auto nodesInstalled = [&]() -> bool { + for (HighsMipWorker& worker : mipdata_->workers) { + if (worker.search_ptr_->hasNode()) return true; + } + return false; + }; + + auto getSearchIndicesWithNoNodes = [&](std::vector& indices) { + indices.clear(); + const HighsInt heuristic_allowance_mod = 4; + HighsInt heuristic_random_mod_index = + mipdata_->heuristics.getHeuristicRandom(mipdata_->workers.size()) % + heuristic_allowance_mod; + for (HighsInt i = 0; + i != num_workers && i != mipdata_->nodequeue.numActiveNodes(); i++) { + if (!mipdata_->workers[i].search_ptr_->hasNode()) { + indices.emplace_back(i); + mipdata_->workers[i].setAllowHeuristics(i % heuristic_allowance_mod == + heuristic_random_mod_index); } + } + }; - considerHeuristics = false; - - if (mipdata_->domain.infeasible()) break; - - if (!search.currentNodePruned()) { - double this_dive_time = -analysis_.mipTimerRead(kMipClockTheDive); - analysis_.mipTimerStart(kMipClockTheDive); - const HighsSearch::NodeResult search_dive_result = search.dive(); - analysis_.mipTimerStop(kMipClockTheDive); - if (analysis_.analyse_mip_time) { - this_dive_time += analysis_.mipTimerRead(kMipClockTheDive); - analysis_.dive_time.push_back(this_dive_time); - } - if (search_dive_result == HighsSearch::NodeResult::kSubOptimal) break; - - ++mipdata_->num_leaves; - - search.flushStatistics(); + auto installNodes = [&](std::vector& indices, + bool& limit_reached) -> void { + for (const HighsInt i : indices) { + if (numQueueLeaves - lastLbLeave >= 10) { + 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) + lastLbLeave = numQueueLeaves; + mipdata_->workers[i].search_ptr_->installNode(std::move(nextNode)); } - if (mipdata_->checkLimits()) { - limit_reached = true; - break; - } + ++numQueueLeaves; - HighsInt numPlungeNodes = mipdata_->num_nodes - plungestart; - if (numPlungeNodes >= 100) break; + 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 + numStallNodes = 0; + } + }; + + auto evaluateNode = [&](HighsInt i) -> bool { + if (!mipdata_->parallelLockActive()) + profiling_->start(kMipClockEvaluateNode1); + if (mipdata_->workers[i].search_ptr_->evaluateNode() == + HighsSearch::NodeResult::kSubOptimal) { + HighsNodeQueue& globalqueue = mipdata_->parallelLockActive() + ? mipdata_->workers[i].nodequeue + : mipdata_->nodequeue; + mipdata_->workers[i].search_ptr_->currentNodeToQueue(globalqueue); + if (!mipdata_->parallelLockActive()) + profiling_->stop(kMipClockEvaluateNode1); + return true; + } + if (!mipdata_->parallelLockActive()) + profiling_->stop(kMipClockEvaluateNode1); + return false; + }; + + auto pruneNode = [&](HighsInt i) -> bool { + if (!mipdata_->parallelLockActive()) + profiling_->start(kMipClockNodePrunedLoop); + bool pruned = false; + if (mipdata_->workers[i].search_ptr_->currentNodePruned()) { + mipdata_->workers[i].search_ptr_->backtrack(); + mipdata_->workers[i].getGlobalDomain().propagate(); + pruned = true; + ++mipdata_->workers[i].search_ptr_->getLocalNodes(); + ++mipdata_->workers[i].search_ptr_->getLocalLeaves(); + } + if (!mipdata_->parallelLockActive()) + profiling_->stop(kMipClockNodePrunedLoop); + return mipdata_->workers[i].getGlobalDomain().infeasible() || pruned; + }; + + auto separateAndStoreBasis = [&](HighsInt i) -> bool { + HighsMipWorker& worker = mipdata_->workers[i]; + if (options_mip_->mip_allow_cut_separation_at_nodes) { + if (!mipdata_->parallelLockActive()) + profiling_->start(kMipClockNodeSearchSeparation); + worker.sepa_ptr_->separate(worker.search_ptr_->getLocalDomain()); + if (!mipdata_->parallelLockActive()) + profiling_->stop(kMipClockNodeSearchSeparation); + } else { + worker.getCutPool().performAging(); + } - analysis_.mipTimerStart(kMipClockBacktrackPlunge); - const bool backtrack_plunge = search.backtrackPlunge(mipdata_->nodequeue); - analysis_.mipTimerStop(kMipClockBacktrackPlunge); - if (!backtrack_plunge) break; + if (worker.getGlobalDomain().infeasible()) { + worker.search_ptr_->cutoffNode(); + HighsNodeQueue& globalqueue = mipdata_->parallelLockActive() + ? worker.nodequeue + : mipdata_->nodequeue; + worker.search_ptr_->openNodesToQueue(globalqueue); + return true; + } - assert(search.hasNode()); + if (worker.getLpRelaxation().getStatus() != + HighsLpRelaxation::Status::kError && + worker.getLpRelaxation().getStatus() != + HighsLpRelaxation::Status::kNotSet) + worker.getLpRelaxation().storeBasis(); + + std::shared_ptr basis = + worker.getLpRelaxation().getStoredBasis(); + if (!basis || + !isBasisConsistent(worker.getLpRelaxation().getLp(), *basis)) { + HighsBasis b = mipdata_->firstrootbasis; + b.row_status.resize(worker.getLpRelaxation().numRows(), + HighsBasisStatus::kBasic); + basis = std::make_shared(std::move(b)); + worker.getLpRelaxation().setStoredBasis(basis); + } - if (mipdata_->conflictPool.getNumConflicts() > - options_mip_->mip_pool_soft_limit) { - analysis_.mipTimerStart(kMipClockPerformAging2); - mipdata_->conflictPool.performAging(); - analysis_.mipTimerStop(kMipClockPerformAging2); - } + return false; + }; - search.flushStatistics(); - mipdata_->printDisplayLine(); - // printf("continue plunging due to good estimate\n"); - } // while (true) - analysis_.mipTimerStop(kMipClockDive); + 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); + if (!mipdata_->parallelLockActive()) + profiling_->stop(kMipClockBacktrackPlunge); - analysis_.mipTimerStart(kMipClockOpenNodesToQueue0); - search.openNodesToQueue(mipdata_->nodequeue); - analysis_.mipTimerStop(kMipClockOpenNodesToQueue0); + if (!backtrack_plunge) return true; - search.flushStatistics(); + assert(mipdata_->workers[i].search_ptr_->hasNode()); - if (limit_reached) { - mipdata_->updateLowerBound(std::min( - mipdata_->upper_bound, mipdata_->nodequeue.getBestLowerBound())); - mipdata_->printDisplayLine(); - break; + if (mipdata_->workers[i].getConflictPool().getNumConflicts() > + options_mip_->mip_pool_soft_limit) { + mipdata_->workers[i].getConflictPool().performAging(); } - - // the search datastructure should have no installed node now - assert(!search.hasNode()); - - // propagate the global domain - analysis_.mipTimerStart(kMipClockDomainPropgate); - mipdata_->domain.propagate(); - analysis_.mipTimerStop(kMipClockDomainPropgate); - - analysis_.mipTimerStart(kMipClockPruneInfeasibleNodes); - mipdata_->pruned_treeweight += mipdata_->nodequeue.pruneInfeasibleNodes( - mipdata_->domain, mipdata_->feastol); - analysis_.mipTimerStop(kMipClockPruneInfeasibleNodes); - - // if global propagation detected infeasibility, stop here - if (mipdata_->domain.infeasible()) { - mipdata_->nodequeue.clear(); - mipdata_->pruned_treeweight = 1.0; - mipdata_->updateLowerBound(std::min(kHighsInf, mipdata_->upper_bound)); - mipdata_->printDisplayLine(); - break; + return false; + }; + + auto runHeuristics = [&](HighsInt i) -> bool { + HighsMipWorker& worker = mipdata_->workers[i]; + if (!mipdata_->parallelLockActive()) + profiling_->start(kMipClockDiveEvaluateNode); + const HighsSearch::NodeResult evaluate_node_result = + worker.search_ptr_->evaluateNode(); + if (!mipdata_->parallelLockActive()) + profiling_->stop(kMipClockDiveEvaluateNode); + + if (evaluate_node_result == HighsSearch::NodeResult::kSubOptimal) { + return true; } - mipdata_->updateLowerBound(std::min( - mipdata_->upper_bound, mipdata_->nodequeue.getBestLowerBound())); - mipdata_->printDisplayLine(); - if (mipdata_->nodequeue.empty()) break; - - // if global propagation found bound changes, we update the local domain - if (!mipdata_->domain.getChangedCols().empty()) { - analysis_.mipTimerStart(kMipClockUpdateLocalDomain); - highsLogDev(options_mip_->log_options, HighsLogType::kInfo, - "added %" HIGHSINT_FORMAT " global bound changes\n", - (HighsInt)mipdata_->domain.getChangedCols().size()); - mipdata_->cliquetable.cleanupFixed(mipdata_->domain); - for (HighsInt col : mipdata_->domain.getChangedCols()) - mipdata_->implications.cleanupVarbounds(col); + if (worker.search_ptr_->currentNodePruned()) { + ++worker.search_ptr_->getLocalLeaves(); + return false; + } - mipdata_->domain.setDomainChangeStack(std::vector()); - search.resetLocalDomain(); + if (!mipdata_->parallelLockActive()) + profiling_->start(kMipClockDivePrimalHeuristics); + if (mipdata_->incumbent.empty()) { + if (!mipdata_->parallelLockActive()) + profiling_->start(kMipClockDiveRandomizedRounding); + mipdata_->heuristics.randomizedRounding( + worker, + worker.getLpRelaxation().getLpSolver().getSolution().col_value); + if (!mipdata_->parallelLockActive()) + profiling_->stop(kMipClockDiveRandomizedRounding); + } + if (mipdata_->incumbent.empty()) { + if (options_mip_->mip_heuristic_run_rens) { + if (!mipdata_->parallelLockActive()) + profiling_->start(kMipClockDiveRens); + mipdata_->heuristics.RENS( + worker, + worker.getLpRelaxation().getLpSolver().getSolution().col_value); + if (!mipdata_->parallelLockActive()) + profiling_->stop(kMipClockDiveRens); + } + } else { + if (options_mip_->mip_heuristic_run_rins) { + if (!mipdata_->parallelLockActive()) + profiling_->start(kMipClockDiveRins); + mipdata_->heuristics.RINS( + worker, + worker.getLpRelaxation().getLpSolver().getSolution().col_value); + if (!mipdata_->parallelLockActive()) + profiling_->stop(kMipClockDiveRins); + } + } - mipdata_->domain.clearChangedCols(); - mipdata_->removeFixedIndices(); - analysis_.mipTimerStop(kMipClockUpdateLocalDomain); + if (!mipdata_->parallelLockActive()) + profiling_->stop(kMipClockDivePrimalHeuristics); + + return worker.getGlobalDomain().infeasible(); + }; + + auto dive = [&](HighsInt i, bool ramp_up) -> bool { + HighsMipWorker& worker = mipdata_->workers[i]; + if (!worker.search_ptr_->currentNodePruned()) { + if (!mipdata_->parallelLockActive()) profiling_->start(kMipClockTheDive); + const HighsSearch::NodeResult search_dive_result = + worker.search_ptr_->dive(ramp_up); + if (!mipdata_->parallelLockActive()) profiling_->stop(kMipClockTheDive); + if (search_dive_result == HighsSearch::NodeResult::kSubOptimal) { + return true; + } + worker.search_ptr_->getLocalLeaves()++; } + return ramp_up; + }; + + auto processNodes = [&](std::vector& indices, + const bool skip_separation, const bool ramp_up, + const HighsInt plungeLimit, double avgiter) { + auto processNode = [&](HighsInt i) { + HighsMipWorker& worker = mipdata_->workers[i]; + int64_t nodes_explored = 0; + if (!skip_separation) { + evaluateNode(i); + if (pruneNode(i)) return; + if (!mipdata_->parallelLockActive()) { + if (mipdata_->checkLimits()) return; + mipdata_->printDisplayLine(); + } else { + if (worker.search_ptr_->checkLocalLimits()) return; + } + 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); + bool considerHeuristics = true; + while (true) { + if (considerHeuristics && !ramp_up && worker.getAllowHeuristics() && + mipdata_->moreHeuristicsAllowed()) { + if (runHeuristics(i)) break; + } + considerHeuristics = false; + if (worker.getGlobalDomain().infeasible()) break; + if (dive(i, ramp_up)) break; + if (worker.search_ptr_->checkLimits( + worker.search_ptr_->getLocalNodes())) { + break; + } + if (worker.search_ptr_->getLocalNodes() + nodes_explored >= plungeLimit) + break; + if (!mipdata_->parallelLockActive()) { + nodes_explored += worker.search_ptr_->getLocalNodes(); + } + if (backtrackPlunge(i)) break; + if (!mipdata_->parallelLockActive()) { + worker.search_ptr_->flushStatistics(); + mipdata_->printDisplayLine(); + } + } + }; + runTask(processNode, tg, true, false, indices); + }; + + auto syncSepaStats = [&](HighsMipWorker& worker) { + mipdata_->cliquetable.getNumNeighbourhoodQueries() += + worker.getNumNeighbourhoodQueries(); + mipdata_->sepa_lp_iterations += worker.getSepaLpIterations(); + mipdata_->total_lp_iterations += worker.getSepaLpIterations(); + worker.resetSepaStats(); + }; + + auto checkRestart = [&]() -> bool { if (!submip && mipdata_->num_nodes >= nextCheck) { auto nTreeRestarts = mipdata_->numRestarts - mipdata_->numRestartsRoot; double currNodeEstim = @@ -483,175 +827,144 @@ void HighsMipSolver::run() { highsLogUser(options_mip_->log_options, HighsLogType::kInfo, "\nRestarting search from the root node\n"); mipdata_->performRestart(); - analysis_.mipTimerStop(kMipClockSearch); - goto restart; + profiling_->stop(kMipClockSearch); + return true; } - } // if (!submip && mipdata_->num_nodes >= nextCheck)) + } + return false; + }; - // remove the iteration limit when installing a new node - // mipdata_->lp.setIterationLimit(); + // Main solve loop + std::vector search_indices(1, 0); + bool root_node = true; // Don't separate the root node again + while (!mipdata_->nodequeue.empty()) { + // Possibly query existence of an external solution + if (!submip) + mipdata_->queryExternalSolution( + solution_objective_, kExternalMipSolutionQueryOriginBeforeDive); - // loop to install the next node for the search - double this_node_search_time = -analysis_.mipTimerRead(kMipClockNodeSearch); - analysis_.mipTimerStart(kMipClockNodeSearch); + // Update global pseudo-cost with worker information + syncGlobalPseudoCost(); - while (!mipdata_->nodequeue.empty()) { - // printf("popping node from nodequeue (length = %" HIGHSINT_FORMAT ")\n", - // (HighsInt)nodequeue.size()); - assert(!search.hasNode()); + // Get new candidate worker search indices + getSearchIndicesWithNoNodes(search_indices); - if (numQueueLeaves - lastLbLeave >= 10) { - search.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) - lastLbLeave = numQueueLeaves; - search.installNode(std::move(nextNode)); - } + // Only update worker's pseudo-costs that have been assigned a node + resetWorkerPseudoCosts(search_indices); - ++numQueueLeaves; + // Assign nodes to workers + bool limit_reached = false; + if (root_node) { + master_worker.search_ptr_->installNode( + mipdata_->nodequeue.popBestBoundNode()); + } else { + installNodes(search_indices, limit_reached); + } + if (limit_reached) break; - if (search.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 - numStallNodes = 0; + // Process nodes (separation / heuristics / dives) + processNodes(search_indices, root_node, num_workers < max_num_workers, 100, + mipdata_->getLp().getAvgSolveIters()); - assert(search.hasNode()); - - // we evaluate the node directly here instead of performing a dive - // because we first want to check if the node is not fathomed due to - // new global information before we perform separation rounds for the node - analysis_.mipTimerStart(kMipClockEvaluateNode1); - const HighsSearch::NodeResult evaluate_node_result = - search.evaluateNode(); - analysis_.mipTimerStop(kMipClockEvaluateNode1); - if (evaluate_node_result == HighsSearch::NodeResult::kSubOptimal) { - analysis_.mipTimerStart(kMipClockCurrentNodeToQueue); - search.currentNodeToQueue(mipdata_->nodequeue); - analysis_.mipTimerStop(kMipClockCurrentNodeToQueue); + root_node = false; + + // Sync statistics, check infeasibility, and flush nodes from worker queues + bool infeasible = false; + for (HighsInt i : search_indices) { + HighsMipWorker& worker = mipdata_->workers[i]; + if (worker.getGlobalDomain().infeasible()) { + 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( + std::move(node.domchgstack), std::move(node.branchings), + node.lower_bound, node.estimate, node.depth); } + profiling_->stop(kMipClockOpenNodesToQueue0); + worker.search_ptr_->flushStatistics(); + syncSepaStats(worker); + mipdata_->heuristics.flushStatistics(*this, worker); + } - // if the node was pruned we remove it from the search and install the - // next node from the queue - analysis_.mipTimerStart(kMipClockNodePrunedLoop); - if (search.currentNodePruned()) { - // analysis_.mipTimerStart(kMipClockSearchBacktrack); - search.backtrack(); - // analysis_.mipTimerStop(kMipClockSearchBacktrack); - ++mipdata_->num_leaves; - ++mipdata_->num_nodes; - search.flushStatistics(); - - mipdata_->domain.propagate(); - mipdata_->pruned_treeweight += mipdata_->nodequeue.pruneInfeasibleNodes( - mipdata_->domain, mipdata_->feastol); - - if (mipdata_->domain.infeasible()) { - mipdata_->nodequeue.clear(); - mipdata_->pruned_treeweight = 1.0; - - mipdata_->updateLowerBound( - std::min(kHighsInf, mipdata_->upper_bound)); - - analysis_.mipTimerStop(kMipClockNodePrunedLoop); - break; - } + if (infeasible) { + mipdata_->nodequeue.clear(); + mipdata_->pruned_treeweight = 1.0; + mipdata_->updateLowerBound(std::min(kHighsInf, mipdata_->upper_bound)); + break; + } - if (mipdata_->checkLimits()) { - limit_reached = true; - break; - } + mipdata_->updateLowerBound(std::min( + mipdata_->upper_bound, mipdata_->nodequeue.getBestLowerBound())); - // analysis_.mipTimerStart(kMipClockStoreBasis); - mipdata_->updateLowerBound(std::min( - mipdata_->upper_bound, mipdata_->nodequeue.getBestLowerBound())); + syncSolutions(); - mipdata_->printDisplayLine(); + limit_reached = mipdata_->checkLimits(); + if (limit_reached) { + mipdata_->printDisplayLine(); + break; + } - if (!mipdata_->domain.getChangedCols().empty()) { - highsLogDev(options_mip_->log_options, HighsLogType::kInfo, - "added %" HIGHSINT_FORMAT " global bound changes\n", - (HighsInt)mipdata_->domain.getChangedCols().size()); - mipdata_->cliquetable.cleanupFixed(mipdata_->domain); - for (HighsInt col : mipdata_->domain.getChangedCols()) - mipdata_->implications.cleanupVarbounds(col); + assert(!nodesInstalled()); - mipdata_->domain.setDomainChangeStack( - std::vector()); - search.resetLocalDomain(); + // Sync global information + profiling_->start(kMipClockDomainPropgate); + syncPools(search_indices); + syncGlobalDomain(search_indices); + mipdata_->getDomain().propagate(); + profiling_->stop(kMipClockDomainPropgate); - mipdata_->domain.clearChangedCols(); - mipdata_->removeFixedIndices(); - } - // analysis_.mipTimerStop(kMipClockStoreBasis); + profiling_->start(kMipClockPruneInfeasibleNodes); + mipdata_->pruned_treeweight += mipdata_->nodequeue.pruneInfeasibleNodes( + mipdata_->getDomain(), mipdata_->feastol); + profiling_->stop(kMipClockPruneInfeasibleNodes); - analysis_.mipTimerStop(kMipClockNodePrunedLoop); - continue; - } - analysis_.mipTimerStop(kMipClockNodePrunedLoop); + if (mipdata_->getDomain().infeasible()) { + mipdata_->nodequeue.clear(); + mipdata_->pruned_treeweight = 1.0; + mipdata_->updateLowerBound(std::min(kHighsInf, mipdata_->upper_bound)); + mipdata_->printDisplayLine(); + break; + } - // the node is still not fathomed, so perform separation - if (options_mip_->mip_allow_cut_separation_at_nodes) { - analysis_.mipTimerStart(kMipClockNodeSearchSeparation); - sepa.separate(search.getLocalDomain()); - analysis_.mipTimerStop(kMipClockNodeSearchSeparation); - } else { - // perform aging - mipdata_->cutpool.performAging(); - } + mipdata_->updateLowerBound(std::min( + mipdata_->upper_bound, mipdata_->nodequeue.getBestLowerBound())); + mipdata_->printDisplayLine(); - if (mipdata_->domain.infeasible()) { - search.cutoffNode(); - analysis_.mipTimerStart(kMipClockOpenNodesToQueue1); - search.openNodesToQueue(mipdata_->nodequeue); - analysis_.mipTimerStop(kMipClockOpenNodesToQueue1); - mipdata_->nodequeue.clear(); - mipdata_->pruned_treeweight = 1.0; + if (mipdata_->nodequeue.empty()) break; - mipdata_->updateLowerBound(std::min(kHighsInf, mipdata_->upper_bound)); - break; - } + // Reset global domain and sync worker's global domains. + bool spawn_more_workers = num_workers < max_num_workers && + mipdata_->nodequeue.numNodes() > num_workers; + resetGlobalDomain(spawn_more_workers, mipdata_->hasMultipleWorkers()); - // after separation we store the new basis and proceed with the outer loop - // to perform a dive from this node - if (mipdata_->lp.getStatus() != HighsLpRelaxation::Status::kError && - mipdata_->lp.getStatus() != HighsLpRelaxation::Status::kNotSet) - mipdata_->lp.storeBasis(); - - basis = mipdata_->lp.getStoredBasis(); - if (!basis || !isBasisConsistent(mipdata_->lp.getLp(), *basis)) { - HighsBasis b = mipdata_->firstrootbasis; - b.row_status.resize(mipdata_->lp.numRows(), HighsBasisStatus::kBasic); - basis = std::make_shared(std::move(b)); - mipdata_->lp.setStoredBasis(basis); - } + if (checkRestart()) goto restart; - break; - } // while(!mipdata_->nodequeue.empty()) - analysis_.mipTimerStop(kMipClockNodeSearch); - if (analysis_.analyse_mip_time) { - this_node_search_time += analysis_.mipTimerRead(kMipClockNodeSearch); - analysis_.node_search_time.push_back(this_node_search_time); + if (spawn_more_workers) { + HighsInt new_max_num_workers = + std::min(static_cast(mipdata_->nodequeue.numNodes()), + max_num_workers); + mipdata_->getPseudoCost().removeChanged(); + if (num_workers == 1) { + constructAdditionalWorkerData(master_worker); + } + createNewWorkers(new_max_num_workers - num_workers); + num_workers = new_max_num_workers; } - if (limit_reached) break; - } // while(search.hasNode()) - analysis_.mipTimerStop(kMipClockSearch); + } + syncSolutions(); + profiling_->stop(kMipClockSearch); cleanupSolve(); } void HighsMipSolver::cleanupSolve() { + for (HighsMipWorker& worker : mipdata_->workers) { + assert(worker.solutions_.empty()); + } if (mipdata_->terminatorActive()) { if (mipdata_->terminatorTerminated()) { // Indicate that this instance has been interrupted @@ -672,15 +985,13 @@ void HighsMipSolver::cleanupSolve() { mipdata_->printDisplayLine(kSolutionSourceCleanup); // Stop the solve clock - which won't be running if presolve // determines the model status - if (analysis_.mipTimerRunning(kMipClockSolve)) - analysis_.mipTimerStop(kMipClockSolve); - + if (profiling_->running(kSolveTime)) profiling_->stop(kSolveTime); // Need to complete the calculation of P-D integral, checking for NO // gap change mipdata_->updatePrimalDualIntegral( mipdata_->lower_bound, mipdata_->lower_bound, mipdata_->upper_bound, mipdata_->upper_bound, false); - analysis_.mipTimerStart(kMipClockPostsolve); + profiling_->start(kPostsolveTime); bool havesolution = solution_objective_ != kHighsInf; bool feasible; @@ -722,21 +1033,9 @@ void HighsMipSolver::cleanupSolve() { modelstatus_ = HighsModelStatus::kInfeasible; } - analysis_.mipTimerStop(kMipClockPostsolve); + profiling_->stop(kPostsolveTime); timer_.stop(); - std::string solutionstatus = "-"; - - if (havesolution) { - // Surely this definition of feasible is unnecessary - bool lc_feasible = - bound_violation_ <= options_mip_->mip_feasibility_tolerance && - integrality_violation_ <= options_mip_->mip_feasibility_tolerance && - row_violation_ <= options_mip_->mip_feasibility_tolerance; - assert(feasible == lc_feasible); - solutionstatus = feasible ? "feasible" : "infeasible"; - } - gap_ = fabs(primal_bound_ - dual_bound_); if (primal_bound_ == 0.0) gap_ = dual_bound_ == 0.0 ? 0.0 : kHighsInf; @@ -745,6 +1044,22 @@ void HighsMipSolver::cleanupSolve() { else gap_ = kHighsInf; + // + if (options_mip_->output_flag) { + // solutionstatus is only used in solvingReport, but depends on + // values of havesolution and feasible that are local to + // HighsMipSolver::cleanupSolve + const std::string solutionstatus = + havesolution ? (feasible ? "feasible" : "infeasible") : "-"; + solvingReport(solutionstatus); + } + + assert(modelstatus_ != HighsModelStatus::kNotset); + + if (improving_solution_file_ != nullptr) fclose(improving_solution_file_); +} + +void HighsMipSolver::solvingReport(const std::string& solutionstatus) const { std::array gapString = getGapString(gap_, primal_bound_, options_mip_); @@ -777,16 +1092,34 @@ void HighsMipSolver::cleanupSolve() { solution_objective_, bound_violation_, integrality_violation_, row_violation_); if (!timeless_log) { - highsLogUser(options_mip_->log_options, HighsLogType::kInfo, - " Timing %.2f\n", timer_.read()); - if (analysis_.analyse_mip_time) + auto callRecord = [&](HighsInt clock) { + double mip_time = profiling_->read(clock, kMipRecord); + double submip_time = profiling_->read(clock, kSubMipRecord); + HighsInt mip_calls = profiling_->numCall(clock, kMipRecord); + HighsInt submip_calls = profiling_->numCall(clock, kSubMipRecord); + double total_time = mip_time + submip_time; + // Only log postsolve if it's written as nonzero highsLogUser(options_mip_->log_options, HighsLogType::kInfo, - " %.2f (presolve)\n" - " %.2f (solve)\n" - " %.2f (postsolve)\n", - analysis_.mipTimerRead(kMipClockPresolve), - analysis_.mipTimerRead(kMipClockSolve), - analysis_.mipTimerRead(kMipClockPostsolve)); + " %.2f (%s)\n", total_time, + profiling_->name[clock].c_str()); + if (mip_calls > 1 || submip_calls > 0) { + highsLogUser( + options_mip_->log_options, HighsLogType::kInfo, + " MIP time [calls] = %.2f [%d]\n", + mip_time, int(mip_calls)); + if (submip_calls > 0) + highsLogUser( + options_mip_->log_options, HighsLogType::kInfo, + " subMIP time [calls] = %.2f [%d]\n", + submip_time, int(submip_calls)); + } + }; + double total = timer_.read(); + highsLogUser(options_mip_->log_options, HighsLogType::kInfo, + " Timing %.2f\n", total); + callRecord(kPresolveTime); + callRecord(kSolveTime); + callRecord(kPostsolveTime); } highsLogUser(options_mip_->log_options, HighsLogType::kInfo, " Max sub-MIP depth %d\n" @@ -813,14 +1146,6 @@ void HighsMipSolver::cleanupSolve() { (long long unsigned)mipdata_->sb_lp_iterations, (long long unsigned)mipdata_->sepa_lp_iterations, (long long unsigned)mipdata_->heuristic_lp_iterations); - - if (!timeless_log) analysis_.reportMipTimer(); - - analysis_.checkSubSolverCallTime(sub_solver_call_time_); - - assert(modelstatus_ != HighsModelStatus::kNotset); - - if (improving_solution_file_ != nullptr) fclose(improving_solution_file_); } // Only called in Highs::runPresolve @@ -849,9 +1174,9 @@ void HighsMipSolver::callbackGetCutPool() const { HighsCallbackOutput& data_out = callback_->data_out; HighsSparseMatrix cut_matrix; - mipdata_->lp.getCutPool(data_out.cutpool_num_col, data_out.cutpool_num_cut, - data_out.cutpool_lower, data_out.cutpool_upper, - cut_matrix); + mipdata_->getLp().getCutPool(data_out.cutpool_num_col, + data_out.cutpool_num_cut, data_out.cutpool_lower, + data_out.cutpool_upper, cut_matrix); // take ownership data_out.cutpool_start = std::move(cut_matrix.start_); @@ -998,3 +1323,17 @@ void HighsMipSolver::initialiseTerminator(const HighsMipSolver& mip_solver) { mip_solver.mipdata_->terminatorMyInstance(), mip_solver.terminator_.record); } + +void HighsMipSolver::setParallelLock(bool lock) const { + if (!mipdata_->hasMultipleWorkers()) return; + mipdata_->parallel_lock = lock; + for (HighsConflictPool& conflictpool : mipdata_->conflictpools) { + conflictpool.setAgeLock(lock); + } + mipdata_->cliquetable.setAllowParallel(!lock && !submip); +} + +void HighsMipSolver::setProfiling(HighsProfiling* profiling) { + assert(profiling); + this->profiling_ = profiling; +} diff --git a/highs/mip/HighsMipSolver.h b/highs/mip/HighsMipSolver.h index a70e8736998..0921e83ef4a 100644 --- a/highs/mip/HighsMipSolver.h +++ b/highs/mip/HighsMipSolver.h @@ -11,7 +11,7 @@ #include "Highs.h" #include "lp_data/HighsCallback.h" #include "lp_data/HighsOptions.h" -#include "mip/HighsMipAnalysis.h" +#include "parallel/HighsParallel.h" struct HighsMipSolverData; class HighsCutPool; @@ -67,8 +67,6 @@ class HighsMipSolver { std::unique_ptr mipdata_; - HighsMipAnalysis analysis_; - HighsModelStatus termination_status_; HighsTerminator terminator_; @@ -118,15 +116,22 @@ class HighsMipSolver { ~HighsMipSolver(); + template + void runTask(F&& f, highs::parallel::TaskGroup& tg, bool parallel_lock, + bool force_serial, + const std::vector& indices = std::vector(1, + 0)); + void setModel(const HighsLp& model) { model_ = &model; solution_objective_ = kHighsInf; } mutable HighsTimer timer_; - mutable HighsSubSolverCallTime sub_solver_call_time_; + HighsProfiling* profiling_ = nullptr; void cleanupSolve(); + void solvingReport(const std::string& solutionstatus) const; void runMipPresolve(const HighsInt presolve_reduction_limit); const HighsLp& getPresolvedModel() const; @@ -151,6 +156,8 @@ class HighsMipSolver { HighsModelStatus terminationStatus() const { return this->termination_status_; } + void setParallelLock(bool lock) const; + void setProfiling(HighsProfiling* profiling); }; std::array getGapString(const double gap_, diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index db415afa692..0993594a557 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -19,6 +19,71 @@ #include "presolve/HPresolve.h" #include "util/HighsIntegers.h" +HighsMipSolverData::HighsMipSolverData(HighsMipSolver& mipsolver) + : mipsolver(mipsolver), + lps(1, HighsLpRelaxation(mipsolver)), + domains(1, HighsDomain(mipsolver)), + pseudocosts(1), + parallel_lock(false), + heuristics(mipsolver), + cliquetable(mipsolver.numCol()), + implications(mipsolver), + objectiveFunction(mipsolver), + presolve_status(HighsPresolveStatus::kNotSet), + cliquesExtracted(false), + rowMatrixSet(false), + analyticCenterComputed(false), + analyticCenterStatus(HighsModelStatus::kNotset), + detectSymmetries(false), + numRestarts(0), + numRestartsRoot(0), + numCliqueEntriesAfterPresolve(0), + numCliqueEntriesAfterFirstPresolve(0), + feastol(0.0), + epsilon(0.0), + heuristic_effort(0.0), + dispfreq(0), + firstlpsolobj(-kHighsInf), + rootlpsolobj(-kHighsInf), + numintegercols(0), + maxTreeSizeLog2(0), + pruned_treeweight(0), + avgrootlpiters(0.0), + disptime(0.0), + last_disptime(0.0), + firstrootlpiters(0), + num_nodes(0), + num_leaves(0), + num_leaves_before_run(0), + num_nodes_before_run(0), + total_repair_lp(0), + total_repair_lp_feasible(0), + total_repair_lp_iterations(0), + total_lp_iterations(0), + heuristic_lp_iterations(0), + sepa_lp_iterations(0), + sb_lp_iterations(0), + total_lp_iterations_before_run(0), + heuristic_lp_iterations_before_run(0), + sepa_lp_iterations_before_run(0), + sb_lp_iterations_before_run(0), + num_disp_lines(0), + numImprovingSols(0), + lower_bound(-kHighsInf), + upper_bound(kHighsInf), + upper_limit(kHighsInf), + optimality_limit(kHighsInf), + debugSolution(mipsolver) { + conflictpools.emplace_back(5 * mipsolver.options_mip_->mip_pool_age_limit, + mipsolver.options_mip_->mip_pool_soft_limit); + cutpools.emplace_back(mipsolver.numCol(), + mipsolver.options_mip_->mip_pool_age_limit, + mipsolver.options_mip_->mip_pool_soft_limit, 0); + getDomain().addCutpool(getCutPool()); + getDomain().addConflictPool(getConflictPool()); + cliquetable.setAllowParallel(!mipsolver.submip); +} + std::string HighsMipSolverData::solutionSourceToString( const int solution_source, const bool code) const { if (solution_source == kSolutionSourceNone) { @@ -346,7 +411,10 @@ void HighsMipSolverData::startAnalyticCenterComputation( taskGroup.spawn([&]() { // first check if the analytic centre computation should be cancelled, e.g. // due to early return in the root node evaluation + // + // Highs instantiation Highs ipm; + ipm.setProfiling(mipsolver.profiling_); ipm.setOptionValue("output_flag", false); const std::vector& sol = ipm.getSolution().col_value; // Don't use presolve - because this can lead to postsolve putting @@ -396,7 +464,11 @@ void HighsMipSolverData::startAnalyticCenterComputation( (void)output_flag; ipm.setOptionValue("output_flag", !mipsolver.submip); } + const HighsInt profiling_clock = + use_hipo ? kSubSolverHipoAc : kSubSolverIpxAc; + if (mipsolver.profiling_) mipsolver.profiling_->start(profiling_clock); ipm.optimizeLp(); + if (mipsolver.profiling_) mipsolver.profiling_->stop(profiling_clock); if (ipm_logging) ipm.setOptionValue("output_flag", false); if (use_hipo && mip_ipm_solver == kHighsChooseString && HighsInt(sol.size()) != mipsolver.numCol()) { @@ -408,18 +480,6 @@ void HighsMipSolverData::startAnalyticCenterComputation( ipm.setOptionValue("solver", kIpxString); ipm.optimizeLp(); } - if (!mipsolver.submip) { - const HighsSubSolverCallTime& sub_solver_call_time = - ipm.getSubSolverCallTime(); - const bool analytic_centre = true; - mipsolver.analysis_.addSubSolverCallTime(sub_solver_call_time, - analytic_centre); - // Go through sub_solver_call_time to update any MIP clocks - const bool valid_basis = false; - const bool use_presolve = false; - mipsolver.analysis_.mipTimerUpdate(sub_solver_call_time, valid_basis, - use_presolve, analytic_centre); - } if (HighsInt(sol.size()) != mipsolver.numCol()) return; analyticCenterStatus = ipm.getModelStatus(); analyticCenter = sol; @@ -428,17 +488,17 @@ void HighsMipSolverData::startAnalyticCenterComputation( void HighsMipSolverData::finishAnalyticCenterComputation( const highs::parallel::TaskGroup& taskGroup) { - if (mipsolver.analysis_.analyse_mip_time) { + if (mipsolver.profiling_->mip_) { highsLogUser(mipsolver.options_mip_->log_options, HighsLogType::kInfo, "MIP-Timing: %11.2g - starting analytic centre synch\n", - mipsolver.analysis_.mipTimerRead()); + mipsolver.timer_.read()); fflush(stdout); } taskGroup.sync(); - if (mipsolver.analysis_.analyse_mip_time) { + if (mipsolver.profiling_->mip_) { highsLogUser(mipsolver.options_mip_->log_options, HighsLogType::kInfo, "MIP-Timing: %11.2g - completed analytic centre synch\n", - mipsolver.analysis_.mipTimerRead()); + mipsolver.timer_.read()); fflush(stdout); } analyticCenterComputed = true; @@ -446,26 +506,26 @@ void HighsMipSolverData::finishAnalyticCenterComputation( HighsInt nfixed = 0; HighsInt nintfixed = 0; for (HighsInt i = 0; i != mipsolver.numCol(); ++i) { - double boundRange = mipsolver.mipdata_->domain.col_upper_[i] - - mipsolver.mipdata_->domain.col_lower_[i]; + double boundRange = mipsolver.mipdata_->getDomain().col_upper_[i] - + mipsolver.mipdata_->getDomain().col_lower_[i]; if (boundRange == 0.0) continue; double tolerance = mipsolver.mipdata_->feastol * std::min(boundRange, 1.0); if (analyticCenter[i] <= mipsolver.model_->col_lower_[i] + tolerance) { - mipsolver.mipdata_->domain.changeBound( + mipsolver.mipdata_->getDomain().changeBound( HighsBoundType::kUpper, i, mipsolver.model_->col_lower_[i], HighsDomain::Reason::unspecified()); - if (mipsolver.mipdata_->domain.infeasible()) return; + if (mipsolver.mipdata_->getDomain().infeasible()) return; ++nfixed; if (mipsolver.isColInteger(i)) ++nintfixed; } else if (analyticCenter[i] >= mipsolver.model_->col_upper_[i] - tolerance) { - mipsolver.mipdata_->domain.changeBound( + mipsolver.mipdata_->getDomain().changeBound( HighsBoundType::kLower, i, mipsolver.model_->col_upper_[i], HighsDomain::Reason::unspecified()); - if (mipsolver.mipdata_->domain.infeasible()) return; + if (mipsolver.mipdata_->getDomain().infeasible()) return; ++nfixed; if (mipsolver.isColInteger(i)) ++nintfixed; } @@ -475,8 +535,8 @@ void HighsMipSolverData::finishAnalyticCenterComputation( "Fixing %d columns (%d integers) sitting at bound at " "analytic center\n", int(nfixed), int(nintfixed)); - mipsolver.mipdata_->domain.propagate(); - if (mipsolver.mipdata_->domain.infeasible()) return; + mipsolver.mipdata_->getDomain().propagate(); + if (mipsolver.mipdata_->getDomain().infeasible()) return; } } @@ -539,8 +599,10 @@ void HighsMipSolverData::finishSymmetryDetection( for (HighsOrbitopeMatrix& orbitope : symmetries.orbitopes) orbitope.determineOrbitopeType(cliquetable); - if (symmetries.numPerms != 0) - globalOrbits = symmetries.computeStabilizerOrbits(domain); + if (symmetries.numPerms != 0) { + StabilizerOrbitWorkspace workspace; + globalOrbits = symmetries.computeStabilizerOrbits(getDomain(), workspace); + } } double HighsMipSolverData::limitsToGap(const double use_lower_bound, @@ -671,19 +733,19 @@ bool HighsMipSolverData::moreHeuristicsAllowed() const { void HighsMipSolverData::removeFixedIndices() { integral_cols.erase( std::remove_if(integral_cols.begin(), integral_cols.end(), - [&](HighsInt col) { return domain.isFixed(col); }), + [&](HighsInt col) { return getDomain().isFixed(col); }), integral_cols.end()); integer_cols.erase( std::remove_if(integer_cols.begin(), integer_cols.end(), - [&](HighsInt col) { return domain.isFixed(col); }), + [&](HighsInt col) { return getDomain().isFixed(col); }), integer_cols.end()); implint_cols.erase( std::remove_if(implint_cols.begin(), implint_cols.end(), - [&](HighsInt col) { return domain.isFixed(col); }), + [&](HighsInt col) { return getDomain().isFixed(col); }), implint_cols.end()); continuous_cols.erase( std::remove_if(continuous_cols.begin(), continuous_cols.end(), - [&](HighsInt col) { return domain.isFixed(col); }), + [&](HighsInt col) { return getDomain().isFixed(col); }), continuous_cols.end()); } @@ -770,7 +832,7 @@ void HighsMipSolverData::runSetup() { const HighsLp& model = *mipsolver.model_; // Indicate that the first LP has not been solved - this->lp.setSolvedFirstLp(false); + this->getLp().setSolvedFirstLp(false); last_disptime = -kHighsInf; disptime = 0; @@ -842,7 +904,7 @@ void HighsMipSolverData::runSetup() { addIncumbent(std::vector(), 0, kSolutionSourceEmptyMip); redcostfixing = HighsRedcostFixing(); - pseudocost = HighsPseudocost(mipsolver); + getPseudoCost() = HighsPseudocost(mipsolver); nodequeue.setNumCol(mipsolver.numCol()); nodequeue.setOptimalityLimit(optimality_limit); @@ -914,11 +976,11 @@ void HighsMipSolverData::runSetup() { } // compute row activities and propagate all rows once - objectiveFunction.setupCliquePartition(domain, cliquetable); - domain.setupObjectivePropagation(); - domain.computeRowActivities(); - domain.propagate(); - if (domain.infeasible()) { + objectiveFunction.setupCliquePartition(getDomain(), cliquetable); + getDomain().setupObjectivePropagation(); + getDomain().computeRowActivities(); + getDomain().propagate(); + if (getDomain().infeasible()) { mipsolver.modelstatus_ = HighsModelStatus::kInfeasible; updateLowerBound(kHighsInf); @@ -935,14 +997,15 @@ void HighsMipSolverData::runSetup() { if (checkLimits()) return; // extract cliques if they have not been extracted before - for (HighsInt col : domain.getChangedCols()) + for (HighsInt col : getDomain().getChangedCols()) implications.cleanupVarbounds(col); - domain.clearChangedCols(); + getDomain().clearChangedCols(); - lp.getLpSolver().setOptionValue("presolve", kHighsOffString); + getLp().getLpSolver().setOptionValue("presolve", kHighsOffString); // lp.getLpSolver().setOptionValue("dual_simplex_cost_perturbation_multiplier", // 0.0); lp.getLpSolver().setOptionValue("parallel", kHighsOnString); - lp.getLpSolver().setOptionValue("simplex_initial_condition_check", false); + getLp().getLpSolver().setOptionValue("simplex_initial_condition_check", + false); checkObjIntegrality(); rootlpsol.clear(); @@ -953,14 +1016,14 @@ void HighsMipSolverData::runSetup() { for (HighsInt i = 0; i != mipsolver.numCol(); ++i) { switch (mipsolver.variableType(i)) { case HighsVarType::kContinuous: - if (domain.isFixed(i)) { + if (getDomain().isFixed(i)) { num_domain_fixed++; continue; } continuous_cols.push_back(i); break; case HighsVarType::kImplicitInteger: - if (domain.isFixed(i)) { + if (getDomain().isFixed(i)) { num_domain_fixed++; continue; } @@ -968,9 +1031,9 @@ void HighsMipSolverData::runSetup() { integral_cols.push_back(i); break; case HighsVarType::kInteger: - if (domain.isFixed(i)) { + if (getDomain().isFixed(i)) { num_domain_fixed++; - if (fractionality(domain.col_lower_[i]) > feastol) { + if (fractionality(getDomain().col_lower_[i]) > feastol) { // integer variable is fixed to a fractional value -> infeasible mipsolver.modelstatus_ = HighsModelStatus::kInfeasible; @@ -1073,7 +1136,7 @@ void HighsMipSolverData::runSetup() { debugsolobj += mipsolver.colCost(i) * HighsCDouble(debugSolution.debugSolution[i]); debugSolution.debugSolObjective = static_cast(debugsolobj); - debugSolution.registerDomain(domain); + debugSolution.registerDomain(getDomain()); assert(checkSolution(debugSolution.debugSolution)); } #endif @@ -1132,7 +1195,9 @@ double HighsMipSolverData::transformNewIntegerFeasibleSolution( this->total_repair_lp++; double time_available = std::max( mipsolver.options_mip_->time_limit - mipsolver.timer_.read(), 0.1); + // Highs instantiation Highs tmpSolver; + tmpSolver.setProfiling(mipsolver.profiling_); const bool debug_report = false; if (debug_report) { tmpSolver.setOptionValue("log_dev_level", 2); @@ -1161,17 +1226,6 @@ double HighsMipSolverData::transformNewIntegerFeasibleSolution( // HiPO or IPX to solve an LP without a basis, use simplex tmpSolver.setOptionValue("solver", kSimplexString); tmpSolver.optimizeLp(); - if (!mipsolver.submip) { - const HighsSubSolverCallTime& sub_solver_call_time = - tmpSolver.getSubSolverCallTime(); - const bool analytic_centre = false; - mipsolver.analysis_.addSubSolverCallTime(sub_solver_call_time, - analytic_centre); - // Go through sub_solver_call_time to update any MIP clocks - const bool valid_basis = false; - mipsolver.analysis_.mipTimerUpdate(sub_solver_call_time, valid_basis, - use_presolve, analytic_centre); - } this->total_repair_lp_iterations += tmpSolver.getInfo().simplex_iteration_count; if (tmpSolver.getInfo().primal_solution_status == kSolutionStatusFeasible) { @@ -1259,7 +1313,7 @@ double HighsMipSolverData::percentageInactiveIntegers() const { void HighsMipSolverData::performRestart() { HighsBasis root_basis; HighsPseudocostInitialization pscostinit( - pseudocost, mipsolver.options_mip_->mip_pscost_minreliable, + getPseudoCost(), mipsolver.options_mip_->mip_pscost_minreliable, postSolveStack); mipsolver.pscostinit = &pscostinit; @@ -1270,13 +1324,13 @@ void HighsMipSolverData::performRestart() { heuristic_lp_iterations_before_run = heuristic_lp_iterations; sepa_lp_iterations_before_run = sepa_lp_iterations; sb_lp_iterations_before_run = sb_lp_iterations; - HighsInt numLpRows = lp.getLp().num_row_; + HighsInt numLpRows = getLp().getLp().num_row_; HighsInt numModelRows = mipsolver.numRow(); HighsInt numCuts = numLpRows - numModelRows; if (numCuts > 0) postSolveStack.appendCutsToModel(numCuts); auto integrality = std::move(presolvedModel.integrality_); double offset = presolvedModel.offset_; - presolvedModel = lp.getLp(); + presolvedModel = getLp().getLp(); presolvedModel.offset_ = offset; presolvedModel.integrality_ = std::move(integrality); #ifdef HIGHS_DEBUGSOL @@ -1372,8 +1426,8 @@ void HighsMipSolverData::performRestart() { // updatePrimalDualIntegral (unless solving a sub-MIP) // // Surely there must be a lower bound change - updateLowerBound(upper_bound); - + updateLowerBound(upper_bound, true, + mipsolver.modelstatus_ != HighsModelStatus::kOptimal); if (mipsolver.solution_objective_ != kHighsInf && mipsolver.modelstatus_ == HighsModelStatus::kInfeasible) mipsolver.modelstatus_ = HighsModelStatus::kOptimal; @@ -1392,6 +1446,19 @@ void HighsMipSolverData::performRestart() { // HighsNodeQueue oldNodeQueue; // std::swap(nodequeue, oldNodeQueue); + // Ensure master worker is pointing to the correct cut and conflict pools + if (mipsolver.options_mip_->mip_search_concurrency >= 1) { + workers[0].setCutPool(&getCutPool()); + workers[0].setConflictPool(&getConflictPool()); + workers[0].setGlobalDomain(&getDomain()); + workers[0].setPseudocost(&getPseudoCost()); + } + if (!workers.empty()) { + workers[0].upper_bound = upper_bound; + workers[0].upper_limit = upper_limit; + workers[0].optimality_limit = optimality_limit; + } + // remove the pointer into the stack-space of this function if (mipsolver.rootbasis == &root_basis) mipsolver.rootbasis = nullptr; mipsolver.pscostinit = nullptr; @@ -1431,6 +1498,7 @@ bool HighsMipSolverData::addIncumbent(const std::vector& sol, double solobj, const int solution_source, const bool print_display_line, const bool is_user_solution) { + assert(!parallelLockActive()); const bool execute_mip_solution_callback = !is_user_solution && !mipsolver.submip && (mipsolver.callback_->user_callback @@ -1476,6 +1544,9 @@ bool HighsMipSolverData::addIncumbent(const std::vector& sol, double prev_upper_bound = upper_bound; upper_bound = solobj; + for (HighsMipWorker& worker : workers) { + worker.upper_bound = upper_bound; + } bool bound_change = upper_bound != prev_upper_bound; if (!mipsolver.submip && bound_change) @@ -1495,15 +1566,20 @@ bool HighsMipSolverData::addIncumbent(const std::vector& sol, computeNewUpperLimit(solobj, mipsolver.options_mip_->mip_abs_gap, mipsolver.options_mip_->mip_rel_gap); nodequeue.setOptimalityLimit(optimality_limit); + for (HighsMipWorker& worker : workers) { + worker.upper_limit = upper_limit; + worker.optimality_limit = optimality_limit; + } debugSolution.newIncumbentFound(); - domain.propagate(); - if (!domain.infeasible()) redcostfixing.propagateRootRedcost(mipsolver); + getDomain().propagate(); + if (!getDomain().infeasible()) + redcostfixing.propagateRootRedcost(mipsolver); // Two calls to printDisplayLine added for completeness, // ensuring that when the root node has an integer solution, a // logging line is issued - if (domain.infeasible()) { + if (getDomain().infeasible()) { pruned_treeweight = 1.0; nodequeue.clear(); if (print_display_line) @@ -1511,7 +1587,7 @@ bool HighsMipSolverData::addIncumbent(const std::vector& sol, return true; } cliquetable.extractObjCliques(mipsolver); - if (domain.infeasible()) { + if (getDomain().infeasible()) { pruned_treeweight = 1.0; nodequeue.clear(); if (print_display_line) @@ -1692,7 +1768,7 @@ void HighsMipSolverData::printDisplayLine(const int solution_source) { auto print_lp_iters = convertToPrintString(total_lp_iterations); HighsInt dynamic_constraints_in_lp = - lp.numRows() > 0 ? lp.numRows() - lp.getNumModelRows() : 0; + getLp().numRows() > 0 ? getLp().numRows() - getLp().getNumModelRows() : 0; if (upper_bound != kHighsInf) { std::array gap_string = {}; if (gap >= 9999.) @@ -1717,8 +1793,8 @@ void HighsMipSolverData::printDisplayLine(const int solution_source) { // clang-format on solutionSourceToString(solution_source).c_str(), print_nodes.data(), queue_nodes.data(), print_leaves.data(), explored, lb_string.data(), - ub_string.data(), gap_string.data(), cutpool.getNumCuts(), - dynamic_constraints_in_lp, conflictPool.getNumConflicts(), + ub_string.data(), gap_string.data(), getCutPool().getNumCuts(), + dynamic_constraints_in_lp, getConflictPool().getNumConflicts(), print_lp_iters.data(), time_string.c_str()); } else { std::array ub_string; @@ -1738,9 +1814,9 @@ void HighsMipSolverData::printDisplayLine(const int solution_source) { // clang-format on solutionSourceToString(solution_source).c_str(), print_nodes.data(), queue_nodes.data(), print_leaves.data(), explored, lb_string.data(), - ub_string.data(), gap, cutpool.getNumCuts(), dynamic_constraints_in_lp, - conflictPool.getNumConflicts(), print_lp_iters.data(), - time_string.c_str()); + ub_string.data(), gap, getCutPool().getNumCuts(), + dynamic_constraints_in_lp, getConflictPool().getNumConflicts(), + print_lp_iters.data(), time_string.c_str()); } // Check that limitsToBounds yields the same values for the // dual_bound, primal_bound (modulo optimization sense) and @@ -1762,39 +1838,42 @@ void HighsMipSolverData::printDisplayLine(const int solution_source) { } bool HighsMipSolverData::rootSeparationRound( - HighsSeparation& sepa, HighsInt& ncuts, HighsLpRelaxation::Status& status) { - int64_t tmpLpIters = -lp.getNumLpIterations(); - ncuts = sepa.separationRound(domain, status); - tmpLpIters += lp.getNumLpIterations(); - avgrootlpiters = lp.getAvgSolveIters(); + HighsMipWorker& worker, HighsSeparation& sepa, HighsInt& ncuts, + HighsLpRelaxation::Status& status) { + int64_t tmpLpIters = -getLp().getNumLpIterations(); + ncuts = sepa.separationRound(getDomain(), status); + tmpLpIters += getLp().getNumLpIterations(); + avgrootlpiters = getLp().getAvgSolveIters(); total_lp_iterations += tmpLpIters; sepa_lp_iterations += tmpLpIters; - status = evaluateRootLp(); + status = evaluateRootLp(worker); if (status == HighsLpRelaxation::Status::kInfeasible) return true; - const std::vector& solvals = lp.getLpSolver().getSolution().col_value; + const std::vector& solvals = + getLp().getLpSolver().getSolution().col_value; if (mipsolver.submip || incumbent.empty()) { - heuristics.randomizedRounding(solvals); + heuristics.randomizedRounding(worker, solvals); if (mipsolver.options_mip_->mip_heuristic_run_shifting) - heuristics.shifting(solvals); - heuristics.flushStatistics(); - status = evaluateRootLp(); + heuristics.shifting(worker, solvals); + heuristics.flushStatistics(mipsolver, worker); + status = evaluateRootLp(worker); if (status == HighsLpRelaxation::Status::kInfeasible) return true; } return false; } -HighsLpRelaxation::Status HighsMipSolverData::evaluateRootLp() { +HighsLpRelaxation::Status HighsMipSolverData::evaluateRootLp( + HighsMipWorker& worker) { do { - domain.propagate(); + getDomain().propagate(); - if (globalOrbits && !domain.infeasible()) - globalOrbits->orbitalFixing(domain); + if (globalOrbits && !getDomain().infeasible()) + globalOrbits->orbitalFixing(getDomain()); - if (domain.infeasible()) { + if (getDomain().infeasible()) { updateLowerBound(std::min(kHighsInf, upper_bound)); pruned_treeweight = 1.0; num_nodes += 1; @@ -1803,21 +1882,21 @@ HighsLpRelaxation::Status HighsMipSolverData::evaluateRootLp() { } bool lpBoundsChanged = false; - if (!domain.getChangedCols().empty()) { + if (!getDomain().getChangedCols().empty()) { lpBoundsChanged = true; removeFixedIndices(); - lp.flushDomain(domain); + getLp().flushDomain(getDomain()); } bool lpWasSolved = false; HighsLpRelaxation::Status status; if (lpBoundsChanged || - lp.getLpSolver().getModelStatus() == HighsModelStatus::kNotset) { - int64_t lpIters = -lp.getNumLpIterations(); - status = lp.resolveLp(&domain); - lpIters += lp.getNumLpIterations(); + getLp().getLpSolver().getModelStatus() == HighsModelStatus::kNotset) { + int64_t lpIters = -getLp().getNumLpIterations(); + status = getLp().resolveLp(&getDomain()); + lpIters += getLp().getNumLpIterations(); total_lp_iterations += lpIters; - avgrootlpiters = lp.getAvgSolveIters(); + avgrootlpiters = getLp().getAvgSolveIters(); lpWasSolved = true; if (status == HighsLpRelaxation::Status::kUnbounded) { @@ -1833,9 +1912,9 @@ HighsLpRelaxation::Status HighsMipSolverData::evaluateRootLp() { } if (status == HighsLpRelaxation::Status::kOptimal && - lp.getFractionalIntegers().empty() && - addIncumbent(lp.getLpSolver().getSolution().col_value, - lp.getObjective(), kSolutionSourceEvaluateNode)) { + getLp().getFractionalIntegers().empty() && + addIncumbent(getLp().getLpSolver().getSolution().col_value, + getLp().getObjective(), kSolutionSourceEvaluateNode)) { mipsolver.modelstatus_ = HighsModelStatus::kOptimal; updateLowerBound(upper_bound); pruned_treeweight = 1.0; @@ -1846,10 +1925,11 @@ HighsLpRelaxation::Status HighsMipSolverData::evaluateRootLp() { if (status == HighsLpRelaxation::Status::kOptimal && mipsolver.options_mip_->mip_heuristic_run_zi_round) - heuristics.ziRound(lp.getLpSolver().getSolution().col_value); + heuristics.ziRound(worker, + getLp().getLpSolver().getSolution().col_value); } else - status = lp.getStatus(); + status = getLp().getStatus(); if (status == HighsLpRelaxation::Status::kInfeasible) { updateLowerBound(std::min(kHighsInf, upper_bound)); @@ -1859,13 +1939,13 @@ HighsLpRelaxation::Status HighsMipSolverData::evaluateRootLp() { return status; } - if (lp.unscaledDualFeasible(lp.getStatus())) { - updateLowerBound(std::max(lp.getObjective(), lower_bound)); + if (getLp().unscaledDualFeasible(getLp().getStatus())) { + updateLowerBound(std::max(getLp().getObjective(), lower_bound)); if (lpWasSolved) { - redcostfixing.addRootRedcost(mipsolver, - lp.getLpSolver().getSolution().col_dual, - lp.getObjective()); + redcostfixing.addRootRedcost( + mipsolver, getLp().getLpSolver().getSolution().col_dual, + getLp().getObjective()); if (upper_limit != kHighsInf) redcostfixing.propagateRootRedcost(mipsolver); } @@ -1878,30 +1958,31 @@ HighsLpRelaxation::Status HighsMipSolverData::evaluateRootLp() { return HighsLpRelaxation::Status::kInfeasible; } - if (domain.getChangedCols().empty()) return status; + if (getDomain().getChangedCols().empty()) return status; } while (true); } -static void clockOff(HighsMipAnalysis& analysis) { - if (!analysis.analyse_mip_time) return; +static void clockOff(HighsProfiling* profiling) { + if (!profiling->mip_) return; + if (profiling->isSubMip()) return; // Make sure that exactly one of the following clocks is running const int clock0_running = - analysis.mipTimerRunning(kMipClockEvaluateRootNode0) ? 1 : 0; + profiling->running(kMipClockEvaluateRootNode0) ? 1 : 0; const int clock1_running = - analysis.mipTimerRunning(kMipClockEvaluateRootNode1) ? 1 : 0; + profiling->running(kMipClockEvaluateRootNode1) ? 1 : 0; const int clock2_running = - analysis.mipTimerRunning(kMipClockEvaluateRootNode2) ? 1 : 0; + profiling->running(kMipClockEvaluateRootNode2) ? 1 : 0; const bool one_running = clock0_running + clock1_running + clock2_running; if (!one_running) printf("HighsMipSolverData::clockOff Clocks running are (%d; %d; %d)\n", clock0_running, clock1_running, clock2_running); assert(one_running); - if (clock0_running) analysis.mipTimerStop(kMipClockEvaluateRootNode0); - if (clock1_running) analysis.mipTimerStop(kMipClockEvaluateRootNode1); - if (clock2_running) analysis.mipTimerStop(kMipClockEvaluateRootNode2); + if (clock0_running) profiling->stop(kMipClockEvaluateRootNode0); + if (clock1_running) profiling->stop(kMipClockEvaluateRootNode1); + if (clock2_running) profiling->stop(kMipClockEvaluateRootNode2); } -void HighsMipSolverData::evaluateRootNode() { +void HighsMipSolverData::evaluateRootNode(HighsMipWorker& worker) { const bool compute_analytic_centre = true; if (!compute_analytic_centre) printf("NOT COMPUTING ANALYTIC CENTRE!\n"); HighsInt maxSepaRounds = mipsolver.submip ? 5 : kHighsIInf; @@ -1910,34 +1991,34 @@ void HighsMipSolverData::evaluateRootNode() { std::min(HighsInt(2 * std::sqrt(maxTreeSizeLog2)), maxSepaRounds); std::unique_ptr symData; highs::parallel::TaskGroup tg; - HighsMipAnalysis& analysis = mipsolver.analysis_; + HighsProfiling* profiling = mipsolver.profiling_; restart: - analysis.mipTimerStart(kMipClockEvaluateRootNode0); + profiling->start(kMipClockEvaluateRootNode0); if (detectSymmetries) { - analysis.mipTimerStart(kMipClockStartSymmetryDetection); + profiling->start(kMipClockStartSymmetryDetection); startSymmetryDetection(tg, symData); - analysis.mipTimerStop(kMipClockStartSymmetryDetection); + profiling->stop(kMipClockStartSymmetryDetection); } if (compute_analytic_centre && !analyticCenterComputed) { - if (analysis.analyse_mip_time) + if (profiling->mip_) highsLogUser( mipsolver.options_mip_->log_options, HighsLogType::kInfo, "MIP-Timing: %11.2g - starting analytic centre calculation\n", mipsolver.timer_.read()); - analysis.mipTimerStart(kMipClockStartAnalyticCentreComputation); + profiling->start(kMipClockStartAnalyticCentreComputation); startAnalyticCenterComputation(tg); - analysis.mipTimerStop(kMipClockStartAnalyticCentreComputation); + profiling->stop(kMipClockStartAnalyticCentreComputation); } // lp.getLpSolver().setOptionValue( // "dual_simplex_cost_perturbation_multiplier", 10.0); - lp.setIterationLimit(); - lp.loadModel(); - domain.clearChangedCols(); - lp.setObjectiveLimit(upper_limit); + getLp().setIterationLimit(); + getLp().loadModel(); + getDomain().clearChangedCols(); + getLp().setObjectiveLimit(upper_limit); - updateLowerBound(std::max(lower_bound, domain.getObjectiveLowerBound())); + updateLowerBound(std::max(lower_bound, getDomain().getObjectiveLowerBound())); printDisplayLine(); @@ -1949,38 +2030,39 @@ void HighsMipSolverData::evaluateRootNode() { // check if only root presolve is allowed if (firstrootbasis.valid) - lp.getLpSolver().setBasis(firstrootbasis, - "HighsMipSolverData::evaluateRootNode"); + getLp().getLpSolver().setBasis(firstrootbasis, + "HighsMipSolverData::evaluateRootNode"); else if (mipsolver.options_mip_->mip_root_presolve_only) - lp.getLpSolver().setOptionValue("presolve", kHighsOffString); + getLp().getLpSolver().setOptionValue("presolve", kHighsOffString); else - lp.getLpSolver().setOptionValue("presolve", kHighsOnString); + getLp().getLpSolver().setOptionValue("presolve", kHighsOnString); if (mipsolver.options_mip_->highs_debug_level) - lp.getLpSolver().setOptionValue("output_flag", - mipsolver.options_mip_->output_flag); + getLp().getLpSolver().setOptionValue("output_flag", + mipsolver.options_mip_->output_flag); // lp.getLpSolver().setOptionValue("log_dev_level", kHighsLogDevLevelInfo); // lp.getLpSolver().setOptionValue("log_file", // mipsolver.options_mip_->log_file); - analysis.mipTimerStart(kMipClockEvaluateRootLp); - HighsLpRelaxation::Status status = evaluateRootLp(); - analysis.mipTimerStop(kMipClockEvaluateRootLp); + profiling->start(kMipClockEvaluateRootLp); + HighsLpRelaxation::Status status = evaluateRootLp(worker); + profiling->stop(kMipClockEvaluateRootLp); if (numRestarts == 0) firstrootlpiters = total_lp_iterations; - lp.getLpSolver().setOptionValue("output_flag", false); - lp.getLpSolver().setOptionValue("presolve", kHighsOffString); - lp.getLpSolver().setOptionValue("parallel", kHighsOffString); + getLp().getLpSolver().setOptionValue("output_flag", false); + getLp().getLpSolver().setOptionValue("presolve", kHighsOffString); + getLp().getLpSolver().setOptionValue("parallel", kHighsOffString); if (status == HighsLpRelaxation::Status::kInfeasible || status == HighsLpRelaxation::Status::kUnbounded) - return clockOff(analysis); + return clockOff(profiling); - firstlpsol = lp.getSolution().col_value; - firstlpsolobj = lp.getObjective(); + firstlpsol = getLp().getSolution().col_value; + firstlpsolobj = getLp().getObjective(); rootlpsolobj = firstlpsolobj; - if (lp.getLpSolver().getBasis().valid && lp.numRows() == mipsolver.numRow()) - firstrootbasis = lp.getLpSolver().getBasis(); + if (getLp().getLpSolver().getBasis().valid && + getLp().numRows() == mipsolver.numRow()) + firstrootbasis = getLp().getLpSolver().getBasis(); else { // the root basis is later expected to be consistent for the model without // cuts so set it to the slack basis if the current basis already includes @@ -1993,12 +2075,12 @@ void HighsMipSolverData::evaluateRootNode() { firstrootbasis.useful = true; } - if (cutpool.getNumCuts() != 0) { + if (getCutPool().getNumCuts() != 0) { assert(numRestarts != 0); HighsCutSet cutset; - analysis.mipTimerStart(kMipClockSeparateLpCuts); - cutpool.separateLpCutsAfterRestart(cutset); - analysis.mipTimerStop(kMipClockSeparateLpCuts); + profiling->start(kMipClockSeparateLpCuts); + getCutPool().separateLpCutsAfterRestart(cutset); + profiling->stop(kMipClockSeparateLpCuts); #ifdef HIGHS_DEBUGSOL for (HighsInt i = 0; i < cutset.numCuts(); ++i) { debugSolution.checkCut(cutset.ARindex_.data() + cutset.ARstart_[i], @@ -2007,36 +2089,36 @@ void HighsMipSolverData::evaluateRootNode() { cutset.upper_[i]); } #endif - lp.addCuts(cutset); - analysis.mipTimerStart(kMipClockEvaluateRootLp); - status = evaluateRootLp(); - analysis.mipTimerStop(kMipClockEvaluateRootLp); - lp.removeObsoleteRows(); + getLp().addCuts(cutset); + profiling->start(kMipClockEvaluateRootLp); + status = evaluateRootLp(worker); + profiling->stop(kMipClockEvaluateRootLp); + getLp().removeObsoleteRows(); if (status == HighsLpRelaxation::Status::kInfeasible) - return clockOff(analysis); + return clockOff(profiling); } - lp.setIterationLimit(std::max(10000, int(10 * avgrootlpiters))); + getLp().setIterationLimit(std::max(10000, int(10 * avgrootlpiters))); // make sure first line after solving root LP is printed last_disptime = -kHighsInf; disptime = 0; if (mipsolver.options_mip_->mip_heuristic_run_zi_round) - heuristics.ziRound(firstlpsol); - analysis.mipTimerStart(kMipClockRandomizedRounding); - heuristics.randomizedRounding(firstlpsol); - analysis.mipTimerStop(kMipClockRandomizedRounding); + heuristics.ziRound(worker, firstlpsol); + profiling->start(kMipClockRandomizedRounding); + heuristics.randomizedRounding(worker, firstlpsol); + profiling->stop(kMipClockRandomizedRounding); if (mipsolver.options_mip_->mip_heuristic_run_shifting) - heuristics.shifting(firstlpsol); + heuristics.shifting(worker, firstlpsol); - heuristics.flushStatistics(); + heuristics.flushStatistics(mipsolver, worker); - analysis.mipTimerStart(kMipClockEvaluateRootLp); - status = evaluateRootLp(); - analysis.mipTimerStop(kMipClockEvaluateRootLp); + profiling->start(kMipClockEvaluateRootLp); + status = evaluateRootLp(worker); + profiling->stop(kMipClockEvaluateRootLp); if (status == HighsLpRelaxation::Status::kInfeasible) - return clockOff(analysis); + return clockOff(profiling); rootlpsolobj = firstlpsolobj; removeFixedIndices(); @@ -2049,27 +2131,27 @@ void HighsMipSolverData::evaluateRootNode() { "\n%.1f%% inactive integer columns, restarting\n", fixingRate); tg.taskWait(); - analysis.mipTimerStart(kMipClockPerformRestart); + profiling->start(kMipClockPerformRestart); performRestart(); - analysis.mipTimerStop(kMipClockPerformRestart); + profiling->stop(kMipClockPerformRestart); ++numRestartsRoot; if (mipsolver.modelstatus_ == HighsModelStatus::kNotset) { - clockOff(analysis); + clockOff(profiling); goto restart; } - return clockOff(analysis); + return clockOff(profiling); } } // begin separation - if (analysis.analyse_mip_time) { + if (profiling->mip_) { highsLogUser(mipsolver.options_mip_->log_options, HighsLogType::kInfo, "MIP-Timing: %11.2g - starting separation\n", - analysis.mip_clocks.timer_pointer_->read(0)); + mipsolver.timer_.read()); fflush(stdout); } - analysis.mipTimerStart(kMipClockRootSeparation); + profiling->start(kMipClockRootSeparation); std::vector avgdirection; std::vector curdirection; avgdirection.resize(mipsolver.numCol()); @@ -2078,16 +2160,16 @@ void HighsMipSolverData::evaluateRootNode() { HighsInt stall = 0; double smoothprogress = 0.0; HighsInt nseparounds = 0; - HighsSeparation sepa(mipsolver); - sepa.setLpRelaxation(&lp); + HighsSeparation sepa(worker); + sepa.setLpRelaxation(&getLp()); - while (lp.scaledOptimal(status) && !lp.getFractionalIntegers().empty() && - stall < 3) { + while (getLp().scaledOptimal(status) && + !getLp().getFractionalIntegers().empty() && stall < 3) { printDisplayLine(); if (checkLimits()) { - analysis.mipTimerStop(kMipClockRootSeparation); - return clockOff(analysis); + profiling->stop(kMipClockRootSeparation); + return clockOff(profiling); } if (nseparounds == maxSepaRounds) break; @@ -2107,47 +2189,45 @@ void HighsMipSolverData::evaluateRootNode() { HighsInt ncuts; - analysis.mipTimerStart(kMipClockRootSeparationRound); + profiling->start(kMipClockRootSeparationRound); const bool root_separation_round_result = - rootSeparationRound(sepa, ncuts, status); - analysis.mipTimerStop(kMipClockRootSeparationRound); + rootSeparationRound(worker, sepa, ncuts, status); + profiling->stop(kMipClockRootSeparationRound); if (root_separation_round_result) { - analysis.mipTimerStop(kMipClockRootSeparation); - return clockOff(analysis); + profiling->stop(kMipClockRootSeparation); + return clockOff(profiling); } if (nseparounds >= 5 && !mipsolver.submip && !analyticCenterComputed && compute_analytic_centre) { if (checkLimits()) { - analysis.mipTimerStop(kMipClockRootSeparation); - return clockOff(analysis); + profiling->stop(kMipClockRootSeparation); + return clockOff(profiling); } - analysis.mipTimerStart( - kMipClockRootSeparationFinishAnalyticCentreComputation); + profiling->start(kMipClockRootSeparationFinishAnalyticCentreComputation); finishAnalyticCenterComputation(tg); - analysis.mipTimerStop( - kMipClockRootSeparationFinishAnalyticCentreComputation); + profiling->stop(kMipClockRootSeparationFinishAnalyticCentreComputation); - analysis.mipTimerStart(kMipClockRootSeparationCentralRounding); - heuristics.centralRounding(); - analysis.mipTimerStop(kMipClockRootSeparationCentralRounding); + profiling->start(kMipClockRootSeparationCentralRounding); + heuristics.centralRounding(worker); + profiling->stop(kMipClockRootSeparationCentralRounding); - heuristics.flushStatistics(); + heuristics.flushStatistics(mipsolver, worker); if (checkLimits()) { - analysis.mipTimerStop(kMipClockRootSeparation); - return clockOff(analysis); + profiling->stop(kMipClockRootSeparation); + return clockOff(profiling); } - analysis.mipTimerStart(kMipClockRootSeparationEvaluateRootLp); - status = evaluateRootLp(); - analysis.mipTimerStop(kMipClockRootSeparationEvaluateRootLp); + profiling->start(kMipClockRootSeparationEvaluateRootLp); + status = evaluateRootLp(worker); + profiling->stop(kMipClockRootSeparationEvaluateRootLp); if (status == HighsLpRelaxation::Status::kInfeasible) { - analysis.mipTimerStop(kMipClockRootSeparation); - return clockOff(analysis); + profiling->stop(kMipClockRootSeparation); + return clockOff(profiling); } } HighsCDouble sqrnorm = 0.0; - const auto& solvals = lp.getSolution().col_value; + const auto& solvals = getLp().getSolution().col_value; for (HighsInt i = 0; i != mipsolver.numCol(); ++i) { curdirection[i] = firstlpsol[i] - solvals[i]; @@ -2180,7 +2260,7 @@ void HighsMipSolverData::evaluateRootNode() { double nextprogress = (1.0 - alpha) * smoothprogress + alpha * progress; if (nextprogress < smoothprogress * 1.01 && - (lp.getObjective() - firstlpsolobj) <= + (getLp().getObjective() - firstlpsolobj) <= (rootlpsolobj - firstlpsolobj) * 1.001) ++stall; else { @@ -2189,8 +2269,8 @@ void HighsMipSolverData::evaluateRootNode() { smoothprogress = nextprogress; } - rootlpsolobj = lp.getObjective(); - lp.setIterationLimit(std::max(10000, int(10 * avgrootlpiters))); + rootlpsolobj = getLp().getObjective(); + getLp().setIterationLimit(std::max(10000, int(10 * avgrootlpiters))); if (ncuts == 0) break; // Possibly query existence of an external solution @@ -2199,63 +2279,63 @@ void HighsMipSolverData::evaluateRootNode() { mipsolver.solution_objective_, kExternalMipSolutionQueryOriginEvaluateRootNode1); } - analysis.mipTimerStop(kMipClockRootSeparation); - if (analysis.analyse_mip_time) { + profiling->stop(kMipClockRootSeparation); + if (profiling->mip_) { highsLogUser(mipsolver.options_mip_->log_options, HighsLogType::kInfo, "MIP-Timing: %11.2g - completed separation\n", - analysis.mip_clocks.timer_pointer_->read(0)); + mipsolver.timer_.read()); fflush(stdout); } - lp.setIterationLimit(); - analysis.mipTimerStart(kMipClockEvaluateRootLp); - status = evaluateRootLp(); - analysis.mipTimerStop(kMipClockEvaluateRootLp); + getLp().setIterationLimit(); + profiling->start(kMipClockEvaluateRootLp); + status = evaluateRootLp(worker); + profiling->stop(kMipClockEvaluateRootLp); if (status == HighsLpRelaxation::Status::kInfeasible) - return clockOff(analysis); + return clockOff(profiling); - rootlpsol = lp.getLpSolver().getSolution().col_value; - rootlpsolobj = lp.getObjective(); - lp.setIterationLimit(std::max(10000, int(10 * avgrootlpiters))); + rootlpsol = getLp().getLpSolver().getSolution().col_value; + rootlpsolobj = getLp().getObjective(); + getLp().setIterationLimit(std::max(10000, int(10 * avgrootlpiters))); if (mipsolver.options_mip_->mip_heuristic_run_zi_round) { - heuristics.ziRound(firstlpsol); - heuristics.flushStatistics(); + heuristics.ziRound(worker, firstlpsol); + heuristics.flushStatistics(mipsolver, worker); } if (mipsolver.options_mip_->mip_heuristic_run_shifting) { - heuristics.shifting(rootlpsol); - heuristics.flushStatistics(); + heuristics.shifting(worker, rootlpsol); + heuristics.flushStatistics(mipsolver, worker); } if (!analyticCenterComputed && compute_analytic_centre) { - if (checkLimits()) return clockOff(analysis); + if (checkLimits()) return clockOff(profiling); - analysis.mipTimerStart(kMipClockFinishAnalyticCentreComputation); + profiling->start(kMipClockFinishAnalyticCentreComputation); finishAnalyticCenterComputation(tg); - analysis.mipTimerStop(kMipClockFinishAnalyticCentreComputation); + profiling->stop(kMipClockFinishAnalyticCentreComputation); - analysis.mipTimerStart(kMipClockRootCentralRounding); - heuristics.centralRounding(); - analysis.mipTimerStop(kMipClockRootCentralRounding); + profiling->start(kMipClockRootCentralRounding); + heuristics.centralRounding(worker); + profiling->stop(kMipClockRootCentralRounding); - heuristics.flushStatistics(); + heuristics.flushStatistics(mipsolver, worker); // if there are new global bound changes we re-evaluate the LP and do one // more separation round - if (checkLimits()) return clockOff(analysis); - bool separate = !domain.getChangedCols().empty(); - analysis.mipTimerStart(kMipClockEvaluateRootLp); - status = evaluateRootLp(); - analysis.mipTimerStop(kMipClockEvaluateRootLp); + if (checkLimits()) return clockOff(profiling); + bool separate = !getDomain().getChangedCols().empty(); + profiling->start(kMipClockEvaluateRootLp); + status = evaluateRootLp(worker); + profiling->stop(kMipClockEvaluateRootLp); if (status == HighsLpRelaxation::Status::kInfeasible) - return clockOff(analysis); - if (separate && lp.scaledOptimal(status)) { + return clockOff(profiling); + if (separate && getLp().scaledOptimal(status)) { HighsInt ncuts; - analysis.mipTimerStart(kMipClockRootSeparationRound0); + profiling->start(kMipClockRootSeparationRound0); const bool root_separation_round_result = - rootSeparationRound(sepa, ncuts, status); - analysis.mipTimerStop(kMipClockRootSeparationRound0); - if (root_separation_round_result) return clockOff(analysis); + rootSeparationRound(worker, sepa, ncuts, status); + profiling->stop(kMipClockRootSeparationRound0); + if (root_separation_round_result) return clockOff(profiling); ++nseparounds; printDisplayLine(); } @@ -2272,68 +2352,68 @@ void HighsMipSolverData::evaluateRootNode() { if (!mipsolver.submip && mipsolver.callback_->user_callback && mipsolver.callback_->callbackActive(kCallbackMipGetCutPool)) mipsolver.callbackGetCutPool(); - if (checkLimits()) return clockOff(analysis); + if (checkLimits()) return clockOff(profiling); - analysis.mipTimerStop(kMipClockEvaluateRootNode0); - analysis.mipTimerStart(kMipClockEvaluateRootNode1); + profiling->stop(kMipClockEvaluateRootNode0); + profiling->start(kMipClockEvaluateRootNode1); do { if (rootlpsol.empty()) break; if (upper_limit != kHighsInf && !moreHeuristicsAllowed()) break; if (mipsolver.options_mip_->mip_heuristic_run_root_reduced_cost) { - analysis.mipTimerStart(kMipClockRootHeuristicsReducedCost); - heuristics.rootReducedCost(); - analysis.mipTimerStop(kMipClockRootHeuristicsReducedCost); - heuristics.flushStatistics(); + profiling->start(kMipClockRootHeuristicsReducedCost); + heuristics.rootReducedCost(worker); + profiling->stop(kMipClockRootHeuristicsReducedCost); + heuristics.flushStatistics(mipsolver, worker); } - if (checkLimits()) return clockOff(analysis); + if (checkLimits()) return clockOff(profiling); // if there are new global bound changes we re-evaluate the LP and do one // more separation round - bool separate = !domain.getChangedCols().empty(); - analysis.mipTimerStart(kMipClockEvaluateRootLp); - status = evaluateRootLp(); - analysis.mipTimerStop(kMipClockEvaluateRootLp); + bool separate = !getDomain().getChangedCols().empty(); + profiling->start(kMipClockEvaluateRootLp); + status = evaluateRootLp(worker); + profiling->stop(kMipClockEvaluateRootLp); if (status == HighsLpRelaxation::Status::kInfeasible) - return clockOff(analysis); - if (separate && lp.scaledOptimal(status)) { + return clockOff(profiling); + if (separate && getLp().scaledOptimal(status)) { HighsInt ncuts; - analysis.mipTimerStart(kMipClockRootSeparationRound1); + profiling->start(kMipClockRootSeparationRound1); const bool root_separation_round_result = - rootSeparationRound(sepa, ncuts, status); - analysis.mipTimerStop(kMipClockRootSeparationRound1); - if (root_separation_round_result) return clockOff(analysis); + rootSeparationRound(worker, sepa, ncuts, status); + profiling->stop(kMipClockRootSeparationRound1); + if (root_separation_round_result) return clockOff(profiling); ++nseparounds; printDisplayLine(); } if (upper_limit != kHighsInf && !moreHeuristicsAllowed()) break; - if (checkLimits()) return clockOff(analysis); + if (checkLimits()) return clockOff(profiling); if (mipsolver.options_mip_->mip_heuristic_run_rens) { - analysis.mipTimerStart(kMipClockRootHeuristicsRens); - heuristics.RENS(rootlpsol); - analysis.mipTimerStop(kMipClockRootHeuristicsRens); - heuristics.flushStatistics(); + profiling->start(kMipClockRootHeuristicsRens); + heuristics.RENS(worker, rootlpsol); + profiling->stop(kMipClockRootHeuristicsRens); + heuristics.flushStatistics(mipsolver, worker); } - if (checkLimits()) return clockOff(analysis); + if (checkLimits()) return clockOff(profiling); // if there are new global bound changes we re-evaluate the LP and do one // more separation round - separate = !domain.getChangedCols().empty(); - analysis.mipTimerStart(kMipClockEvaluateRootLp); - status = evaluateRootLp(); - analysis.mipTimerStop(kMipClockEvaluateRootLp); + separate = !getDomain().getChangedCols().empty(); + profiling->start(kMipClockEvaluateRootLp); + status = evaluateRootLp(worker); + profiling->stop(kMipClockEvaluateRootLp); if (status == HighsLpRelaxation::Status::kInfeasible) - return clockOff(analysis); - if (separate && lp.scaledOptimal(status)) { + return clockOff(profiling); + if (separate && getLp().scaledOptimal(status)) { HighsInt ncuts; - analysis.mipTimerStart(kMipClockRootSeparationRound2); + profiling->start(kMipClockRootSeparationRound2); const bool root_separation_round_result = - rootSeparationRound(sepa, ncuts, status); - analysis.mipTimerStop(kMipClockRootSeparationRound2); - if (root_separation_round_result) return clockOff(analysis); + rootSeparationRound(worker, sepa, ncuts, status); + profiling->stop(kMipClockRootSeparationRound2); + if (root_separation_round_result) return clockOff(profiling); ++nseparounds; printDisplayLine(); @@ -2346,45 +2426,45 @@ void HighsMipSolverData::evaluateRootNode() { if (upper_limit != kHighsInf || mipsolver.submip) break; - if (checkLimits()) return clockOff(analysis); - analysis.mipTimerStart(kMipClockRootFeasibilityPump); - heuristics.feasibilityPump(); - analysis.mipTimerStop(kMipClockRootFeasibilityPump); - heuristics.flushStatistics(); + if (checkLimits()) return clockOff(profiling); + profiling->start(kMipClockRootFeasibilityPump); + heuristics.feasibilityPump(worker); + profiling->stop(kMipClockRootFeasibilityPump); + heuristics.flushStatistics(mipsolver, worker); - if (checkLimits()) return clockOff(analysis); - analysis.mipTimerStart(kMipClockEvaluateRootLp); - status = evaluateRootLp(); - analysis.mipTimerStop(kMipClockEvaluateRootLp); + if (checkLimits()) return clockOff(profiling); + profiling->start(kMipClockEvaluateRootLp); + status = evaluateRootLp(worker); + profiling->stop(kMipClockEvaluateRootLp); if (status == HighsLpRelaxation::Status::kInfeasible) - return clockOff(analysis); + return clockOff(profiling); } while (false); - analysis.mipTimerStop(kMipClockEvaluateRootNode1); - analysis.mipTimerStart(kMipClockEvaluateRootNode2); + profiling->stop(kMipClockEvaluateRootNode1); + profiling->start(kMipClockEvaluateRootNode2); if (lower_bound > upper_limit) { mipsolver.modelstatus_ = HighsModelStatus::kOptimal; pruned_treeweight = 1.0; num_nodes += 1; num_leaves += 1; - return clockOff(analysis); + return clockOff(profiling); } // if there are new global bound changes we re-evaluate the LP and do one // more separation round - bool separate = !domain.getChangedCols().empty(); - analysis.mipTimerStart(kMipClockEvaluateRootLp); - status = evaluateRootLp(); - analysis.mipTimerStop(kMipClockEvaluateRootLp); + bool separate = !getDomain().getChangedCols().empty(); + profiling->start(kMipClockEvaluateRootLp); + status = evaluateRootLp(worker); + profiling->stop(kMipClockEvaluateRootLp); if (status == HighsLpRelaxation::Status::kInfeasible) - return clockOff(analysis); - if (separate && lp.scaledOptimal(status)) { + return clockOff(profiling); + if (separate && getLp().scaledOptimal(status)) { HighsInt ncuts; - analysis.mipTimerStart(kMipClockRootSeparationRound3); + profiling->start(kMipClockRootSeparationRound3); const bool root_separation_round_result = - rootSeparationRound(sepa, ncuts, status); - analysis.mipTimerStop(kMipClockRootSeparationRound3); - if (root_separation_round_result) return clockOff(analysis); + rootSeparationRound(worker, sepa, ncuts, status); + profiling->stop(kMipClockRootSeparationRound3); + if (root_separation_round_result) return clockOff(profiling); ++nseparounds; printDisplayLine(); } @@ -2396,8 +2476,8 @@ void HighsMipSolverData::evaluateRootNode() { kExternalMipSolutionQueryOriginEvaluateRootNode4); removeFixedIndices(); - if (lp.getLpSolver().getBasis().valid) lp.removeObsoleteRows(); - rootlpsolobj = lp.getObjective(); + if (getLp().getLpSolver().getBasis().valid) getLp().removeObsoleteRows(); + rootlpsolobj = getLp().getObjective(); printDisplayLine(); @@ -2405,9 +2485,9 @@ void HighsMipSolverData::evaluateRootNode() { if (!mipsolver.submip && mipsolver.options_mip_->mip_allow_restart && mipsolver.options_mip_->presolve != kHighsOffString) { if (!analyticCenterComputed && compute_analytic_centre) { - analysis.mipTimerStart(kMipClockFinishAnalyticCentreComputation); + profiling->start(kMipClockFinishAnalyticCentreComputation); finishAnalyticCenterComputation(tg); - analysis.mipTimerStop(kMipClockFinishAnalyticCentreComputation); + profiling->stop(kMipClockFinishAnalyticCentreComputation); } double fixingRate = percentageInactiveIntegers(); if (fixingRate >= 2.5 + 7.5 * mipsolver.submip || @@ -2418,35 +2498,35 @@ void HighsMipSolverData::evaluateRootNode() { fixingRate); if (stall != -1) maxSepaRounds = std::min(maxSepaRounds, nseparounds); tg.taskWait(); - analysis.mipTimerStart(kMipClockPerformRestart); + profiling->start(kMipClockPerformRestart); performRestart(); - analysis.mipTimerStop(kMipClockPerformRestart); + profiling->stop(kMipClockPerformRestart); if (mipsolver.terminate()) return; ++numRestartsRoot; if (mipsolver.modelstatus_ == HighsModelStatus::kNotset) { - clockOff(analysis); + clockOff(profiling); goto restart; } - return clockOff(analysis); + return clockOff(profiling); } } if (detectSymmetries) { finishSymmetryDetection(tg, symData); - analysis.mipTimerStart(kMipClockEvaluateRootLp); - status = evaluateRootLp(); - analysis.mipTimerStop(kMipClockEvaluateRootLp); + profiling->start(kMipClockEvaluateRootLp); + status = evaluateRootLp(worker); + profiling->stop(kMipClockEvaluateRootLp); if (status == HighsLpRelaxation::Status::kInfeasible) - return clockOff(analysis); + return clockOff(profiling); } // add the root node to the nodequeue to initialize the search - nodequeue.emplaceNode(std::vector(), - std::vector(), lower_bound, - lp.computeBestEstimate(pseudocost), 1); + nodequeue.emplaceNode( + std::vector(), std::vector(), lower_bound, + getLp().computeBestEstimate(worker.getPseudocost()), 1); } // End of HighsMipSolverData::evaluateRootNode() - clockOff(analysis); + clockOff(profiling); } bool HighsMipSolverData::checkLimits(int64_t nodeOffset) const { @@ -2457,7 +2537,8 @@ bool HighsMipSolverData::checkLimits(int64_t nodeOffset) const { if (this->terminatorTerminated()) return true; // Possible user interrupt - if (!mipsolver.submip && mipsolver.callback_->user_callback) { + if (!mipsolver.submip && !parallelLockActive() && + mipsolver.callback_->user_callback) { mipsolver.callback_->clearHighsCallbackOutput(); if (interruptFromCallbackWithData(kCallbackMipInterrupt, mipsolver.solution_objective_, @@ -2561,7 +2642,7 @@ void HighsMipSolverData::setupDomainPropagation() { model.a_matrix_.index_, model.a_matrix_.value_, ARstart_, ARindex_, ARvalue_); - pseudocost = HighsPseudocost(mipsolver); + getPseudoCost() = HighsPseudocost(mipsolver); // compute the maximal absolute coefficients to filter propagation maxAbsRowCoef.resize(mipsolver.numRow()); @@ -2576,8 +2657,8 @@ void HighsMipSolverData::setupDomainPropagation() { maxAbsRowCoef[i] = maxabsval; } - domain = HighsDomain(mipsolver); - domain.computeRowActivities(); + getDomain() = HighsDomain(mipsolver); + getDomain().computeRowActivities(); } void HighsMipSolverData::saveReportMipSolution(const double new_upper_limit) { @@ -2626,13 +2707,15 @@ void HighsMipSolverData::limitsToBounds(double& dual_bound, } } -void HighsMipSolverData::updateLowerBound(double new_lower_bound) { +void HighsMipSolverData::updateLowerBound(double new_lower_bound, + const bool check_bound_change, + const bool check_prev_data) { // Update lower bound double prev_lower_bound = lower_bound; lower_bound = new_lower_bound; if (!mipsolver.submip && lower_bound != prev_lower_bound) updatePrimalDualIntegral(prev_lower_bound, lower_bound, upper_bound, - upper_bound); + upper_bound, check_bound_change, check_prev_data); } // Interface to callbackAction, with mipsolver_objective_value since diff --git a/highs/mip/HighsMipSolverData.h b/highs/mip/HighsMipSolverData.h index f06da7ca6b1..12a235147b3 100644 --- a/highs/mip/HighsMipSolverData.h +++ b/highs/mip/HighsMipSolverData.h @@ -18,6 +18,7 @@ #include "mip/HighsDomain.h" #include "mip/HighsImplications.h" #include "mip/HighsLpRelaxation.h" +#include "mip/HighsMipWorker.h" #include "mip/HighsNodeQueue.h" #include "mip/HighsObjectiveFunction.h" #include "mip/HighsPrimalHeuristics.h" @@ -68,14 +69,18 @@ enum MipSolutionSource : int { struct HighsMipSolverData { HighsMipSolver& mipsolver; - HighsCutPool cutpool; - HighsConflictPool conflictPool; - HighsDomain domain; - HighsLpRelaxation lp; - HighsPseudocost pseudocost; + + std::deque lps; + std::deque cutpools; + std::deque conflictpools; + std::deque domains; + std::deque pseudocosts; + std::deque workers; + bool parallel_lock; + + HighsPrimalHeuristics heuristics; HighsCliqueTable cliquetable; HighsImplications implications; - HighsPrimalHeuristics heuristics; HighsRedcostFixing redcostfixing; HighsObjectiveFunction objectiveFunction; presolve::HighsPostsolveStack postSolveStack; @@ -154,67 +159,7 @@ struct HighsMipSolverData { HighsDebugSol debugSolution; - HighsMipSolverData(HighsMipSolver& mipsolver) - : mipsolver(mipsolver), - cutpool(mipsolver.numCol(), mipsolver.options_mip_->mip_pool_age_limit, - mipsolver.options_mip_->mip_pool_soft_limit), - conflictPool(5 * mipsolver.options_mip_->mip_pool_age_limit, - mipsolver.options_mip_->mip_pool_soft_limit), - domain(mipsolver), - lp(mipsolver), - pseudocost(), - cliquetable(mipsolver.numCol()), - implications(mipsolver), - heuristics(mipsolver), - objectiveFunction(mipsolver), - presolve_status(HighsPresolveStatus::kNotSet), - cliquesExtracted(false), - rowMatrixSet(false), - analyticCenterComputed(false), - analyticCenterStatus(HighsModelStatus::kNotset), - detectSymmetries(false), - numRestarts(0), - numRestartsRoot(0), - numCliqueEntriesAfterPresolve(0), - numCliqueEntriesAfterFirstPresolve(0), - feastol(0.0), - epsilon(0.0), - heuristic_effort(0.0), - dispfreq(0), - firstlpsolobj(-kHighsInf), - rootlpsolobj(-kHighsInf), - numintegercols(0), - maxTreeSizeLog2(0), - pruned_treeweight(0), - avgrootlpiters(0.0), - disptime(0.0), - last_disptime(0.0), - firstrootlpiters(0), - num_nodes(0), - num_leaves(0), - num_leaves_before_run(0), - num_nodes_before_run(0), - total_repair_lp(0), - total_repair_lp_feasible(0), - total_repair_lp_iterations(0), - total_lp_iterations(0), - heuristic_lp_iterations(0), - sepa_lp_iterations(0), - sb_lp_iterations(0), - total_lp_iterations_before_run(0), - heuristic_lp_iterations_before_run(0), - sepa_lp_iterations_before_run(0), - sb_lp_iterations_before_run(0), - num_disp_lines(0), - numImprovingSols(0), - lower_bound(-kHighsInf), - upper_bound(kHighsInf), - upper_limit(kHighsInf), - optimality_limit(kHighsInf), - debugSolution(mipsolver) { - domain.addCutpool(cutpool); - domain.addConflictPool(conflictPool); - } + HighsMipSolverData(HighsMipSolver& mipsolver); bool solutionRowFeasible(const std::vector& solution) const; HighsModelStatus feasibilityJump(); @@ -266,10 +211,12 @@ struct HighsMipSolverData { const std::vector& solution) const; bool trySolution(const std::vector& solution, const int solution_source = kSolutionSourceNone); - bool rootSeparationRound(HighsSeparation& sepa, HighsInt& ncuts, - HighsLpRelaxation::Status& status); - HighsLpRelaxation::Status evaluateRootLp(); - void evaluateRootNode(); + bool rootSeparationRound(HighsMipWorker& worker, HighsSeparation& sepa, + HighsInt& ncuts, HighsLpRelaxation::Status& status); + HighsLpRelaxation::Status evaluateRootLp(HighsMipWorker& worker); + + void evaluateRootNode(HighsMipWorker& worker); + bool addIncumbent(const std::vector& sol, double solobj, const int solution_source, const bool print_display_line = true, @@ -293,7 +240,9 @@ struct HighsMipSolverData { bool checkLimits(int64_t nodeOffset = 0) const; void limitsToBounds(double& dual_bound, double& primal_bound, double& mip_rel_gap) const; - void updateLowerBound(double new_lower_bound); + void updateLowerBound(double new_lower_bound, + const bool check_bound_change = true, + const bool check_prev_data = true); void setCallbackDataOut(const double mipsolver_objective_value) const; bool interruptFromCallbackWithData(const int callback_type, const double mipsolver_objective_value, @@ -308,6 +257,23 @@ struct HighsMipSolverData { void terminatorTerminate(); bool terminatorTerminated() const; void terminatorReport() const; + + bool parallelLockActive() const { + return (parallel_lock && hasMultipleWorkers()); + } + + bool hasMultipleWorkers() const { return workers.size() > 1; } + + HighsDomain& getDomain() { return domains[0]; } + HighsConflictPool& getConflictPool() { return conflictpools[0]; } + HighsCutPool& getCutPool() { return cutpools[0]; } + HighsLpRelaxation& getLp() { return lps[0]; } + HighsPseudocost& getPseudoCost() { return pseudocosts[0]; } + const HighsDomain& getDomain() const { return domains[0]; } + const HighsConflictPool& getConflictPool() const { return conflictpools[0]; } + const HighsCutPool& getCutPool() const { return cutpools[0]; } + const HighsLpRelaxation& getLp() const { return lps[0]; } + const HighsPseudocost& getPseudoCost() const { return pseudocosts[0]; } }; #endif diff --git a/highs/mip/HighsMipWorker.cpp b/highs/mip/HighsMipWorker.cpp new file mode 100644 index 00000000000..4788905ddb1 --- /dev/null +++ b/highs/mip/HighsMipWorker.cpp @@ -0,0 +1,202 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/* */ +/* This file is part of the HiGHS linear optimization suite */ +/* */ +/* Available as open-source under the MIT License */ +/* */ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +#include "mip/HighsMipWorker.h" + +#include "mip/HighsMipSolverData.h" +#include "mip/MipTimer.h" + +HighsMipWorker::HighsMipWorker(const HighsMipSolver& mipsolver, + HighsLpRelaxation* lp, HighsDomain* domain, + HighsCutPool* cutpool, + HighsConflictPool* conflictpool, + HighsPseudocost* pseudocost) + : mipsolver_(mipsolver), + mipdata_(*mipsolver_.mipdata_), + lp_(lp), + globaldom_(domain), + cutpool_(cutpool), + conflictpool_(conflictpool), + pseudocost_(pseudocost), + randgen(mipsolver.options_mip_->random_seed) { + upper_bound = mipdata_.upper_bound; + upper_limit = mipdata_.upper_limit; + optimality_limit = mipdata_.optimality_limit; + heuristics_allowed = true; + search_ptr_ = + std::unique_ptr(new HighsSearch(*this, getPseudocost())); + sepa_ptr_ = std::unique_ptr(new HighsSeparation(*this)); + search_ptr_->setLpRelaxation(lp_); + sepa_ptr_->setLpRelaxation(lp_); +} + +const HighsMipSolver& HighsMipWorker::getMipSolver() const { + return mipsolver_; +} + +void HighsMipWorker::resetSearch() { + search_ptr_.reset(); + search_ptr_ = + std::unique_ptr(new HighsSearch(*this, getPseudocost())); + search_ptr_->setLpRelaxation(lp_); +} + +void HighsMipWorker::resetSepa() { + sepa_ptr_.reset(); + sepa_ptr_ = std::unique_ptr(new HighsSeparation(*this)); + sepa_ptr_->setLpRelaxation(lp_); +} + +bool HighsMipWorker::addIncumbent(const std::vector& sol, double solobj, + int solution_source) { + if (solobj < upper_bound) { + // Get the transformed objective and solution if required + const std::pair transformed_solobj = + transformNewIntegerFeasibleSolution(sol); + if (transformed_solobj.first && transformed_solobj.second < upper_bound) { + upper_bound = transformed_solobj.second; + double new_upper_limit = + mipdata_.computeNewUpperLimit(upper_bound, 0.0, 0.0); + if (new_upper_limit < upper_limit) { + upper_limit = new_upper_limit; + optimality_limit = mipdata_.computeNewUpperLimit( + upper_bound, mipsolver_.options_mip_->mip_abs_gap, + mipsolver_.options_mip_->mip_rel_gap); + } + } + // Can't repair solutions locally, so also buffer infeasible ones + solutions_.emplace_back(sol, solobj, solution_source); + } + return true; +} + +std::pair HighsMipWorker::transformNewIntegerFeasibleSolution( + const std::vector& sol) const { + HighsSolution solution; + solution.col_value = sol; + solution.value_valid = true; + + // Perform primal postsolve to get the original column values + mipsolver_.mipdata_->postSolveStack.undoPrimal(*mipsolver_.options_mip_, + solution, -1, true); + + // Determine the row values, as they aren't computed in primal + // postsolve + HighsStatus return_status = + calculateRowValuesQuad(*mipsolver_.orig_model_, solution); + if (kAllowDeveloperAssert) assert(return_status == HighsStatus::kOk); + + // compute the objective value in the original space + double bound_violation_ = 0; + double row_violation_ = 0; + double integrality_violation_ = 0; + + HighsCDouble mipsolver_quad_objective_value = 0; + + bool feasible = mipsolver_.solutionFeasible( + mipsolver_.orig_model_, solution.col_value, &solution.row_value, + bound_violation_, row_violation_, integrality_violation_, + mipsolver_quad_objective_value); + + const double transformed_solobj = static_cast( + static_cast(mipsolver_.orig_model_->sense_) * + mipsolver_quad_objective_value - + mipsolver_.model_->offset_); + + return std::make_pair(feasible, transformed_solobj); +} + +bool HighsMipWorker::trySolution(const std::vector& solution, + const int solution_source) { + if (static_cast(solution.size()) != mipsolver_.model_->num_col_) + return false; + + HighsCDouble obj = 0; + + for (HighsInt i = 0; i != mipsolver_.model_->num_col_; ++i) { + if (solution[i] < mipsolver_.model_->col_lower_[i] - mipdata_.feastol) + return false; + if (solution[i] > mipsolver_.model_->col_upper_[i] + mipdata_.feastol) + return false; + if (mipsolver_.variableType(i) == HighsVarType::kInteger && + fractionality(solution[i]) > mipdata_.feastol) + return false; + + obj += mipsolver_.colCost(i) * solution[i]; + } + + for (HighsInt i = 0; i != mipsolver_.model_->num_row_; ++i) { + double rowactivity = 0.0; + + HighsInt start = mipdata_.ARstart_[i]; + HighsInt end = mipdata_.ARstart_[i + 1]; + + for (HighsInt j = start; j != end; ++j) + rowactivity += solution[mipdata_.ARindex_[j]] * mipdata_.ARvalue_[j]; + + if (rowactivity > mipsolver_.rowUpper(i) + mipdata_.feastol) return false; + if (rowactivity < mipsolver_.rowLower(i) - mipdata_.feastol) return false; + } + + return addIncumbent(solution, static_cast(obj), solution_source); +} + +void HighsMipWorker::resetSepaStats() { + sepa_stats.numNeighbourhoodQueries = 0; + sepa_stats.sepa_lp_iterations = 0; +} + +void HighsMipWorker::updateHeurStatsLpIters(int64_t lp_iters, + int64_t total_repair_lp, + int64_t total_repair_lp_feasible, + int64_t total_repair_lp_iters) { + heur_stats.lp_iterations += lp_iters; + heur_stats.total_repair_lp += total_repair_lp; + heur_stats.total_repair_lp_feasible += total_repair_lp_feasible; + heur_stats.total_repair_lp_iterations += total_repair_lp_iters; +} + +void HighsMipWorker::updateHeurStatsInfeasObservations(double fixingRate) { + heur_stats.infeasObservations += fixingRate; + ++heur_stats.numInfeasObservations; +} + +void HighsMipWorker::updateHeurStatsSuccessObservations(double fixingRate) { + heur_stats.successObservations += fixingRate; + ++heur_stats.numSuccessObservations; +} + +void HighsMipWorker::getHeurStatsValues( + int64_t& total_repair_lp, int64_t& total_repair_lp_feasible, + int64_t& total_repair_lp_iterations, int64_t& lp_iterations, + double& successObservations, HighsInt& numSuccessObservations, + double& infeasObservations, HighsInt& numInfeasObservations, + HighsInt& max_submip_level, HighsModelStatus& termination_status) const { + total_repair_lp = heur_stats.total_repair_lp; + total_repair_lp_feasible = heur_stats.total_repair_lp_feasible; + total_repair_lp_iterations = heur_stats.total_repair_lp_iterations; + lp_iterations = heur_stats.lp_iterations; + successObservations = heur_stats.successObservations; + numSuccessObservations = heur_stats.numSuccessObservations; + infeasObservations = heur_stats.infeasObservations; + numInfeasObservations = heur_stats.numInfeasObservations; + max_submip_level = heur_stats.max_submip_level; + termination_status = heur_stats.termination_status_; +} + +void HighsMipWorker::resetHeurStats() { + heur_stats.total_repair_lp = 0; + heur_stats.total_repair_lp_feasible = 0; + heur_stats.total_repair_lp_iterations = 0; + heur_stats.lp_iterations = 0; + heur_stats.max_submip_level = 0; + heur_stats.termination_status_ = HighsModelStatus::kNotset; + heur_stats.successObservations = 0; + heur_stats.numSuccessObservations = 0; + heur_stats.infeasObservations = 0; + heur_stats.numInfeasObservations = 0; +} \ No newline at end of file diff --git a/highs/mip/HighsMipWorker.h b/highs/mip/HighsMipWorker.h new file mode 100644 index 00000000000..ebdf9ea0b21 --- /dev/null +++ b/highs/mip/HighsMipWorker.h @@ -0,0 +1,191 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/* */ +/* This file is part of the HiGHS linear optimization suite */ +/* */ +/* Available Hias open-source under the MIT License */ +/* */ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +#ifndef HIGHS_MIP_WORKER_H_ +#define HIGHS_MIP_WORKER_H_ + +#include "mip/HighsConflictPool.h" +#include "mip/HighsCutPool.h" +#include "mip/HighsImplications.h" +#include "mip/HighsLpRelaxation.h" +#include "mip/HighsMipSolver.h" +#include "mip/HighsNodeQueue.h" +#include "mip/HighsPrimalHeuristics.h" +#include "mip/HighsPseudocost.h" +#include "mip/HighsSeparation.h" + +class HighsSearch; + +class HighsMipWorker { + private: + struct SepaStatistics { + SepaStatistics() : numNeighbourhoodQueries(0), sepa_lp_iterations(0) {} + + int64_t numNeighbourhoodQueries; + int64_t sepa_lp_iterations; + }; + struct HeurStatistics { + HeurStatistics() + : total_repair_lp(0), + total_repair_lp_feasible(0), + total_repair_lp_iterations(0), + lp_iterations(0) { + successObservations = 0; + numSuccessObservations = 0; + infeasObservations = 0; + numInfeasObservations = 0; + max_submip_level = 0; + termination_status_ = HighsModelStatus::kNotset; + } + + int64_t total_repair_lp; + int64_t total_repair_lp_feasible; + int64_t total_repair_lp_iterations; + int64_t lp_iterations; + + double successObservations; + HighsInt numSuccessObservations; + double infeasObservations; + HighsInt numInfeasObservations; + HighsInt max_submip_level; + HighsModelStatus termination_status_; + }; + const HighsMipSolver& mipsolver_; + const HighsMipSolverData& mipdata_; + + HighsLpRelaxation* lp_; + HighsDomain* globaldom_; + HighsCutPool* cutpool_; + HighsConflictPool* conflictpool_; + HighsPseudocost* pseudocost_; + + bool heuristics_allowed; + HeurStatistics heur_stats; + SepaStatistics sepa_stats; + + public: + std::unique_ptr search_ptr_; + std::unique_ptr sepa_ptr_; + HighsNodeQueue nodequeue; + + double upper_bound; + double upper_limit; + double optimality_limit; + + std::vector, double, int>> solutions_; + + HighsRandom randgen; + + const HighsMipSolver& getMipSolver() const; + + HighsMipWorker(const HighsMipSolver& mipsolver, HighsLpRelaxation* lp, + HighsDomain* domain, HighsCutPool* cutpool, + HighsConflictPool* conflictpool, HighsPseudocost* pseudocost); + + ~HighsMipWorker() { + search_ptr_.reset(); + sepa_ptr_.reset(); + } + + void resetSearch(); + + void resetSepa(); + + HighsDomain& getGlobalDomain() const { return *globaldom_; } + + void setGlobalDomain(HighsDomain* globaldom) { globaldom_ = globaldom; } + + HighsPseudocost& getPseudocost() const { return *pseudocost_; } + + void setPseudocost(HighsPseudocost* pseudocost) { pseudocost_ = pseudocost; } + + HighsConflictPool& getConflictPool() const { return *conflictpool_; } + + void setConflictPool(HighsConflictPool* conflictpool) { + conflictpool_ = conflictpool; + } + + HighsCutPool& getCutPool() const { return *cutpool_; } + + void setCutPool(HighsCutPool* cutpool) { cutpool_ = cutpool; } + + HighsLpRelaxation& getLpRelaxation() const { return *lp_; } + + void setLpRelaxation(HighsLpRelaxation* lp) { lp_ = lp; } + + bool addIncumbent(const std::vector& sol, double solobj, + int solution_source); + + std::pair transformNewIntegerFeasibleSolution( + const std::vector& sol) const; + + bool trySolution(const std::vector& solution, int solution_source); + + void setAllowHeuristics(const bool allowed) { heuristics_allowed = allowed; } + + bool getAllowHeuristics() const { return heuristics_allowed; } + + int64_t& getNumNeighbourhoodQueries() { + return sepa_stats.numNeighbourhoodQueries; + } + + int64_t& getSepaLpIterations() { return sepa_stats.sepa_lp_iterations; } + + void resetSepaStats(); + + void updateHeurStatsLpIters(int64_t lp_iters, int64_t total_repair_lp, + int64_t total_repair_lp_feasible, + int64_t total_repair_lp_iters); + + void updateHeurStatsInfeasObservations(double fixingRate); + + void updateHeurStatsSuccessObservations(double fixingRate); + + int64_t& getHeurLpIterations() { return heur_stats.lp_iterations; } + + HighsInt& getHeurNumSuccessObservations() { + return heur_stats.numSuccessObservations; + } + + HighsInt& getHeurNumInfeasObservations() { + return heur_stats.numInfeasObservations; + } + + double& getHeurSuccessObservations() { + return heur_stats.successObservations; + } + + double& getHeurInfeasObservations() { return heur_stats.infeasObservations; } + + void setHeurTerminationStatus(HighsModelStatus status) { + heur_stats.termination_status_ = status; + } + + HighsModelStatus getHeurTerminationStatus() const { + return heur_stats.termination_status_; + } + + bool terminatorTerminated() const { + return heur_stats.termination_status_ != HighsModelStatus::kNotset; + } + + void updateHeurStatsMaxSubMipLevel(HighsInt new_level) { + heur_stats.max_submip_level = + std::max(new_level, heur_stats.max_submip_level); + } + + void getHeurStatsValues( + int64_t& total_repair_lp, int64_t& total_repair_lp_feasible, + int64_t& total_repair_lp_iterations, int64_t& lp_iterations, + double& successObservations, HighsInt& numSuccessObservations, + double& infeasObservations, HighsInt& numInfeasObservations, + HighsInt& max_submip_level, HighsModelStatus& termination_status) const; + + void resetHeurStats(); +}; + +#endif diff --git a/highs/mip/HighsPathSeparator.cpp b/highs/mip/HighsPathSeparator.cpp index 443a24b98bb..d4fa7ad8e14 100644 --- a/highs/mip/HighsPathSeparator.cpp +++ b/highs/mip/HighsPathSeparator.cpp @@ -530,7 +530,8 @@ void HighsPathSeparator::separateLpSolution(HighsLpRelaxation& lpRelaxation, inds.resize(numInds); if (transLp.untransform(cutVals, inds, rhs)) - success |= cutGen.finalizeAndAddCut(inds, cutVals, rhs); + success |= cutGen.finalizeAndAddCut(transLp.getGlobaldom(), + inds, cutVals, rhs); // printf("cut is violated for k = %d\n", k); break; diff --git a/highs/mip/HighsPrimalHeuristics.cpp b/highs/mip/HighsPrimalHeuristics.cpp index 6b5209a694f..db6054f6655 100644 --- a/highs/mip/HighsPrimalHeuristics.cpp +++ b/highs/mip/HighsPrimalHeuristics.cpp @@ -34,16 +34,11 @@ HighsPrimalHeuristics::HighsPrimalHeuristics(HighsMipSolver& mipsolver) : mipsolver(mipsolver), - total_repair_lp(0), - total_repair_lp_feasible(0), - total_repair_lp_iterations(0), - lp_iterations(0), - randgen(mipsolver.options_mip_->random_seed) { - successObservations = 0; - numSuccessObservations = 0; - infeasObservations = 0; - numInfeasObservations = 0; -} + successObservations(0.0), + numSuccessObservations(0), + infeasObservations(0.0), + numInfeasObservations(0), + randgen(mipsolver.options_mip_->random_seed) {} void HighsPrimalHeuristics::setupIntCols() { intcols = mipsolver.mipdata_->integer_cols; @@ -80,9 +75,10 @@ void HighsPrimalHeuristics::setupIntCols() { } bool HighsPrimalHeuristics::solveSubMip( - const HighsLp& lp, const HighsBasis& basis, double fixingRate, - std::vector colLower, std::vector colUpper, - HighsInt maxleaves, HighsInt maxnodes, HighsInt stallnodes) { + HighsMipWorker& worker, const HighsLp& lp, const HighsBasis& basis, + double fixingRate, std::vector colLower, + std::vector colUpper, HighsInt maxleaves, HighsInt maxnodes, + HighsInt stallnodes) { HighsOptions submipoptions = *mipsolver.options_mip_; HighsLp submip = lp; @@ -99,21 +95,21 @@ bool HighsPrimalHeuristics::solveSubMip( const bool allow_submip_log = true; if (allow_submip_log && lp.num_col_ == -54 && lp.num_row_ == -172) { submipoptions.output_flag = true; - printf( - "HighsPrimalHeuristics::solveSubMip (%d, %d) with output_flag = %s\n", - int(lp.num_col_), int(lp.num_row_), - highsBoolToString(submipoptions.output_flag).c_str()); + if (mipsolver.profiling_->sub_solver_) + printf( + "HighsPrimalHeuristics::solveSubMip (%d, %d) with output_flag = %s\n", + int(lp.num_col_), int(lp.num_row_), + highsBoolToString(submipoptions.output_flag).c_str()); } submipoptions.mip_max_nodes = maxnodes; submipoptions.mip_max_stall_nodes = stallnodes; submipoptions.mip_pscost_minreliable = 0; submipoptions.time_limit -= mipsolver.timer_.read(); - submipoptions.objective_bound = mipsolver.mipdata_->upper_limit; + submipoptions.objective_bound = worker.upper_limit; if (!mipsolver.submip) { - double curr_abs_gap = - mipsolver.mipdata_->upper_limit - mipsolver.mipdata_->lower_bound; + double curr_abs_gap = worker.upper_limit - mipsolver.mipdata_->lower_bound; if (curr_abs_gap == kHighsInf) { curr_abs_gap = fabs(mipsolver.mipdata_->lower_bound); @@ -137,12 +133,10 @@ bool HighsPrimalHeuristics::solveSubMip( HighsSolution solution; solution.value_valid = false; solution.dual_valid = false; - if (!mipsolver.submip) { - mipsolver.analysis_.mipTimerStart(kMipClockSubMipSolve); - // Remember to accumulate time for sub-MIP solves! - mipsolver.sub_solver_call_time_.run_time[kSubSolverSubMip] -= - mipsolver.timer_.read(); + if (!mipsolver.submip && !mipsolver.mipdata_->parallelLockActive()) { + mipsolver.profiling_->start(kMipClockSubMipSolve); } + mipsolver.profiling_->solveCall("MIP", mipsolver.submip); // Create HighsMipSolver instance for sub-MIP HighsMipSolver submipsolver(*mipsolver.callback_, submipoptions, submip, solution, true, mipsolver.submip_level + 1); @@ -150,19 +144,52 @@ bool HighsPrimalHeuristics::solveSubMip( // the sub-MIP submipsolver.initialiseTerminator(mipsolver); submipsolver.rootbasis = &basis; - HighsPseudocostInitialization pscostinit(mipsolver.mipdata_->pseudocost, 1); + HighsPseudocostInitialization pscostinit(worker.getPseudocost(), 1); submipsolver.pscostinit = &pscostinit; submipsolver.clqtableinit = &mipsolver.mipdata_->cliquetable; submipsolver.implicinit = &mipsolver.mipdata_->implications; // Solve the sub-MIP + // + // Copy the pointer to global sub-solver data into the sub-MIP + // solver + submipsolver.setProfiling(mipsolver.profiling_); + // Stop the solve timer so that presolve/solve/postsolve for the + // sub-MIP are timed independently + const bool was_running_solve = mipsolver.profiling_->running(kSolveTime); + if (was_running_solve) mipsolver.profiling_->stop(kSolveTime); + // Only start timing the submip if the calling MIP isn't a sub-MIP + if (mipsolver.profiling_->sub_solver_) + printf( + "\nHighsPrimalHeuristics::solveSubMip Before run() for %sMIP at depth " + "%2d on thread %2d\n", + mipsolver.submip ? "sub-" : " ", int(mipsolver.submip_level), + int(mipsolver.profiling_->myThread())); + if (!mipsolver.submip) mipsolver.profiling_->start(kSubSolverSubMip); + // Ensure that sub-solver call time data accumulate in the sub-MIP record + mipsolver.profiling_->setSubMip(true); submipsolver.run(); - mipsolver.max_submip_level = - std::max(submipsolver.max_submip_level + 1, mipsolver.max_submip_level); - if (!mipsolver.submip) { - mipsolver.analysis_.mipTimerStop(kMipClockSubMipSolve); - mipsolver.sub_solver_call_time_.num_call[kSubSolverSubMip]++; - mipsolver.sub_solver_call_time_.run_time[kSubSolverSubMip] += - mipsolver.timer_.read(); + if (mipsolver.profiling_->sub_solver_) + printf( + "HighsPrimalHeuristics::solveSubMip After run() for %sMIP at depth " + "%2d " + "on thread %2d\n\n", + mipsolver.submip ? "sub-" : " ", int(mipsolver.submip_level), + int(mipsolver.profiling_->myThread())); + // Ensure that further sub-solver call time data accumulate in the + // MIP or sub-MIP record, according to whether the calling MIP is a + // sub-MIP + mipsolver.profiling_->setSubMip(mipsolver.submip); + if (!mipsolver.submip) mipsolver.profiling_->stop(kSubSolverSubMip); + worker.updateHeurStatsMaxSubMipLevel(submipsolver.max_submip_level + 1); + if (!mipsolver.submip && !mipsolver.mipdata_->parallelLockActive()) { + // Only stop timing the submip if the calling MIP isn't a sub-MIP + mipsolver.profiling_->stop(kMipClockSubMipSolve); + } + if (was_running_solve) { + // Re-start the solve timer now that presolve/solve/postsolve for the + // sub-MIP have been timed independently + const bool restart = true; + mipsolver.profiling_->start(kSolveTime, restart); } // 22/07/25: Seems impossible for submipsolver.mipdata_ to be a null // pointer after calling HighsMipSolver::run(), and assert isn't @@ -176,7 +203,7 @@ bool HighsPrimalHeuristics::solveSubMip( assert(submipsolver.mipdata_); } if (submipsolver.termination_status_ != HighsModelStatus::kNotset) { - mipsolver.termination_status_ = submipsolver.termination_status_; + worker.setHeurTerminationStatus(submipsolver.termination_status_); return false; } if (submipsolver.mipdata_) { @@ -189,51 +216,54 @@ bool HighsPrimalHeuristics::solveSubMip( // (double)mipsolver.orig_model_->a_matrix_.value_.size(); int64_t adjusted_lp_iterations = (size_t)(adjustmentfactor * submipsolver.mipdata_->total_lp_iterations); - lp_iterations += adjusted_lp_iterations; - total_repair_lp += submipsolver.mipdata_->total_repair_lp; - total_repair_lp_feasible += submipsolver.mipdata_->total_repair_lp_feasible; - total_repair_lp_iterations += - submipsolver.mipdata_->total_repair_lp_iterations; + worker.updateHeurStatsLpIters( + adjusted_lp_iterations, submipsolver.mipdata_->total_repair_lp, + submipsolver.mipdata_->total_repair_lp_feasible, + submipsolver.mipdata_->total_repair_lp_iterations); + // Warning: This will not be deterministic if sub-mips are run in parallel if (mipsolver.submip) mipsolver.mipdata_->num_nodes += std::max( int64_t{1}, int64_t(adjustmentfactor * submipsolver.node_count_)); } if (submipsolver.modelstatus_ == HighsModelStatus::kInfeasible) { - infeasObservations += fixingRate; - ++numInfeasObservations; + worker.updateHeurStatsInfeasObservations(fixingRate); } if (submipsolver.node_count_ <= 1 && submipsolver.modelstatus_ == HighsModelStatus::kInfeasible) return false; - HighsInt oldNumImprovingSols = mipsolver.mipdata_->numImprovingSols; + double oldUpperLimit = worker.upper_limit; if (submipsolver.modelstatus_ != HighsModelStatus::kInfeasible && !submipsolver.solution_.empty()) { - mipsolver.mipdata_->trySolution(submipsolver.solution_, - kSolutionSourceSubMip); + trySolution(submipsolver.solution_, kSolutionSourceSubMip, worker); } - if (mipsolver.mipdata_->numImprovingSols != oldNumImprovingSols) { + if (worker.upper_limit < oldUpperLimit) { // remember fixing rate as good - successObservations += fixingRate; - ++numSuccessObservations; + worker.updateHeurStatsSuccessObservations(fixingRate); } return true; } -double HighsPrimalHeuristics::determineTargetFixingRate() { +double HighsPrimalHeuristics::determineTargetFixingRate( + HighsMipWorker& worker) { double lowFixingRate = 0.6; double highFixingRate = 0.6; - if (numInfeasObservations != 0) { - double infeasRate = infeasObservations / numInfeasObservations; + HighsRandom& randgen = + mipsolver.mipdata_->parallelLockActive() ? worker.randgen : this->randgen; + + if (getNumInfeasObservations(worker) != 0) { + double infeasRate = + getInfeasObservations(worker) / getNumInfeasObservations(worker); highFixingRate = 0.9 * infeasRate; lowFixingRate = std::min(lowFixingRate, highFixingRate); } - if (numSuccessObservations != 0) { - double successFixingRate = successObservations / numSuccessObservations; + if (getNumSuccessObservations(worker) != 0) { + double successFixingRate = + getSuccessObservations(worker) / getNumSuccessObservations(worker); lowFixingRate = std::min(lowFixingRate, 0.9 * successFixingRate); highFixingRate = std::max(successFixingRate * 1.1, highFixingRate); } @@ -252,7 +282,7 @@ class HeuristicNeighbourhood { HighsInt numTotal; public: - HeuristicNeighbourhood(HighsMipSolver& mipsolver, HighsDomain& localdom) + HeuristicNeighbourhood(const HighsMipSolver& mipsolver, HighsDomain& localdom) : localdom(localdom), numFixed(0), startCheckedChanges(localdom.getDomainChangeStack().size()), @@ -281,9 +311,10 @@ class HeuristicNeighbourhood { } }; -void HighsPrimalHeuristics::rootReducedCost() { +void HighsPrimalHeuristics::rootReducedCost(HighsMipWorker& worker) { std::vector> lurkingBounds = - mipsolver.mipdata_->redcostfixing.getLurkingBounds(mipsolver); + mipsolver.mipdata_->redcostfixing.getLurkingBounds( + mipsolver, worker.getGlobalDomain()); if (10 * lurkingBounds.size() < mipsolver.mipdata_->integral_cols.size()) return; pdqsort(lurkingBounds.begin(), lurkingBounds.end(), @@ -292,7 +323,7 @@ void HighsPrimalHeuristics::rootReducedCost() { return a.first > b.first; }); - auto localdom = mipsolver.mipdata_->domain; + HighsDomain localdom = worker.getGlobalDomain(); HeuristicNeighbourhood neighbourhood(mipsolver, localdom); @@ -311,7 +342,9 @@ void HighsPrimalHeuristics::rootReducedCost() { while (true) { localdom.propagate(); if (localdom.infeasible()) { - localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool); + localdom.conflictAnalysis(worker.getConflictPool(), + worker.getGlobalDomain(), + worker.getPseudocost()); mipsolver.mipdata_->updateLowerBound( std::max(mipsolver.mipdata_->lower_bound, currCutoff)); @@ -333,8 +366,8 @@ void HighsPrimalHeuristics::rootReducedCost() { double fixingRate = neighbourhood.getFixingRate(); if (fixingRate < 0.3) return; - solveSubMip(*mipsolver.model_, mipsolver.mipdata_->firstrootbasis, fixingRate, - localdom.col_lower_, localdom.col_upper_, + solveSubMip(worker, *mipsolver.model_, mipsolver.mipdata_->firstrootbasis, + fixingRate, localdom.col_lower_, localdom.col_upper_, 500, // std::max(50, int(0.05 * // (mipsolver.mipdata_->num_leaves))), 200 + static_cast(mipsolver.mipdata_->num_nodes / 20), @@ -358,24 +391,35 @@ static double calcFixVal(double rootchange, double fracval, double cost) { return std::floor(fracval + 0.5); } -void HighsPrimalHeuristics::RENS(const std::vector& tmp) { +void HighsPrimalHeuristics::RENS(HighsMipWorker& worker, + const std::vector& tmp) { // return if domain is infeasible - if (mipsolver.mipdata_->domain.infeasible()) return; + if (worker.getGlobalDomain().infeasible()) return; + + HighsPseudocost pscost(worker.getPseudocost()); + HighsSearch heur(worker, pscost); - HighsPseudocost pscost(mipsolver.mipdata_->pseudocost); - HighsSearch heur(mipsolver, pscost); HighsDomain& localdom = heur.getLocalDomain(); heur.setHeuristic(true); + std::vector intcols_; + if (mipsolver.mipdata_->parallelLockActive()) { + intcols_ = intcols; + } + std::vector& intcols = + mipsolver.mipdata_->parallelLockActive() ? intcols_ : this->intcols; intcols.erase(std::remove_if(intcols.begin(), intcols.end(), [&](HighsInt i) { - return mipsolver.mipdata_->domain.isFixed(i); + return worker.getGlobalDomain().isFixed(i); }), intcols.end()); - HighsLpRelaxation heurlp(mipsolver.mipdata_->lp); + // LP relaxation instantiation + HighsLpRelaxation heurlp(worker.getLpRelaxation()); + heurlp.setMipWorker(worker); + heurlp.setProfiling(mipsolver.profiling_); // only use the global upper limit as LP limit so that dual proofs are valid - heurlp.setObjectiveLimit(mipsolver.mipdata_->upper_limit); + heurlp.setObjectiveLimit(worker.upper_limit); heurlp.setAdjustSymmetricBranchingCol(false); heur.setLpRelaxation(&heurlp); @@ -387,7 +431,7 @@ void HighsPrimalHeuristics::RENS(const std::vector& tmp) { // determine the initial number of unfixed variables fixing rate to decide if // the problem is restricted enough to be considered for solving a submip - double maxfixingrate = determineTargetFixingRate(); + double maxfixingrate = determineTargetFixingRate(worker); double fixingrate = 0.0; bool stop = false; // heurlp.setIterationLimit(2 * mipsolver.mipdata_->maxrootlpiters); @@ -404,7 +448,7 @@ void HighsPrimalHeuristics::RENS(const std::vector& tmp) { // heur.getCurrentDepth(), targetdepth); if (heur.getCurrentDepth() > targetdepth) { if (!heur.backtrackUntilDepth(targetdepth)) { - lp_iterations += heur.getLocalLpIterations(); + worker.getHeurLpIterations() += heur.getLocalLpIterations(); return; } } @@ -417,8 +461,8 @@ void HighsPrimalHeuristics::RENS(const std::vector& tmp) { // printf("done evaluating node\n"); if (heur.currentNodePruned()) { ++nbacktracks; - if (mipsolver.mipdata_->domain.infeasible()) { - lp_iterations += heur.getLocalLpIterations(); + if (worker.getGlobalDomain().infeasible()) { + worker.getHeurLpIterations() += heur.getLocalLpIterations(); return; } @@ -451,7 +495,9 @@ void HighsPrimalHeuristics::RENS(const std::vector& tmp) { heur.branchUpwards(i, downval, downval - 0.5); localdom.propagate(); if (localdom.infeasible()) { - localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool); + localdom.conflictAnalysis(worker.getConflictPool(), + worker.getGlobalDomain(), + worker.getPseudocost()); break; } } @@ -460,7 +506,9 @@ void HighsPrimalHeuristics::RENS(const std::vector& tmp) { heur.branchDownwards(i, upval, upval + 0.5); localdom.propagate(); if (localdom.infeasible()) { - localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool); + localdom.conflictAnalysis(worker.getConflictPool(), + worker.getGlobalDomain(), + worker.getPseudocost()); break; } } @@ -511,7 +559,9 @@ void HighsPrimalHeuristics::RENS(const std::vector& tmp) { heur.branchUpwards(fracint.first, fixval, fracint.second); localdom.propagate(); if (localdom.infeasible()) { - localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool); + localdom.conflictAnalysis(worker.getConflictPool(), + worker.getGlobalDomain(), + worker.getPseudocost()); break; } @@ -523,7 +573,9 @@ void HighsPrimalHeuristics::RENS(const std::vector& tmp) { heur.branchDownwards(fracint.first, fixval, fracint.second); localdom.propagate(); if (localdom.infeasible()) { - localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool); + localdom.conflictAnalysis(worker.getConflictPool(), + worker.getGlobalDomain(), + worker.getPseudocost()); break; } @@ -545,7 +597,7 @@ void HighsPrimalHeuristics::RENS(const std::vector& tmp) { // if there is no node left it means we backtracked to the global domain and // the subproblem was solved with the dive if (!heur.hasNode()) { - lp_iterations += heur.getLocalLpIterations(); + worker.getHeurLpIterations() += heur.getLocalLpIterations(); return; } // determine the fixing rate to decide if the problem is restricted enough to @@ -558,7 +610,7 @@ void HighsPrimalHeuristics::RENS(const std::vector& tmp) { // heur.childselrule = ChildSelectionRule::kBestCost; heur.setMinReliable(0); heur.solveDepthFirst(10); - lp_iterations += heur.getLocalLpIterations(); + worker.getHeurLpIterations() += heur.getLocalLpIterations(); if (mipsolver.submip) mipsolver.mipdata_->num_nodes += heur.getLocalNodes(); // lpiterations += heur.lpiterations; // pseudocost = heur.pseudocost; @@ -566,27 +618,35 @@ void HighsPrimalHeuristics::RENS(const std::vector& tmp) { } heurlp.removeObsoleteRows(false); - const bool solve_sub_mip_return = - solveSubMip(heurlp.getLp(), heurlp.getLpSolver().getBasis(), fixingrate, - localdom.col_lower_, localdom.col_upper_, - 500, // std::max(50, int(0.05 * - // (mipsolver.mipdata_->num_leaves))), - 200 + mipsolver.mipdata_->num_nodes / 20, 12); - if (mipsolver.mipdata_->terminatorTerminated()) return; + HighsInt node_reduction_factor = + mipsolver.mipdata_->parallelLockActive() + ? std::max( + HighsInt{1}, + static_cast(mipsolver.mipdata_->workers.size()) / 4) + : 1; + const bool solve_sub_mip_return = solveSubMip( + worker, heurlp.getLp(), heurlp.getLpSolver().getBasis(), fixingrate, + localdom.col_lower_, localdom.col_upper_, + 500, // std::max(50, int(0.05 * + // (mipsolver.mipdata_->num_leaves))), + 200 + mipsolver.mipdata_->num_nodes / (node_reduction_factor * 20), 12); + if (worker.terminatorTerminated()) return; if (!solve_sub_mip_return) { - int64_t new_lp_iterations = lp_iterations + heur.getLocalLpIterations(); + int64_t new_lp_iterations = + worker.getHeurLpIterations() + heur.getLocalLpIterations(); if (new_lp_iterations + mipsolver.mipdata_->heuristic_lp_iterations > 100000 + ((mipsolver.mipdata_->total_lp_iterations - mipsolver.mipdata_->heuristic_lp_iterations - mipsolver.mipdata_->sb_lp_iterations) >> 1)) { - lp_iterations = new_lp_iterations; + worker.getHeurLpIterations() = new_lp_iterations; return; } targetdepth = heur.getCurrentDepth() / 2; - if (targetdepth <= 1 || mipsolver.mipdata_->checkLimits()) { - lp_iterations = new_lp_iterations; + if (targetdepth <= 1 || (!mipsolver.mipdata_->parallelLockActive() && + mipsolver.mipdata_->checkLimits())) { + worker.getHeurLpIterations() = new_lp_iterations; return; } maxfixingrate = fixingrate * 0.5; @@ -595,29 +655,40 @@ void HighsPrimalHeuristics::RENS(const std::vector& tmp) { goto retry; } - lp_iterations += heur.getLocalLpIterations(); + worker.getHeurLpIterations() += heur.getLocalLpIterations(); } -void HighsPrimalHeuristics::RINS(const std::vector& relaxationsol) { +void HighsPrimalHeuristics::RINS(HighsMipWorker& worker, + const std::vector& relaxationsol) { // return if domain is infeasible - if (mipsolver.mipdata_->domain.infeasible()) return; + if (worker.getGlobalDomain().infeasible()) return; if (relaxationsol.size() != static_cast(mipsolver.numCol())) return; + std::vector intcols_; + if (mipsolver.mipdata_->parallelLockActive()) { + intcols_ = intcols; + } + std::vector& intcols = + mipsolver.mipdata_->parallelLockActive() ? intcols_ : this->intcols; intcols.erase(std::remove_if(intcols.begin(), intcols.end(), [&](HighsInt i) { - return mipsolver.mipdata_->domain.isFixed(i); + return worker.getGlobalDomain().isFixed(i); }), intcols.end()); - HighsPseudocost pscost(mipsolver.mipdata_->pseudocost); - HighsSearch heur(mipsolver, pscost); + HighsPseudocost pscost(worker.getPseudocost()); + HighsSearch heur(worker, pscost); + HighsDomain& localdom = heur.getLocalDomain(); heur.setHeuristic(true); - HighsLpRelaxation heurlp(mipsolver.mipdata_->lp); + // LP relaxation instantiation + HighsLpRelaxation heurlp(worker.getLpRelaxation()); + heurlp.setMipWorker(worker); + heurlp.setProfiling(mipsolver.profiling_); // only use the global upper limit as LP limit so that dual proofs are valid - heurlp.setObjectiveLimit(mipsolver.mipdata_->upper_limit); + heurlp.setObjectiveLimit(worker.upper_limit); heurlp.setAdjustSymmetricBranchingCol(false); heur.setLpRelaxation(&heurlp); @@ -629,7 +700,7 @@ void HighsPrimalHeuristics::RINS(const std::vector& relaxationsol) { // determine the initial number of unfixed variables fixing rate to decide if // the problem is restricted enough to be considered for solving a submip - double maxfixingrate = determineTargetFixingRate(); + double maxfixingrate = determineTargetFixingRate(worker); double minfixingrate = 0.25; double fixingrate = 0.0; bool stop = false; @@ -644,7 +715,7 @@ void HighsPrimalHeuristics::RINS(const std::vector& relaxationsol) { // targetdepth); if (heur.getCurrentDepth() > targetdepth) { if (!heur.backtrackUntilDepth(targetdepth)) { - lp_iterations += heur.getLocalLpIterations(); + worker.getHeurLpIterations() += heur.getLocalLpIterations(); return; } } @@ -656,8 +727,8 @@ void HighsPrimalHeuristics::RINS(const std::vector& relaxationsol) { if (heur.currentNodePruned()) { ++nbacktracks; // printf("backtrack1\n"); - if (mipsolver.mipdata_->domain.infeasible()) { - lp_iterations += heur.getLocalLpIterations(); + if (worker.getGlobalDomain().infeasible()) { + worker.getHeurLpIterations() += heur.getLocalLpIterations(); return; } @@ -727,7 +798,9 @@ void HighsPrimalHeuristics::RINS(const std::vector& relaxationsol) { heur.branchUpwards(i, fixval, fixval - 0.5); localdom.propagate(); if (localdom.infeasible()) { - localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool); + localdom.conflictAnalysis(worker.getConflictPool(), + worker.getGlobalDomain(), + worker.getPseudocost()); break; } @@ -738,7 +811,9 @@ void HighsPrimalHeuristics::RINS(const std::vector& relaxationsol) { heur.branchDownwards(i, fixval, fixval + 0.5); localdom.propagate(); if (localdom.infeasible()) { - localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool); + localdom.conflictAnalysis(worker.getConflictPool(), + worker.getGlobalDomain(), + worker.getPseudocost()); break; } @@ -794,7 +869,9 @@ void HighsPrimalHeuristics::RINS(const std::vector& relaxationsol) { ++numBranched; heur.branchUpwards(fracint->first, fixval, fracint->second); if (localdom.infeasible()) { - localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool); + localdom.conflictAnalysis(worker.getConflictPool(), + worker.getGlobalDomain(), + worker.getPseudocost()); break; } @@ -805,7 +882,9 @@ void HighsPrimalHeuristics::RINS(const std::vector& relaxationsol) { ++numBranched; heur.branchDownwards(fracint->first, fixval, fracint->second); if (localdom.infeasible()) { - localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool); + localdom.conflictAnalysis(worker.getConflictPool(), + worker.getGlobalDomain(), + worker.getPseudocost()); break; } @@ -829,7 +908,7 @@ void HighsPrimalHeuristics::RINS(const std::vector& relaxationsol) { // if there is no node left it means we backtracked to the global domain and // the subproblem was solved with the dive if (!heur.hasNode()) { - lp_iterations += heur.getLocalLpIterations(); + worker.getHeurLpIterations() += heur.getLocalLpIterations(); return; } // determine the fixing rate to decide if the problem is restricted enough @@ -842,7 +921,7 @@ void HighsPrimalHeuristics::RINS(const std::vector& relaxationsol) { // heur.childselrule = ChildSelectionRule::kBestCost; heur.setMinReliable(0); heur.solveDepthFirst(10); - lp_iterations += heur.getLocalLpIterations(); + worker.getHeurLpIterations() += heur.getLocalLpIterations(); if (mipsolver.submip) mipsolver.mipdata_->num_nodes += heur.getLocalNodes(); // lpiterations += heur.lpiterations; // pseudocost = heur.pseudocost; @@ -850,27 +929,35 @@ void HighsPrimalHeuristics::RINS(const std::vector& relaxationsol) { } heurlp.removeObsoleteRows(false); - const bool solve_sub_mip_return = - solveSubMip(heurlp.getLp(), heurlp.getLpSolver().getBasis(), fixingrate, - localdom.col_lower_, localdom.col_upper_, - 500, // std::max(50, int(0.05 * - // (mipsolver.mipdata_->num_leaves))), - 200 + mipsolver.mipdata_->num_nodes / 20, 12); - if (mipsolver.mipdata_->terminatorTerminated()) return; + HighsInt node_reduction_factor = + mipsolver.mipdata_->parallelLockActive() + ? std::max( + HighsInt{1}, + static_cast(mipsolver.mipdata_->workers.size()) / 4) + : 1; + const bool solve_sub_mip_return = solveSubMip( + worker, heurlp.getLp(), heurlp.getLpSolver().getBasis(), fixingrate, + localdom.col_lower_, localdom.col_upper_, + 500, // std::max(50, int(0.05 * + // (mipsolver.mipdata_->num_leaves))), + 200 + mipsolver.mipdata_->num_nodes / (node_reduction_factor * 20), 12); + if (worker.terminatorTerminated()) return; if (!solve_sub_mip_return) { - int64_t new_lp_iterations = lp_iterations + heur.getLocalLpIterations(); + int64_t new_lp_iterations = + worker.getHeurLpIterations() + heur.getLocalLpIterations(); if (new_lp_iterations + mipsolver.mipdata_->heuristic_lp_iterations > 100000 + ((mipsolver.mipdata_->total_lp_iterations - mipsolver.mipdata_->heuristic_lp_iterations - mipsolver.mipdata_->sb_lp_iterations) >> 1)) { - lp_iterations = new_lp_iterations; + worker.getHeurLpIterations() = new_lp_iterations; return; } targetdepth = heur.getCurrentDepth() / 2; - if (targetdepth <= 1 || mipsolver.mipdata_->checkLimits()) { - lp_iterations = new_lp_iterations; + if (targetdepth <= 1 || (!mipsolver.mipdata_->parallelLockActive() && + mipsolver.mipdata_->checkLimits())) { + worker.getHeurLpIterations() = new_lp_iterations; return; } // printf("infeasible in root node, trying with lower fixing rate\n"); @@ -878,12 +965,13 @@ void HighsPrimalHeuristics::RINS(const std::vector& relaxationsol) { goto retry; } - lp_iterations += heur.getLocalLpIterations(); + worker.getHeurLpIterations() += heur.getLocalLpIterations(); } -bool HighsPrimalHeuristics::tryRoundedPoint(const std::vector& point, +bool HighsPrimalHeuristics::tryRoundedPoint(HighsMipWorker& worker, + const std::vector& point, const int solution_source) { - auto localdom = mipsolver.mipdata_->domain; + HighsDomain localdom = worker.getGlobalDomain(); bool integerFeasible = true; HighsInt numintcols = intcols.size(); @@ -904,18 +992,25 @@ bool HighsPrimalHeuristics::tryRoundedPoint(const std::vector& point, localdom.fixCol(col, intval, HighsDomain::Reason::branching()); if (localdom.infeasible()) { - localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool); + localdom.conflictAnalysis(worker.getConflictPool(), + worker.getGlobalDomain(), + worker.getPseudocost()); return false; } localdom.propagate(); if (localdom.infeasible()) { - localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool); + localdom.conflictAnalysis(worker.getConflictPool(), + worker.getGlobalDomain(), + worker.getPseudocost()); return false; } } if (numintcols != mipsolver.numCol()) { + // LP relaxation instantiation HighsLpRelaxation lprelax(mipsolver); + lprelax.setMipWorker(worker); + lprelax.setProfiling(mipsolver.profiling_); lprelax.loadModel(); lprelax.setIterationLimit( std::max(int64_t{10000}, 2 * mipsolver.mipdata_->firstrootlpiters)); @@ -939,33 +1034,33 @@ bool HighsPrimalHeuristics::tryRoundedPoint(const std::vector& point, std::vector inds; std::vector vals; double rhs; - if (lprelax.computeDualInfProof(mipsolver.mipdata_->domain, inds, vals, + if (lprelax.computeDualInfProof(worker.getGlobalDomain(), inds, vals, rhs)) { - HighsCutGeneration cutGen(lprelax, mipsolver.mipdata_->cutpool); - cutGen.generateConflict(localdom, inds, vals, rhs); + HighsCutGeneration cutGen(lprelax, worker.getCutPool()); + cutGen.generateConflict(localdom, worker.getGlobalDomain(), inds, vals, + rhs); } return false; } else if (lprelax.unscaledPrimalFeasible(st)) { const auto& lpsol = lprelax.getLpSolver().getSolution().col_value; if (!integerFeasible) { // there may be fractional integer variables -> try ziRound heuristic - ziRound(lpsol); - return mipsolver.mipdata_->trySolution(lpsol, solution_source); + ziRound(worker, lpsol); + trySolution(lpsol, solution_source, worker); } else { // all integer variables are fixed -> add incumbent - mipsolver.mipdata_->addIncumbent(lpsol, lprelax.getObjective(), - solution_source); + addIncumbent(lpsol, lprelax.getObjective(), solution_source, worker); return true; } } } - return mipsolver.mipdata_->trySolution(localdom.col_lower_, solution_source); + return trySolution(localdom.col_lower_, solution_source, worker); } bool HighsPrimalHeuristics::linesearchRounding( - const std::vector& point1, const std::vector& point2, - const int solution_source) { + HighsMipWorker& worker, const std::vector& point1, + const std::vector& point2, const int solution_source) { std::vector roundedpoint; HighsInt numintcols = intcols.size(); @@ -1009,7 +1104,7 @@ bool HighsPrimalHeuristics::linesearchRounding( if (tmpalpha < nextalpha && tmpalpha > alpha + 1e-2) nextalpha = tmpalpha; } - if (tryRoundedPoint(roundedpoint, solution_source)) return true; + if (tryRoundedPoint(worker, roundedpoint, solution_source)) return true; if (reachedpoint2) return false; @@ -1020,10 +1115,12 @@ bool HighsPrimalHeuristics::linesearchRounding( } void HighsPrimalHeuristics::randomizedRounding( - const std::vector& relaxationsol) { + HighsMipWorker& worker, const std::vector& relaxationsol) { if (relaxationsol.size() != static_cast(mipsolver.numCol())) return; - auto localdom = mipsolver.mipdata_->domain; + HighsDomain localdom = worker.getGlobalDomain(); + HighsRandom& randgen = + mipsolver.mipdata_->parallelLockActive() ? worker.randgen : this->randgen; for (HighsInt i : intcols) { double intval; @@ -1039,19 +1136,26 @@ void HighsPrimalHeuristics::randomizedRounding( localdom.fixCol(i, intval, HighsDomain::Reason::branching()); if (localdom.infeasible()) { - localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool); + localdom.conflictAnalysis(worker.getConflictPool(), + worker.getGlobalDomain(), + worker.getPseudocost()); return; } localdom.propagate(); if (localdom.infeasible()) { - localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool); + localdom.conflictAnalysis(worker.getConflictPool(), + worker.getGlobalDomain(), + worker.getPseudocost()); return; } } if (mipsolver.mipdata_->integer_cols.size() != static_cast(mipsolver.numCol())) { + // LP relaxation instantiation HighsLpRelaxation lprelax(mipsolver); + lprelax.setMipWorker(worker); + lprelax.setProfiling(mipsolver.profiling_); lprelax.loadModel(); lprelax.setIterationLimit( std::max(int64_t{10000}, 2 * mipsolver.mipdata_->firstrootlpiters)); @@ -1080,29 +1184,36 @@ void HighsPrimalHeuristics::randomizedRounding( std::vector inds; std::vector vals; double rhs; - if (lprelax.computeDualInfProof(mipsolver.mipdata_->domain, inds, vals, + if (lprelax.computeDualInfProof(worker.getGlobalDomain(), inds, vals, rhs)) { - HighsCutGeneration cutGen(lprelax, mipsolver.mipdata_->cutpool); - cutGen.generateConflict(localdom, inds, vals, rhs); + HighsCutGeneration cutGen(lprelax, worker.getCutPool()); + cutGen.generateConflict(localdom, worker.getGlobalDomain(), inds, vals, + rhs); } - } else if (lprelax.unscaledPrimalFeasible(st)) - mipsolver.mipdata_->addIncumbent( - lprelax.getLpSolver().getSolution().col_value, lprelax.getObjective(), - kSolutionSourceRandomizedRounding); + } else if (HighsLpRelaxation::unscaledPrimalFeasible(st)) { + addIncumbent(lprelax.getLpSolver().getSolution().col_value, + lprelax.getObjective(), kSolutionSourceRandomizedRounding, + worker); + } } else { - mipsolver.mipdata_->trySolution(localdom.col_lower_, - kSolutionSourceRandomizedRounding); + trySolution(localdom.col_lower_, kSolutionSourceRandomizedRounding, worker); } } -void HighsPrimalHeuristics::shifting(const std::vector& relaxationsol) { +void HighsPrimalHeuristics::shifting(HighsMipWorker& worker, + const std::vector& relaxationsol) { if (relaxationsol.size() != static_cast(mipsolver.numCol())) return; std::vector current_relax_solution = relaxationsol; HighsInt t = 0; const HighsLp& currentLp = *mipsolver.model_; - HighsLpRelaxation lprelax(mipsolver.mipdata_->lp); + // LP relaxation instantiation + HighsLpRelaxation lprelax(worker.getLpRelaxation()); + lprelax.setMipWorker(worker); + lprelax.setProfiling(mipsolver.profiling_); + HighsRandom& randgen = + mipsolver.mipdata_->parallelLockActive() ? worker.randgen : this->randgen; std::vector> current_fractional_integers = lprelax.getFractionalIntegers(); std::vector> current_infeasible_rows = @@ -1341,17 +1452,18 @@ void HighsPrimalHeuristics::shifting(const std::vector& relaxationsol) { } // re-check for feasibility and add incumbent if (hasInfeasibleConstraints) { - tryRoundedPoint(current_relax_solution, kSolutionSourceShifting); + tryRoundedPoint(worker, current_relax_solution, kSolutionSourceShifting); } else { - if (current_fractional_integers.size() > 0) - ziRound(current_relax_solution); - else - mipsolver.mipdata_->trySolution(current_relax_solution, - kSolutionSourceShifting); + if (current_fractional_integers.size() > 0) { + ziRound(worker, current_relax_solution); + } else { + trySolution(current_relax_solution, kSolutionSourceShifting, worker); + } } } -void HighsPrimalHeuristics::ziRound(const std::vector& relaxationsol) { +void HighsPrimalHeuristics::ziRound(HighsMipWorker& worker, + const std::vector& relaxationsol) { // if (mipsolver.submip) return; if (relaxationsol.size() != static_cast(mipsolver.numCol())) return; @@ -1362,7 +1474,7 @@ void HighsPrimalHeuristics::ziRound(const std::vector& relaxationsol) { x - std::floor(x + mipsolver.mipdata_->feastol)); }; - // auto localdom = mipsolver.mipdata_->domain; + // auto localdom = mipsolver.mipdata_->getDomain(); HighsCDouble zi_total = 0.0; for (HighsInt i : intcols) { @@ -1460,17 +1572,22 @@ void HighsPrimalHeuristics::ziRound(const std::vector& relaxationsol) { improvement_in_feasibility = previous_zi_total - zi_total; } // re-check for feasibility and add incumbent - mipsolver.mipdata_->trySolution(current_relax_solution, - kSolutionSourceZiRound); + trySolution(current_relax_solution, kSolutionSourceZiRound, worker); } -void HighsPrimalHeuristics::feasibilityPump() { - HighsLpRelaxation lprelax(mipsolver.mipdata_->lp); +void HighsPrimalHeuristics::feasibilityPump(HighsMipWorker& worker) { + // LP relaxation instantiation + HighsLpRelaxation lprelax(worker.getLpRelaxation()); + lprelax.setMipWorker(worker); + lprelax.setProfiling(mipsolver.profiling_); std::unordered_set, HighsVectorHasher, HighsVectorEqual> referencepoints; std::vector roundedsol; HighsLpRelaxation::Status status = lprelax.resolveLp(); - lp_iterations += lprelax.getNumLpIterations(); + worker.getHeurLpIterations() += lprelax.getNumLpIterations(); + + HighsRandom& randgen = + mipsolver.mipdata_->parallelLockActive() ? worker.randgen : this->randgen; std::vector fracintcost; std::vector fracintset; @@ -1493,7 +1610,7 @@ void HighsPrimalHeuristics::feasibilityPump() { std::vector referencepoint; referencepoint.reserve(mipsolver.mipdata_->integer_cols.size()); - auto localdom = mipsolver.mipdata_->domain; + HighsDomain localdom = worker.getGlobalDomain(); for (HighsInt i : mipsolver.mipdata_->integer_cols) { assert(mipsolver.isColInteger(i)); double intval = std::floor(roundedsol[i] + randgen.real(0.4, 0.6)); @@ -1504,12 +1621,16 @@ void HighsPrimalHeuristics::feasibilityPump() { if (!localdom.infeasible()) { localdom.fixCol(i, intval, HighsDomain::Reason::branching()); if (localdom.infeasible()) { - localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool); + localdom.conflictAnalysis(worker.getConflictPool(), + worker.getGlobalDomain(), + worker.getPseudocost()); continue; } localdom.propagate(); if (localdom.infeasible()) { - localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool); + localdom.conflictAnalysis(worker.getConflictPool(), + worker.getGlobalDomain(), + worker.getPseudocost()); continue; } } @@ -1525,10 +1646,10 @@ void HighsPrimalHeuristics::feasibilityPump() { roundedsol[col] = (HighsInt)std::floor(lpsol[col]); else if (roundedsol[col] < lpsol[col]) roundedsol[col] = (HighsInt)std::ceil(lpsol[col]); - else if (roundedsol[col] < mipsolver.mipdata_->domain.col_upper_[col]) - roundedsol[col] = mipsolver.mipdata_->domain.col_upper_[col]; + else if (roundedsol[col] < worker.getGlobalDomain().col_upper_[col]) + roundedsol[col] = worker.getGlobalDomain().col_upper_[col]; else - roundedsol[col] = mipsolver.mipdata_->domain.col_lower_[col]; + roundedsol[col] = worker.getGlobalDomain().col_lower_[col]; referencepoint[flippos] = (HighsInt)roundedsol[col]; } @@ -1537,7 +1658,8 @@ void HighsPrimalHeuristics::feasibilityPump() { if (havecycle) return; - if (linesearchRounding(lpsol, roundedsol, kSolutionSourceFeasibilityPump)) + if (linesearchRounding(worker, lpsol, roundedsol, + kSolutionSourceFeasibilityPump)) return; if (lprelax.getNumLpIterations() >= @@ -1561,31 +1683,32 @@ void HighsPrimalHeuristics::feasibilityPump() { status = lprelax.resolveLp(); niters += lprelax.getNumLpIterations(); if (niters == 0) break; - lp_iterations += niters; + worker.getHeurLpIterations() += niters; } if (lprelax.getFractionalIntegers().empty() && - lprelax.unscaledPrimalFeasible(status)) - mipsolver.mipdata_->addIncumbent( - lprelax.getLpSolver().getSolution().col_value, lprelax.getObjective(), - kSolutionSourceFeasibilityPump); + HighsLpRelaxation::unscaledPrimalFeasible(status)) { + addIncumbent(lprelax.getLpSolver().getSolution().col_value, + lprelax.getObjective(), kSolutionSourceFeasibilityPump, + worker); + } } -void HighsPrimalHeuristics::centralRounding() { +void HighsPrimalHeuristics::centralRounding(HighsMipWorker& worker) { if (mipsolver.mipdata_->analyticCenter.size() != static_cast(mipsolver.numCol())) return; if (!mipsolver.mipdata_->firstlpsol.empty()) - linesearchRounding(mipsolver.mipdata_->firstlpsol, + linesearchRounding(worker, mipsolver.mipdata_->firstlpsol, mipsolver.mipdata_->analyticCenter, kSolutionSourceCentralRounding); else if (!mipsolver.mipdata_->rootlpsol.empty()) - linesearchRounding(mipsolver.mipdata_->rootlpsol, + linesearchRounding(worker, mipsolver.mipdata_->rootlpsol, mipsolver.mipdata_->analyticCenter, kSolutionSourceCentralRounding); else - linesearchRounding(mipsolver.mipdata_->analyticCenter, + linesearchRounding(worker, mipsolver.mipdata_->analyticCenter, mipsolver.mipdata_->analyticCenter, kSolutionSourceCentralRounding); } @@ -1595,7 +1718,7 @@ void HighsPrimalHeuristics::clique() { HighsHashTable entries; double offset = 0.0; - HighsDomain& globaldom = mipsolver.mipdata_->domain; + HighsDomain& globaldom = mipsolver.mipdata_->getDomain(); for (HighsInt j = 0; j != mipsolver.numCol(); ++j) { HighsInt col = j; double val = mipsolver.colCost(col); @@ -1636,7 +1759,7 @@ void HighsPrimalHeuristics::clique() { HighsInt numcliques; cliques = mipsolver.mipdata_->cliquetable.separateCliques( - solution, mipsolver.mipdata_->domain, mipsolver.mipdata_->feastol); + solution, mipsolver.mipdata_->getDomain(), mipsolver.mipdata_->feastol); numcliques = cliques.size(); while (numcliques != 0) { bestviol = 0.5; @@ -1654,20 +1777,85 @@ void HighsPrimalHeuristics::clique() { } cliques = mipsolver.mipdata_->cliquetable.separateCliques( - solution, mipsolver.mipdata_->domain, mipsolver.mipdata_->feastol); + solution, mipsolver.mipdata_->getDomain(), mipsolver.mipdata_->feastol); numcliques = cliques.size(); } } #endif -void HighsPrimalHeuristics::flushStatistics() { +bool HighsPrimalHeuristics::addIncumbent(const std::vector& sol, + double solobj, + const int solution_source, + HighsMipWorker& worker) { + if (mipsolver.mipdata_->parallelLockActive()) { + return worker.addIncumbent(sol, solobj, solution_source); + } else { + return mipsolver.mipdata_->addIncumbent(sol, solobj, solution_source); + } +} + +bool HighsPrimalHeuristics::trySolution(const std::vector& solution, + const int solution_source, + HighsMipWorker& worker) { + if (mipsolver.mipdata_->parallelLockActive()) { + return worker.trySolution(solution, solution_source); + } else { + return mipsolver.mipdata_->trySolution(solution, solution_source); + } +} + +HighsInt HighsPrimalHeuristics::getNumSuccessObservations( + HighsMipWorker& worker) const { + return numSuccessObservations + worker.getHeurNumSuccessObservations(); +} + +HighsInt HighsPrimalHeuristics::getNumInfeasObservations( + HighsMipWorker& worker) const { + return numInfeasObservations + worker.getHeurNumInfeasObservations(); +} + +double HighsPrimalHeuristics::getSuccessObservations( + HighsMipWorker& worker) const { + return successObservations + worker.getHeurSuccessObservations(); +} + +double HighsPrimalHeuristics::getInfeasObservations( + HighsMipWorker& worker) const { + return infeasObservations + worker.getHeurInfeasObservations(); +} + +void HighsPrimalHeuristics::flushStatistics(HighsMipSolver& mipsolver, + HighsMipWorker& worker) { + int64_t total_repair_lp; + int64_t total_repair_lp_feasible; + int64_t total_repair_lp_iterations; + int64_t lp_iterations; + double successObservations; + HighsInt numSuccessObservations; + double infeasObservations; + HighsInt numInfeasObservations; + HighsInt max_submip_level; + HighsModelStatus termination_status; + worker.getHeurStatsValues(total_repair_lp, total_repair_lp_feasible, + total_repair_lp_iterations, lp_iterations, + successObservations, numSuccessObservations, + infeasObservations, numInfeasObservations, + max_submip_level, termination_status); + mipsolver.mipdata_->total_repair_lp += total_repair_lp; mipsolver.mipdata_->total_repair_lp_feasible += total_repair_lp_feasible; mipsolver.mipdata_->total_repair_lp_iterations += total_repair_lp_iterations; - total_repair_lp = 0; - total_repair_lp_feasible = 0; - total_repair_lp_iterations = 0; mipsolver.mipdata_->heuristic_lp_iterations += lp_iterations; mipsolver.mipdata_->total_lp_iterations += lp_iterations; - lp_iterations = 0; + mipsolver.max_submip_level = + std::max(mipsolver.max_submip_level, max_submip_level); + if (termination_status != HighsModelStatus::kNotset && + mipsolver.termination_status_ == HighsModelStatus::kNotset) { + mipsolver.termination_status_ = termination_status; + } + this->successObservations += successObservations; + this->numSuccessObservations += numSuccessObservations; + this->infeasObservations += infeasObservations; + this->numInfeasObservations += numInfeasObservations; + worker.resetHeurStats(); } diff --git a/highs/mip/HighsPrimalHeuristics.h b/highs/mip/HighsPrimalHeuristics.h index 3d29f7b89cf..34c989a1fb5 100644 --- a/highs/mip/HighsPrimalHeuristics.h +++ b/highs/mip/HighsPrimalHeuristics.h @@ -15,16 +15,13 @@ #include "util/HighsRandom.h" class HighsMipSolver; +class HighsMipWorker; class HighsLpRelaxation; class HighsPrimalHeuristics { private: - HighsMipSolver& mipsolver; - size_t total_repair_lp; - size_t total_repair_lp_feasible; - size_t total_repair_lp_iterations; - size_t lp_iterations; - + const HighsMipSolver& mipsolver; + std::vector intcols; double successObservations; HighsInt numSuccessObservations; double infeasObservations; @@ -32,44 +29,64 @@ class HighsPrimalHeuristics { HighsRandom randgen; - std::vector intcols; - public: HighsPrimalHeuristics(HighsMipSolver& mipsolver); void setupIntCols(); - bool solveSubMip(const HighsLp& lp, const HighsBasis& basis, - double fixingRate, std::vector colLower, - std::vector colUpper, HighsInt maxleaves, - HighsInt maxnodes, HighsInt stallnodes); + bool solveSubMip(HighsMipWorker& worker, const HighsLp& lp, + const HighsBasis& basis, double fixingRate, + std::vector colLower, std::vector colUpper, + HighsInt maxleaves, HighsInt maxnodes, HighsInt stallnodes); - double determineTargetFixingRate(); + double determineTargetFixingRate(HighsMipWorker& worker); - void rootReducedCost(); + void rootReducedCost(HighsMipWorker& worker); - void RENS(const std::vector& relaxationsol); + void RENS(HighsMipWorker& worker, const std::vector& relaxationsol); - void RINS(const std::vector& relaxationsol); + void RINS(HighsMipWorker& worker, const std::vector& relaxationsol); - void feasibilityPump(); + void feasibilityPump(HighsMipWorker& worker); - void centralRounding(); + void centralRounding(HighsMipWorker& worker); - void flushStatistics(); + void flushStatistics(HighsMipSolver& mipsolver, HighsMipWorker& worker); - bool tryRoundedPoint(const std::vector& point, + bool tryRoundedPoint(HighsMipWorker& worker, const std::vector& point, const int solution_source); - bool linesearchRounding(const std::vector& point1, + bool linesearchRounding(HighsMipWorker& worker, + const std::vector& point1, const std::vector& point2, const int solution_source); - void randomizedRounding(const std::vector& relaxationsol); + void randomizedRounding(HighsMipWorker& worker, + const std::vector& relaxationsol); + + void shifting(HighsMipWorker& worker, + const std::vector& relaxationsol); + + void ziRound(HighsMipWorker& worker, + const std::vector& relaxationsol); + + bool addIncumbent(const std::vector& sol, double solobj, + const int solution_source, HighsMipWorker& worker); + + bool trySolution(const std::vector& solution, + const int solution_source, HighsMipWorker& worker); + + HighsInt getNumSuccessObservations(HighsMipWorker& worker) const; + + HighsInt getNumInfeasObservations(HighsMipWorker& worker) const; + + double getSuccessObservations(HighsMipWorker& worker) const; - void shifting(const std::vector& relaxationsol); + double getInfeasObservations(HighsMipWorker& worker) const; - void ziRound(const std::vector& relaxationsol); + HighsInt getHeuristicRandom(const HighsInt sup) { + return randgen.integer(sup); + } }; #endif diff --git a/highs/mip/HighsPseudocost.cpp b/highs/mip/HighsPseudocost.cpp index a702eed2423..73d7eeded3f 100644 --- a/highs/mip/HighsPseudocost.cpp +++ b/highs/mip/HighsPseudocost.cpp @@ -22,15 +22,21 @@ HighsPseudocost::HighsPseudocost(const HighsMipSolver& mipsolver) ncutoffsdown(mipsolver.numCol()), conflictscoreup(mipsolver.numCol()), conflictscoredown(mipsolver.numCol()), + changedpos(mipsolver.numCol(), -1), conflict_weight(1.0), conflict_avg_score(0.0), cost_total(0), inferences_total(0), + delta_cost_sum(0.0), + delta_inferences_sum(0.0), nsamplestotal(0), ninferencestotal(0), ncutoffstotal(0), + delta_nsamplestotal(0), + delta_ninferencestotal(0), minreliable(mipsolver.options_mip_->mip_pscost_minreliable), degeneracyFactor(1.0) { + deltas.reserve(std::min(HighsInt{256}, mipsolver.numCol())); if (mipsolver.pscostinit != nullptr) { cost_total = mipsolver.pscostinit->cost_total; inferences_total = mipsolver.pscostinit->inferences_total; diff --git a/highs/mip/HighsPseudocost.h b/highs/mip/HighsPseudocost.h index 342a4f66e14..52591c4dea3 100644 --- a/highs/mip/HighsPseudocost.h +++ b/highs/mip/HighsPseudocost.h @@ -50,6 +50,22 @@ struct HighsPseudocostInitialization { const presolve::HighsPostsolveStack& postsolveStack); }; +struct HighsPseudocostDelta { + HighsInt col = -1; + HighsInt nsamplesup = 0; + HighsInt nsamplesdown = 0; + HighsInt ninferencesup = 0; + HighsInt ninferencesdown = 0; + HighsInt ncutoffsup = 0; + HighsInt ncutoffsdown = 0; + double pseudocostup_sum = 0.0; + double pseudocostdown_sum = 0.0; + double inferencesup_sum = 0.0; + double inferencesdown_sum = 0.0; + double conflictscoreup_sum = 0.0; + double conflictscoredown_sum = 0.0; +}; + class HighsPseudocost { friend struct HighsPseudocostInitialization; std::vector pseudocostup; @@ -64,30 +80,46 @@ class HighsPseudocost { std::vector ncutoffsdown; std::vector conflictscoreup; std::vector conflictscoredown; + std::vector changedpos; + std::vector deltas; double conflict_weight; double conflict_avg_score; double cost_total; double inferences_total; + double delta_cost_sum; + double delta_inferences_sum; int64_t nsamplestotal; int64_t ninferencestotal; int64_t ncutoffstotal; + int64_t delta_nsamplestotal; + int64_t delta_ninferencestotal; HighsInt minreliable; double degeneracyFactor; + HighsPseudocostDelta& markChanged(HighsInt col) { + assert(col >= 0 && col < static_cast(changedpos.size())); + if (changedpos[col] == -1) { + changedpos[col] = static_cast(deltas.size()); + deltas.emplace_back(); + deltas.back().col = col; + } + return deltas[changedpos[col]]; + } + + template + static void addDeltaAverage(double& avg, CountType& count, double delta_sum, + CountType delta_count) { + if (delta_count <= 0) return; + avg = (avg * static_cast(count) + delta_sum) / + static_cast(count + delta_count); + count += delta_count; + } + public: HighsPseudocost() = default; HighsPseudocost(const HighsMipSolver& mipsolver); - void subtractBase(const HighsPseudocost& base) { - for (size_t i = 0; i != pseudocostup.size(); ++i) { - pseudocostup[i] -= base.pseudocostup[i]; - pseudocostdown[i] -= base.pseudocostdown[i]; - nsamplesup[i] -= base.nsamplesup[i]; - nsamplesdown[i] -= base.nsamplesdown[i]; - } - } - void increaseConflictWeight() { conflict_weight *= 1.02; @@ -100,6 +132,10 @@ class HighsPseudocost { conflictscoreup[i] *= scale; conflictscoredown[i] *= scale; } + for (HighsPseudocostDelta& delta : deltas) { + delta.conflictscoreup_sum *= scale; + delta.conflictscoredown_sum *= scale; + } } } @@ -109,13 +145,17 @@ class HighsPseudocost { } void increaseConflictScoreUp(HighsInt col) { + HighsPseudocostDelta& delta = markChanged(col); conflictscoreup[col] += conflict_weight; conflict_avg_score += conflict_weight; + delta.conflictscoreup_sum += conflict_weight; } void increaseConflictScoreDown(HighsInt col) { + HighsPseudocostDelta& delta = markChanged(col); conflictscoredown[col] += conflict_weight; conflict_avg_score += conflict_weight; + delta.conflictscoredown_sum += conflict_weight; } void setMinReliable(HighsInt minreliable) { this->minreliable = minreliable; } @@ -133,50 +173,70 @@ class HighsPseudocost { } void addCutoffObservation(HighsInt col, bool upbranch) { + HighsPseudocostDelta& delta = markChanged(col); ++ncutoffstotal; - if (upbranch) + if (upbranch) { ncutoffsup[col] += 1; - else + delta.ncutoffsup += 1; + } else { ncutoffsdown[col] += 1; + delta.ncutoffsdown += 1; + } } void addObservation(HighsInt col, double delta, double objdelta) { assert(delta != 0.0); assert(objdelta >= 0.0); + HighsPseudocostDelta& ps_delta = markChanged(col); if (delta > 0.0) { double unit_gain = objdelta / delta; double d = unit_gain - pseudocostup[col]; nsamplesup[col] += 1; pseudocostup[col] += d / nsamplesup[col]; + ps_delta.nsamplesup += 1; + ps_delta.pseudocostup_sum += unit_gain; d = unit_gain - cost_total; ++nsamplestotal; cost_total += d / static_cast(nsamplestotal); + ++delta_nsamplestotal; + delta_cost_sum += unit_gain; } else { double unit_gain = -objdelta / delta; double d = unit_gain - pseudocostdown[col]; nsamplesdown[col] += 1; pseudocostdown[col] += d / nsamplesdown[col]; + ps_delta.nsamplesdown += 1; + ps_delta.pseudocostdown_sum += unit_gain; d = unit_gain - cost_total; ++nsamplestotal; cost_total += d / static_cast(nsamplestotal); + ++delta_nsamplestotal; + delta_cost_sum += unit_gain; } } void addInferenceObservation(HighsInt col, HighsInt ninferences, bool upbranch) { + HighsPseudocostDelta& delta = markChanged(col); double d = ninferences - inferences_total; ++ninferencestotal; inferences_total += d / static_cast(ninferencestotal); + ++delta_ninferencestotal; + delta_inferences_sum += ninferences; if (upbranch) { d = ninferences - inferencesup[col]; ninferencesup[col] += 1; inferencesup[col] += d / ninferencesup[col]; + delta.ninferencesup += 1; + delta.inferencesup_sum += ninferences; } else { d = ninferences - inferencesdown[col]; ninferencesdown[col] += 1; inferencesdown[col] += d / ninferencesdown[col]; + delta.ninferencesdown += 1; + delta.inferencesdown_sum += ninferences; } } @@ -361,6 +421,87 @@ class HighsPseudocost { double getAvgInferencesDown(HighsInt col) const { return inferencesdown[col]; } + + void flushPseudoCost(HighsPseudocost& pseudocost) { + assert(pseudocost.ncutoffsup.size() == this->ncutoffsup.size()); + for (const HighsPseudocostDelta& delta : pseudocost.deltas) { + const HighsInt col = delta.col; + assert(col >= 0 && + col < static_cast(pseudocost.ncutoffsup.size())); + addDeltaAverage(this->pseudocostup[col], this->nsamplesup[col], + delta.pseudocostup_sum, delta.nsamplesup); + addDeltaAverage(this->pseudocostdown[col], this->nsamplesdown[col], + delta.pseudocostdown_sum, delta.nsamplesdown); + addDeltaAverage(this->inferencesup[col], this->ninferencesup[col], + delta.inferencesup_sum, delta.ninferencesup); + addDeltaAverage(this->inferencesdown[col], this->ninferencesdown[col], + delta.inferencesdown_sum, delta.ninferencesdown); + const double conflict_scale = + this->conflict_weight / pseudocost.conflict_weight; + const double conflict_delta_up = + conflict_scale * delta.conflictscoreup_sum; + const double conflict_delta_down = + conflict_scale * delta.conflictscoredown_sum; + this->conflictscoreup[col] += conflict_delta_up; + this->conflictscoredown[col] += conflict_delta_down; + this->conflict_avg_score += conflict_delta_up + conflict_delta_down; + this->ncutoffsup[col] += delta.ncutoffsup; + this->ncutoffsdown[col] += delta.ncutoffsdown; + this->ncutoffstotal += delta.ncutoffsup + delta.ncutoffsdown; + } + addDeltaAverage(this->cost_total, this->nsamplestotal, + pseudocost.delta_cost_sum, pseudocost.delta_nsamplestotal); + addDeltaAverage(this->inferences_total, this->ninferencestotal, + pseudocost.delta_inferences_sum, + pseudocost.delta_ninferencestotal); + pseudocost.removeChanged(); + } + + void syncPseudoCost(HighsPseudocost& pseudocost) { + std::copy(pseudocostup.begin(), pseudocostup.end(), + pseudocost.pseudocostup.begin()); + std::copy(pseudocostdown.begin(), pseudocostdown.end(), + pseudocost.pseudocostdown.begin()); + std::copy(nsamplesup.begin(), nsamplesup.end(), + pseudocost.nsamplesup.begin()); + std::copy(nsamplesdown.begin(), nsamplesdown.end(), + pseudocost.nsamplesdown.begin()); + std::copy(inferencesup.begin(), inferencesup.end(), + pseudocost.inferencesup.begin()); + std::copy(inferencesdown.begin(), inferencesdown.end(), + pseudocost.inferencesdown.begin()); + std::copy(ninferencesup.begin(), ninferencesup.end(), + pseudocost.ninferencesup.begin()); + std::copy(ninferencesdown.begin(), ninferencesdown.end(), + pseudocost.ninferencesdown.begin()); + std::copy(ncutoffsup.begin(), ncutoffsup.end(), + pseudocost.ncutoffsup.begin()); + std::copy(ncutoffsdown.begin(), ncutoffsdown.end(), + pseudocost.ncutoffsdown.begin()); + std::copy(conflictscoreup.begin(), conflictscoreup.end(), + pseudocost.conflictscoreup.begin()); + std::copy(conflictscoredown.begin(), conflictscoredown.end(), + pseudocost.conflictscoredown.begin()); + pseudocost.conflict_weight = conflict_weight; + pseudocost.conflict_avg_score = conflict_avg_score; + pseudocost.cost_total = cost_total; + pseudocost.inferences_total = inferences_total; + pseudocost.nsamplestotal = nsamplestotal; + pseudocost.ninferencestotal = ninferencestotal; + pseudocost.ncutoffstotal = ncutoffstotal; + pseudocost.removeChanged(); + } + + void removeChanged() { + for (const HighsPseudocostDelta& delta : deltas) { + changedpos[delta.col] = -1; + } + deltas.clear(); + delta_cost_sum = 0.0; + delta_inferences_sum = 0.0; + delta_nsamplestotal = 0; + delta_ninferencestotal = 0; + } }; #endif diff --git a/highs/mip/HighsRedcostFixing.cpp b/highs/mip/HighsRedcostFixing.cpp index 8bdac3756bf..e5ff1534ca3 100644 --- a/highs/mip/HighsRedcostFixing.cpp +++ b/highs/mip/HighsRedcostFixing.cpp @@ -10,20 +10,21 @@ #include "mip/HighsMipSolverData.h" std::vector> -HighsRedcostFixing::getLurkingBounds(const HighsMipSolver& mipsolver) const { +HighsRedcostFixing::getLurkingBounds(const HighsMipSolver& mipsolver, + const HighsDomain& globaldom) const { std::vector> domchgs; if (lurkingColLower.empty()) return domchgs; for (HighsInt col : mipsolver.mipdata_->integral_cols) { for (const auto& lower : lurkingColLower[col]) { - if (lower.second > mipsolver.mipdata_->domain.col_lower_[col]) + if (lower.second > globaldom.col_lower_[col]) domchgs.emplace_back( lower.first, HighsDomainChange{static_cast(lower.second), col, HighsBoundType::kLower}); } for (const auto& upper : lurkingColUpper[col]) { - if (upper.second < mipsolver.mipdata_->domain.col_upper_[col]) + if (upper.second < globaldom.col_upper_[col]) domchgs.emplace_back( upper.first, HighsDomainChange{static_cast(upper.second), col, HighsBoundType::kUpper}); @@ -47,32 +48,34 @@ void HighsRedcostFixing::propagateRootRedcost(const HighsMipSolver& mipsolver) { for (auto it = lurkingColLower[col].lower_bound(mipsolver.mipdata_->upper_limit); it != lurkingColLower[col].end(); ++it) { - if (it->second > mipsolver.mipdata_->domain.col_lower_[col]) { - mipsolver.mipdata_->domain.changeBound( + if (it->second > mipsolver.mipdata_->getDomain().col_lower_[col]) { + mipsolver.mipdata_->getDomain().changeBound( HighsBoundType::kLower, col, (double)it->second, HighsDomain::Reason::unspecified()); - if (mipsolver.mipdata_->domain.infeasible()) return; + if (mipsolver.mipdata_->getDomain().infeasible()) return; } } for (auto it = lurkingColUpper[col].lower_bound(mipsolver.mipdata_->upper_limit); it != lurkingColUpper[col].end(); ++it) { - if (it->second < mipsolver.mipdata_->domain.col_upper_[col]) { - mipsolver.mipdata_->domain.changeBound( + if (it->second < mipsolver.mipdata_->getDomain().col_upper_[col]) { + mipsolver.mipdata_->getDomain().changeBound( HighsBoundType::kUpper, col, (double)it->second, HighsDomain::Reason::unspecified()); - if (mipsolver.mipdata_->domain.infeasible()) return; + if (mipsolver.mipdata_->getDomain().infeasible()) return; } } } - mipsolver.mipdata_->domain.propagate(); + mipsolver.mipdata_->getDomain().propagate(); } void HighsRedcostFixing::propagateRedCost(const HighsMipSolver& mipsolver, HighsDomain& localdomain, - const HighsLpRelaxation& lp) { + HighsDomain& globaldom, + const HighsLpRelaxation& lp, + HighsConflictPool& conflictpool) { const std::vector& lpredcost = lp.getSolution().col_dual; double lpobjective = lp.getObjective(); HighsCDouble gap = @@ -108,7 +111,7 @@ void HighsRedcostFixing::propagateRedCost(const HighsMipSolver& mipsolver, if (newub >= localdomain.col_upper_[col]) continue; assert(newub < localdomain.col_upper_[col]); - if (mipsolver.mipdata_->domain.isBinary(col)) { + if (globaldom.isBinary(col)) { boundChanges.emplace_back( HighsDomainChange{newub, col, HighsBoundType::kUpper}); } else { @@ -126,7 +129,7 @@ void HighsRedcostFixing::propagateRedCost(const HighsMipSolver& mipsolver, if (newlb <= localdomain.col_lower_[col]) continue; assert(newlb > localdomain.col_lower_[col]); - if (mipsolver.mipdata_->domain.isBinary(col)) { + if (globaldom.isBinary(col)) { boundChanges.emplace_back( HighsDomainChange{newlb, col, HighsBoundType::kLower}); } else { @@ -143,21 +146,18 @@ void HighsRedcostFixing::propagateRedCost(const HighsMipSolver& mipsolver, double rhs; if (boundChanges.size() <= 100 && - lp.computeDualProof(mipsolver.mipdata_->domain, - mipsolver.mipdata_->upper_limit, inds, vals, rhs, - false)) { + lp.computeDualProof(globaldom, mipsolver.mipdata_->upper_limit, inds, + vals, rhs, false)) { bool addedConstraints = false; - HighsInt oldNumConflicts = - mipsolver.mipdata_->conflictPool.getNumConflicts(); + HighsInt oldNumConflicts = conflictpool.getNumConflicts(); for (const HighsDomainChange& domchg : boundChanges) { if (localdomain.isActive(domchg)) continue; - localdomain.conflictAnalyzeReconvergence( - domchg, inds.data(), vals.data(), inds.size(), rhs, - mipsolver.mipdata_->conflictPool); + localdomain.conflictAnalyzeReconvergence(domchg, inds.data(), + vals.data(), inds.size(), rhs, + conflictpool, globaldom); } - addedConstraints = - mipsolver.mipdata_->conflictPool.getNumConflicts() != oldNumConflicts; + addedConstraints = conflictpool.getNumConflicts() != oldNumConflicts; if (addedConstraints) { localdomain.propagate(); @@ -197,8 +197,11 @@ void HighsRedcostFixing::addRootRedcost(const HighsMipSolver& mipsolver, lurkingColLower.resize(mipsolver.numCol()); lurkingColUpper.resize(mipsolver.numCol()); - mipsolver.mipdata_->lp.computeBasicDegenerateDuals( - mipsolver.mipdata_->feastol); + // Provided domains won't be used (only used for dual proof) + mipsolver.mipdata_->getLp().computeBasicDegenerateDuals( + mipsolver.mipdata_->feastol, mipsolver.mipdata_->getDomain(), + mipsolver.mipdata_->getDomain(), mipsolver.mipdata_->getConflictPool(), + false); // Compute maximum number of steps per column with large domain // max_steps = 2 ** k, k = max(5, min(10 ,round(log(|D| / 10)))), @@ -206,8 +209,8 @@ void HighsRedcostFixing::addRootRedcost(const HighsMipSolver& mipsolver, // This is to avoid doing 2**10 steps when there's many unbounded columns HighsInt numRedcostLargeDomainCols = 0; for (HighsInt col : mipsolver.mipdata_->integral_cols) { - if ((mipsolver.mipdata_->domain.col_upper_[col] - - mipsolver.mipdata_->domain.col_lower_[col]) >= 512 && + if ((mipsolver.mipdata_->getDomain().col_upper_[col] - + mipsolver.mipdata_->getDomain().col_lower_[col]) >= 512 && std::abs(lpredcost[col]) > mipsolver.mipdata_->feastol) { numRedcostLargeDomainCols++; } @@ -293,11 +296,13 @@ void HighsRedcostFixing::addRootRedcost(const HighsMipSolver& mipsolver, // cutoffbound = (lurkub - lb) * redcost + lpobj findLurkingBounds( col, HighsInt{1}, - static_cast(mipsolver.mipdata_->domain.col_lower_[col]), - static_cast(mipsolver.mipdata_->domain.col_upper_[col]), - mipsolver.mipdata_->domain.col_upper_[col] != kHighsInf, lpobjective, - lpredcost[col], maxNumSteps, maxNumStepsExp, lurkingColUpper[col], - lurkingColLower[col]); + static_cast( + mipsolver.mipdata_->getDomain().col_lower_[col]), + static_cast( + mipsolver.mipdata_->getDomain().col_upper_[col]), + mipsolver.mipdata_->getDomain().col_upper_[col] != kHighsInf, + lpobjective, lpredcost[col], maxNumSteps, maxNumStepsExp, + lurkingColUpper[col], lurkingColLower[col]); } else if (lpredcost[col] < -mipsolver.mipdata_->feastol) { // col >= (cutoffbound - lpobj)/redcost + ub // so for lurklb = lb + 1 to ub we can compute the necessary cutoff @@ -306,11 +311,13 @@ void HighsRedcostFixing::addRootRedcost(const HighsMipSolver& mipsolver, // cutoffbound = (lurklb - ub) * redcost + lpobj findLurkingBounds( col, HighsInt{-1}, - static_cast(mipsolver.mipdata_->domain.col_upper_[col]), - static_cast(mipsolver.mipdata_->domain.col_lower_[col]), - mipsolver.mipdata_->domain.col_lower_[col] != -kHighsInf, lpobjective, - lpredcost[col], maxNumSteps, maxNumStepsExp, lurkingColLower[col], - lurkingColUpper[col]); + static_cast( + mipsolver.mipdata_->getDomain().col_upper_[col]), + static_cast( + mipsolver.mipdata_->getDomain().col_lower_[col]), + mipsolver.mipdata_->getDomain().col_lower_[col] != -kHighsInf, + lpobjective, lpredcost[col], maxNumSteps, maxNumStepsExp, + lurkingColLower[col], lurkingColUpper[col]); } } } diff --git a/highs/mip/HighsRedcostFixing.h b/highs/mip/HighsRedcostFixing.h index 4e069b9134c..9ab5f12f16c 100644 --- a/highs/mip/HighsRedcostFixing.h +++ b/highs/mip/HighsRedcostFixing.h @@ -15,6 +15,7 @@ #include #include +#include "HighsConflictPool.h" #include "mip/HighsDomainChange.h" class HighsDomain; @@ -27,13 +28,14 @@ class HighsRedcostFixing { public: std::vector> getLurkingBounds( - const HighsMipSolver& mipsolver) const; + const HighsMipSolver& mipsolver, const HighsDomain& globaldom) const; void propagateRootRedcost(const HighsMipSolver& mipsolver); static void propagateRedCost(const HighsMipSolver& mipsolver, - HighsDomain& localdomain, - const HighsLpRelaxation& lp); + HighsDomain& localdomain, HighsDomain& globaldom, + const HighsLpRelaxation& lp, + HighsConflictPool& conflictpool); void addRootRedcost(const HighsMipSolver& mipsolver, const std::vector& lpredcost, double lpobjective); diff --git a/highs/mip/HighsSearch.cpp b/highs/mip/HighsSearch.cpp index 3a01a901795..a1cbc7c260a 100644 --- a/highs/mip/HighsSearch.cpp +++ b/highs/mip/HighsSearch.cpp @@ -14,12 +14,14 @@ #include "mip/HighsDomainChange.h" #include "mip/HighsMipSolverData.h" -HighsSearch::HighsSearch(HighsMipSolver& mipsolver, HighsPseudocost& pseudocost) - : mipsolver(mipsolver), +HighsSearch::HighsSearch(HighsMipWorker& mipworker, HighsPseudocost& pseudocost) + : mipworker(mipworker), + mipsolver(mipworker.getMipSolver()), lp(nullptr), - localdom(mipsolver.mipdata_->domain), + localdom(mipworker.getGlobalDomain()), pseudocost(pseudocost) { nnodes = 0; + nleaves = 0; treeweight = 0.0; depthoffset = 0; lpiterations = 0; @@ -47,7 +49,7 @@ double HighsSearch::checkSol(const std::vector& sol, if (!integerfeasible || !mipsolver.isColInteger(i)) continue; - if (fractionality(sol[i]) > mipsolver.mipdata_->feastol) { + if (fractionality(sol[i]) > getFeasTol()) { integerfeasible = false; } } @@ -75,7 +77,7 @@ bool HighsSearch::orbitsValidInChildNode( } double HighsSearch::getCutoffBound() const { - return std::min(mipsolver.mipdata_->upper_limit, upper_limit); + return std::min(getUpperLimit(), upper_limit); } void HighsSearch::setRINSNeighbourhood(const std::vector& basesol, @@ -85,7 +87,7 @@ void HighsSearch::setRINSNeighbourhood(const std::vector& basesol, if (localdom.col_lower_[i] == localdom.col_upper_[i]) continue; double intval = std::floor(basesol[i] + 0.5); - if (std::abs(relaxsol[i] - intval) < mipsolver.mipdata_->feastol) { + if (std::abs(relaxsol[i] - intval) < getFeasTol()) { if (localdom.col_lower_[i] < intval) localdom.changeBound(HighsBoundType::kLower, i, std::min(intval, localdom.col_upper_[i]), @@ -103,8 +105,8 @@ void HighsSearch::setRENSNeighbourhood(const std::vector& lpsol) { if (!mipsolver.isColInteger(i)) continue; if (localdom.col_lower_[i] == localdom.col_upper_[i]) continue; - double downval = std::floor(lpsol[i] + mipsolver.mipdata_->feastol); - double upval = std::ceil(lpsol[i] - mipsolver.mipdata_->feastol); + double downval = std::floor(lpsol[i] + getFeasTol()); + double upval = std::ceil(lpsol[i] - getFeasTol()); if (localdom.col_lower_[i] < downval) { localdom.changeBound(HighsBoundType::kLower, i, @@ -179,19 +181,19 @@ void HighsSearch::branchUpwards(HighsInt col, double newlb, } void HighsSearch::addBoundExceedingConflict() { - if (mipsolver.mipdata_->upper_limit != kHighsInf) { + if (getUpperLimit() != kHighsInf) { double rhs; - if (lp->computeDualProof(mipsolver.mipdata_->domain, - mipsolver.mipdata_->upper_limit, inds, vals, - rhs)) { - if (mipsolver.mipdata_->domain.infeasible()) return; + if (lp->computeDualProof(getDomain(), getUpperLimit(), inds, vals, rhs)) { + if (getDomain().infeasible()) return; localdom.conflictAnalysis(inds.data(), vals.data(), inds.size(), rhs, - mipsolver.mipdata_->conflictPool); + getConflictPool(), mipworker.getGlobalDomain(), + pseudocost); - HighsCutGeneration cutGen(*lp, mipsolver.mipdata_->cutpool); + HighsCutGeneration cutGen(*lp, getCutPool()); mipsolver.mipdata_->debugSolution.checkCut(inds.data(), vals.data(), inds.size(), rhs); - cutGen.generateConflict(localdom, inds, vals, rhs); + cutGen.generateConflict(localdom, mipworker.getGlobalDomain(), inds, vals, + rhs); } } } @@ -201,8 +203,8 @@ void HighsSearch::addInfeasibleConflict() { if (lp->getLpSolver().getModelStatus() == HighsModelStatus::kObjectiveBound) lp->performAging(); - if (lp->computeDualInfProof(mipsolver.mipdata_->domain, inds, vals, rhs)) { - if (mipsolver.mipdata_->domain.infeasible()) return; + if (lp->computeDualInfProof(getDomain(), inds, vals, rhs)) { + if (getDomain().infeasible()) return; // double minactlocal = 0.0; // double minactglobal = 0.0; // for (HighsInt i = 0; i < int(inds.size()); ++i) { @@ -216,12 +218,14 @@ void HighsSearch::addInfeasibleConflict() { //} // HighsInt oldnumcuts = cutpool.getNumCuts(); localdom.conflictAnalysis(inds.data(), vals.data(), inds.size(), rhs, - mipsolver.mipdata_->conflictPool); + getConflictPool(), mipworker.getGlobalDomain(), + pseudocost); - HighsCutGeneration cutGen(*lp, mipsolver.mipdata_->cutpool); + HighsCutGeneration cutGen(*lp, getCutPool()); mipsolver.mipdata_->debugSolution.checkCut(inds.data(), vals.data(), inds.size(), rhs); - cutGen.generateConflict(localdom, inds, vals, rhs); + cutGen.generateConflict(localdom, mipworker.getGlobalDomain(), inds, vals, + rhs); // if (cutpool.getNumCuts() > oldnumcuts) { // printf( @@ -270,38 +274,34 @@ HighsInt HighsSearch::selectBranchingCandidate(int64_t maxSbIters, double fracval = fracints[k].second; const double lower_residual = - (fracval - localdom.col_lower_[col]) - mipsolver.mipdata_->feastol; + (fracval - localdom.col_lower_[col]) - getFeasTol(); const bool lower_ok = lower_residual > 0; if (!lower_ok) highsLogUser(mipsolver.options_mip_->log_options, HighsLogType::kError, "HighsSearch::selectBranchingCandidate Error fracval = %g " "<= %g = %g + %g = " - "localdom.col_lower_[col] + mipsolver.mipdata_->feastol: " + "localdom.col_lower_[col] + getFeasTol(): " "Residual %g\n", - fracval, - localdom.col_lower_[col] + mipsolver.mipdata_->feastol, - localdom.col_lower_[col], mipsolver.mipdata_->feastol, - lower_residual); + fracval, localdom.col_lower_[col] + getFeasTol(), + localdom.col_lower_[col], getFeasTol(), lower_residual); const double upper_residual = - (localdom.col_upper_[col] - fracval) - mipsolver.mipdata_->feastol; + (localdom.col_upper_[col] - fracval) - getFeasTol(); const bool upper_ok = upper_residual > 0; if (!upper_ok) highsLogUser(mipsolver.options_mip_->log_options, HighsLogType::kError, "HighsSearch::selectBranchingCandidate Error fracval = %g " ">= %g = %g - %g = " - "localdom.col_upper_[col] - mipsolver.mipdata_->feastol: " + "localdom.col_upper_[col] - getFeasTol(): " "Residual %g\n", - fracval, - localdom.col_upper_[col] - mipsolver.mipdata_->feastol, - localdom.col_upper_[col], mipsolver.mipdata_->feastol, - upper_residual); + fracval, localdom.col_upper_[col] - getFeasTol(), + localdom.col_upper_[col], getFeasTol(), upper_residual); assert(lower_residual > -1e-12 && upper_residual > -1e-12); // assert(fracval > localdom.col_lower_[col] + - // mipsolver.mipdata_->feastol); assert(fracval < - // localdom.col_upper_[col] - mipsolver.mipdata_->feastol); + // getFeasTol()); assert(fracval < + // localdom.col_upper_[col] - getFeasTol()); if (pseudocost.isReliable(col)) { upscore[k] = pseudocost.getPseudocostUp(col, fracval); @@ -327,14 +327,14 @@ HighsInt HighsSearch::selectBranchingCandidate(int64_t maxSbIters, std::iota(evalqueue.begin(), evalqueue.end(), 0); auto numNodesUp = [&](HighsInt k) { - return mipsolver.mipdata_->nodequeue.numNodesUp(fracints[k].first); + return getNodeQueue().numNodesUp(fracints[k].first); }; auto numNodesDown = [&](HighsInt k) { - return mipsolver.mipdata_->nodequeue.numNodesDown(fracints[k].first); + return getNodeQueue().numNodesDown(fracints[k].first); }; - double minScore = mipsolver.mipdata_->feastol; + double minScore = getFeasTol(); auto selectBestScore = [&](bool finalSelection) { HighsInt best = -1; @@ -374,10 +374,9 @@ HighsInt HighsSearch::selectBranchingCandidate(int64_t maxSbIters, if (upnodes != 0 || downnodes != 0) nodes = (downnodes / (double)(numnodes)) * (upnodes / (double)(numnodes)); - if (score > bestscore || - (score > bestscore - mipsolver.mipdata_->feastol && - std::make_pair(nodes, numnodes) > - std::make_pair(bestnodes, bestnumnodes))) { + if (score > bestscore || (score > bestscore - getFeasTol() && + std::make_pair(nodes, numnodes) > + std::make_pair(bestnodes, bestnumnodes))) { bestscore = score; best = k; bestnodes = nodes; @@ -391,8 +390,8 @@ HighsInt HighsSearch::selectBranchingCandidate(int64_t maxSbIters, HighsLpRelaxation::Playground playground = lp->playground(); while (true) { - bool mustStop = getStrongBranchingLpIterations() >= maxSbIters || - mipsolver.mipdata_->checkLimits(); + bool mustStop = + getStrongBranchingLpIterations() >= maxSbIters || checkLimits(); HighsInt candidate = selectBestScore(mustStop); @@ -403,7 +402,7 @@ HighsInt HighsSearch::selectBranchingCandidate(int64_t maxSbIters, return candidate; } - lp->setObjectiveLimit(mipsolver.mipdata_->upper_limit); + lp->setObjectiveLimit(getUpperLimit()); HighsInt col = fracints[candidate].first; double fracval = fracints[candidate].second; @@ -421,21 +420,22 @@ HighsInt HighsSearch::selectBranchingCandidate(int64_t maxSbIters, double otherfracval = fracints[k].second; double otherdownval = std::floor(fracints[k].second); double otherupval = std::ceil(fracints[k].second); - if (sol[fracints[k].first] <= - otherdownval + mipsolver.mipdata_->feastol) { + if (sol[fracints[k].first] <= otherdownval + getFeasTol()) { if (localdom.col_upper_[fracints[k].first] > - otherdownval + mipsolver.mipdata_->feastol) { + otherdownval + getFeasTol()) { localdom.changeBound(HighsBoundType::kUpper, fracints[k].first, otherdownval); if (localdom.infeasible()) { - localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool); + localdom.conflictAnalysis( + getConflictPool(), mipworker.getGlobalDomain(), pseudocost); localdom.backtrack(); localdom.clearChangedCols(numChangedCols); continue; } localdom.propagate(); if (localdom.infeasible()) { - localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool); + localdom.conflictAnalysis( + getConflictPool(), mipworker.getGlobalDomain(), pseudocost); localdom.backtrack(); localdom.clearChangedCols(numChangedCols); continue; @@ -447,13 +447,13 @@ HighsInt HighsSearch::selectBranchingCandidate(int64_t maxSbIters, for (HighsInt j = domchgStackSize + 1; j < newStackSize; ++j) { if (domchgstack[j].boundtype == HighsBoundType::kLower) { if (domchgstack[j].boundval > - sol[domchgstack[j].column] + mipsolver.mipdata_->feastol) { + sol[domchgstack[j].column] + getFeasTol()) { solutionValid = false; break; } } else { if (domchgstack[j].boundval < - sol[domchgstack[j].column] - mipsolver.mipdata_->feastol) { + sol[domchgstack[j].column] - getFeasTol()) { solutionValid = false; break; } @@ -465,29 +465,30 @@ HighsInt HighsSearch::selectBranchingCandidate(int64_t maxSbIters, if (!solutionValid) continue; } - if (objdelta <= mipsolver.mipdata_->feastol) { + if (objdelta <= getFeasTol()) { pseudocost.addObservation(fracints[k].first, otherdownval - otherfracval, objdelta); markBranchingVarDownReliableAtNode(fracints[k].first); } downscore[k] = std::min(downscore[k], objdelta); - } else if (sol[fracints[k].first] >= - otherupval - mipsolver.mipdata_->feastol) { + } else if (sol[fracints[k].first] >= otherupval - getFeasTol()) { if (localdom.col_lower_[fracints[k].first] < - otherupval - mipsolver.mipdata_->feastol) { + otherupval - getFeasTol()) { localdom.changeBound(HighsBoundType::kLower, fracints[k].first, otherupval); if (localdom.infeasible()) { - localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool); + localdom.conflictAnalysis( + getConflictPool(), mipworker.getGlobalDomain(), pseudocost); localdom.backtrack(); localdom.clearChangedCols(numChangedCols); continue; } localdom.propagate(); if (localdom.infeasible()) { - localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool); + localdom.conflictAnalysis( + getConflictPool(), mipworker.getGlobalDomain(), pseudocost); localdom.backtrack(); localdom.clearChangedCols(numChangedCols); continue; @@ -499,13 +500,13 @@ HighsInt HighsSearch::selectBranchingCandidate(int64_t maxSbIters, for (HighsInt j = domchgStackSize + 1; j < newStackSize; ++j) { if (domchgstack[j].boundtype == HighsBoundType::kLower) { if (domchgstack[j].boundval > - sol[domchgstack[j].column] + mipsolver.mipdata_->feastol) { + sol[domchgstack[j].column] + getFeasTol()) { solutionValid = false; break; } } else { if (domchgstack[j].boundval < - sol[domchgstack[j].column] - mipsolver.mipdata_->feastol) { + sol[domchgstack[j].column] - getFeasTol()) { solutionValid = false; break; } @@ -518,7 +519,7 @@ HighsInt HighsSearch::selectBranchingCandidate(int64_t maxSbIters, if (!solutionValid) continue; } - if (objdelta <= mipsolver.mipdata_->feastol) { + if (objdelta <= getFeasTol()) { pseudocost.addObservation(fracints[k].first, otherupval - otherfracval, objdelta); markBranchingVarUpReliableAtNode(fracints[k].first); @@ -545,12 +546,13 @@ HighsInt HighsSearch::selectBranchingCandidate(int64_t maxSbIters, if (orbitalFixing) nodestack.back().stabilizerOrbits->orbitalFixing(localdom); else - mipsolver.mipdata_->symmetries.propagateOrbitopes(localdom); + getSymmetries().propagateOrbitopes(localdom); } inferences += localdom.getDomainChangeStack().size(); if (localdom.infeasible()) { - localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool); + localdom.conflictAnalysis(getConflictPool(), + mipworker.getGlobalDomain(), pseudocost); pseudocost.addCutoffObservation(col, upbranch); localdom.backtrack(); localdom.clearChangedCols(); @@ -584,7 +586,7 @@ HighsInt HighsSearch::selectBranchingCandidate(int64_t maxSbIters, double solobj = checkSol(sol, integerfeasible); double objdelta = std::max(solobj - lp->getObjective(), 0.0); - if (objdelta <= mipsolver.mipdata_->epsilon) objdelta = 0.0; + if (objdelta <= getEpsilon()) objdelta = 0.0; if (upbranch) { upscore[candidate] = objdelta; @@ -600,13 +602,12 @@ HighsInt HighsSearch::selectBranchingCandidate(int64_t maxSbIters, if (lp->unscaledPrimalFeasible(status) && integerfeasible) { double cutoffbnd = getCutoffBound(); - mipsolver.mipdata_->addIncumbent( - lp->getLpSolver().getSolution().col_value, solobj, - inheuristic ? kSolutionSourceHeuristic - : kSolutionSourceBranching); + addIncumbent(lp->getLpSolver().getSolution().col_value, solobj, + inheuristic ? kSolutionSourceHeuristic + : kSolutionSourceBranching); - if (mipsolver.mipdata_->upper_limit < cutoffbnd) - lp->setObjectiveLimit(mipsolver.mipdata_->upper_limit); + if (getUpperLimit() < cutoffbnd) + lp->setObjectiveLimit(getUpperLimit()); } if (lp->unscaledDualFeasible(status)) { @@ -615,7 +616,7 @@ HighsInt HighsSearch::selectBranchingCandidate(int64_t maxSbIters, } else { downbound[candidate] = solobj; } - if (solobj > mipsolver.mipdata_->optimality_limit) { + if (solobj > getOptimalityLimit()) { addBoundExceedingConflict(); bool pruned = solobj > getCutoffBound(); @@ -722,7 +723,9 @@ void HighsSearch::currentNodeToQueue(HighsNodeQueue& nodequeue) { localdom.propagate(); localdom.clearChangedCols(oldchangedcols); prune = localdom.infeasible(); - if (prune) localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool); + if (prune) + localdom.conflictAnalysis(getConflictPool(), mipworker.getGlobalDomain(), + pseudocost); } if (!prune) { std::vector branchPositions; @@ -761,7 +764,9 @@ void HighsSearch::openNodesToQueue(HighsNodeQueue& nodequeue) { localdom.propagate(); localdom.clearChangedCols(oldchangedcols); prune = localdom.infeasible(); - if (prune) localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool); + if (prune) + localdom.conflictAnalysis(getConflictPool(), + mipworker.getGlobalDomain(), pseudocost); } if (!prune) { std::vector branchPositions; @@ -789,19 +794,22 @@ void HighsSearch::openNodesToQueue(HighsNodeQueue& nodequeue) { } void HighsSearch::flushStatistics() { - mipsolver.mipdata_->num_nodes += nnodes; + getNumNodes() += nnodes; nnodes = 0; - mipsolver.mipdata_->pruned_treeweight += treeweight; + getNumLeaves() += nleaves; + nleaves = 0; + + getPrunedTreeweight() += treeweight; treeweight = 0; - mipsolver.mipdata_->total_lp_iterations += lpiterations; + getTotalLpIterations() += lpiterations; lpiterations = 0; - mipsolver.mipdata_->heuristic_lp_iterations += heurlpiterations; + getHeuristicLpIterations() += heurlpiterations; heurlpiterations = 0; - mipsolver.mipdata_->sb_lp_iterations += sblpiterations; + getSbLpIterations() += sblpiterations; sblpiterations = 0; } @@ -815,15 +823,17 @@ int64_t HighsSearch::getTotalLpIterations() const { int64_t HighsSearch::getLocalLpIterations() const { return lpiterations; } -int64_t HighsSearch::getLocalNodes() const { return nnodes; } +int64_t& HighsSearch::getLocalNodes() { return nnodes; } + +int64_t& HighsSearch::getLocalLeaves() { return nleaves; } int64_t HighsSearch::getStrongBranchingLpIterations() const { - return sblpiterations + mipsolver.mipdata_->sb_lp_iterations; + return sblpiterations + getSbLpIterations(); } void HighsSearch::resetLocalDomain() { - this->lp->resetToGlobalDomain(); - localdom = mipsolver.mipdata_->domain; + this->lp->resetToGlobalDomain(getDomain()); + localdom = getDomain(); #ifndef NDEBUG for (HighsInt i = 0; i != mipsolver.numCol(); ++i) { @@ -844,9 +854,9 @@ void HighsSearch::installNode(HighsNodeQueue::OpenNode&& node) { const auto& domchgstack = localdom.getDomainChangeStack(); for (HighsInt i : localdom.getBranchingPositions()) { HighsInt col = domchgstack[i].column; - if (mipsolver.mipdata_->symmetries.columnPosition[col] == -1) continue; + if (getSymmetries().columnPosition[col] == -1) continue; - if (!mipsolver.mipdata_->domain.isBinary(col) || + if (!getDomain().isBinary(col) || (domchgstack[i].boundtype == HighsBoundType::kLower && domchgstack[i].boundval == 1.0)) { globalSymmetriesValid = false; @@ -868,25 +878,23 @@ HighsSearch::NodeResult HighsSearch::evaluateNode() { const auto& domchgstack = localdom.getDomainChangeStack(); - if (!inheuristic && - currnode.lower_bound > mipsolver.mipdata_->optimality_limit) + if (!inheuristic && currnode.lower_bound > getOptimalityLimit()) return NodeResult::kSubOptimal; localdom.propagate(); if (!inheuristic && !localdom.infeasible()) { - if (mipsolver.mipdata_->symmetries.numPerms > 0 && - !currnode.stabilizerOrbits && + if (getSymmetries().numPerms > 0 && !currnode.stabilizerOrbits && (parent == nullptr || !parent->stabilizerOrbits || !parent->stabilizerOrbits->orbitCols.empty())) { - currnode.stabilizerOrbits = - mipsolver.mipdata_->symmetries.computeStabilizerOrbits(localdom); + currnode.stabilizerOrbits = getSymmetries().computeStabilizerOrbits( + localdom, stabilizerOrbitWorkspace); } if (currnode.stabilizerOrbits) currnode.stabilizerOrbits->orbitalFixing(localdom); else - mipsolver.mipdata_->symmetries.propagateOrbitopes(localdom); + getSymmetries().propagateOrbitopes(localdom); } if (parent != nullptr) { int64_t inferences = domchgstack.size() - (currnode.domgchgStackPos + 1); @@ -909,10 +917,11 @@ HighsSearch::NodeResult HighsSearch::evaluateNode() { upbranch); } - localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool); + localdom.conflictAnalysis(getConflictPool(), mipworker.getGlobalDomain(), + pseudocost); } else { lp->flushDomain(localdom); - lp->setObjectiveLimit(mipsolver.mipdata_->upper_limit); + lp->setObjectiveLimit(getUpperLimit()); #ifndef NDEBUG for (HighsInt i = 0; i != mipsolver.numCol(); ++i) { @@ -942,7 +951,8 @@ HighsSearch::NodeResult HighsSearch::evaluateNode() { upbranch); } - localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool); + localdom.conflictAnalysis(getConflictPool(), mipworker.getGlobalDomain(), + pseudocost); } else if (lp->scaledOptimal(status)) { lp->storeBasis(); lp->performAging(); @@ -965,12 +975,12 @@ HighsSearch::NodeResult HighsSearch::evaluateNode() { if (lp->unscaledPrimalFeasible(status)) { if (lp->getFractionalIntegers().empty()) { double cutoffbnd = getCutoffBound(); - mipsolver.mipdata_->addIncumbent( - lp->getLpSolver().getSolution().col_value, lp->getObjective(), - inheuristic ? kSolutionSourceHeuristic - : kSolutionSourceEvaluateNode); - if (mipsolver.mipdata_->upper_limit < cutoffbnd) - lp->setObjectiveLimit(mipsolver.mipdata_->upper_limit); + addIncumbent(lp->getLpSolver().getSolution().col_value, + lp->getObjective(), + inheuristic ? kSolutionSourceHeuristic + : kSolutionSourceEvaluateNode); + if (getUpperLimit() < cutoffbnd) + lp->setObjectiveLimit(getUpperLimit()); if (lp->unscaledDualFeasible(status)) { addBoundExceedingConflict(); @@ -987,15 +997,16 @@ HighsSearch::NodeResult HighsSearch::evaluateNode() { if (currnode.lower_bound > getCutoffBound()) { result = NodeResult::kBoundExceeding; addBoundExceedingConflict(); - } else if (mipsolver.mipdata_->upper_limit != kHighsInf) { + } else if (getUpperLimit() != kHighsInf) { if (!inheuristic) { - double gap = mipsolver.mipdata_->upper_limit - lp->getObjective(); + double gap = getUpperLimit() - lp->getObjective(); lp->computeBasicDegenerateDuals( - gap + std::max(10 * mipsolver.mipdata_->feastol, - mipsolver.mipdata_->epsilon * gap), - &localdom); + gap + std::max(10 * getFeasTol(), getEpsilon() * gap), + localdom, getDomain(), getConflictPool(), true); } - HighsRedcostFixing::propagateRedCost(mipsolver, localdom, *lp); + HighsRedcostFixing::propagateRedCost(mipsolver, localdom, + mipworker.getGlobalDomain(), + *lp, getConflictPool()); localdom.propagate(); if (localdom.infeasible()) { result = NodeResult::kDomainInfeasible; @@ -1009,13 +1020,15 @@ HighsSearch::NodeResult HighsSearch::evaluateNode() { parent->branchingdecision.column, upbranch); } - localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool); + localdom.conflictAnalysis( + getConflictPool(), mipworker.getGlobalDomain(), pseudocost); } else if (!localdom.getChangedCols().empty()) { return evaluateNode(); } } else { if (!inheuristic) { - lp->computeBasicDegenerateDuals(kHighsInf, &localdom); + lp->computeBasicDegenerateDuals(kHighsInf, localdom, getDomain(), + getConflictPool(), true); localdom.propagate(); if (localdom.infeasible()) { result = NodeResult::kDomainInfeasible; @@ -1029,7 +1042,8 @@ HighsSearch::NodeResult HighsSearch::evaluateNode() { parent->branchingdecision.column, upbranch); } - localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool); + localdom.conflictAnalysis( + getConflictPool(), mipworker.getGlobalDomain(), pseudocost); } else if (!localdom.getChangedCols().empty()) { return evaluateNode(); } @@ -1071,7 +1085,7 @@ HighsSearch::NodeResult HighsSearch::evaluateNode() { treeweight += std::ldexp(1.0, 1 - getCurrentDepth()); currnode.opensubtrees = 0; } else if (!inheuristic) { - if (currnode.lower_bound > mipsolver.mipdata_->optimality_limit) { + if (currnode.lower_bound > getOptimalityLimit()) { result = NodeResult::kSubOptimal; addBoundExceedingConflict(); } @@ -1148,10 +1162,8 @@ HighsSearch::NodeResult HighsSearch::branch() { childLb = downNodeLb; break; case ChildSelectionRule::kRootSol: { - double downPrio = pseudocost.getAvgInferencesDown(col) + - mipsolver.mipdata_->epsilon; - double upPrio = - pseudocost.getAvgInferencesUp(col) + mipsolver.mipdata_->epsilon; + double downPrio = pseudocost.getAvgInferencesDown(col) + getEpsilon(); + double upPrio = pseudocost.getAvgInferencesUp(col) + getEpsilon(); double downVal = std::floor(currnode.branching_point); double upVal = std::ceil(currnode.branching_point); if (!subrootsol.empty()) { @@ -1167,8 +1179,8 @@ HighsSearch::NodeResult HighsSearch::branch() { } else { if (currnode.lp_objective != -kHighsInf) subrootsol = lp->getSolution().col_value; - if (!mipsolver.mipdata_->rootlpsol.empty()) { - double rootsol = mipsolver.mipdata_->rootlpsol[col]; + if (!getRootLpSol().empty()) { + double rootsol = getRootLpSol()[col]; if (rootsol < downVal) rootsol = downVal; else if (rootsol > upVal) @@ -1178,7 +1190,7 @@ HighsSearch::NodeResult HighsSearch::branch() { downPrio *= (1.0 + (rootsol - currnode.branching_point)); } } - if (upPrio + mipsolver.mipdata_->epsilon >= downPrio) { + if (upPrio + getEpsilon() >= downPrio) { currnode.branchingdecision.boundtype = HighsBoundType::kLower; currnode.branchingdecision.boundval = upVal; currnode.other_child_lb = downNodeLb; @@ -1223,9 +1235,9 @@ HighsSearch::NodeResult HighsSearch::branch() { break; case ChildSelectionRule::kBestCost: { if (pseudocost.getPseudocostUp(col, currnode.branching_point, - mipsolver.mipdata_->feastol) > + getFeasTol()) > pseudocost.getPseudocostDown(col, currnode.branching_point, - mipsolver.mipdata_->feastol)) { + getFeasTol())) { currnode.branchingdecision.boundtype = HighsBoundType::kUpper; currnode.branchingdecision.boundval = std::floor(currnode.branching_point); @@ -1259,8 +1271,8 @@ HighsSearch::NodeResult HighsSearch::branch() { case ChildSelectionRule::kDisjunction: { int64_t numnodesup; int64_t numnodesdown; - numnodesup = mipsolver.mipdata_->nodequeue.numNodesUp(col); - numnodesdown = mipsolver.mipdata_->nodequeue.numNodesDown(col); + numnodesup = getNodeQueue().numNodesUp(col); + numnodesdown = getNodeQueue().numNodesDown(col); if (numnodesup > numnodesdown) { currnode.branchingdecision.boundtype = HighsBoundType::kLower; currnode.branchingdecision.boundval = @@ -1293,14 +1305,12 @@ HighsSearch::NodeResult HighsSearch::branch() { case ChildSelectionRule::kHybridInferenceCost: { double upVal = std::ceil(currnode.branching_point); double downVal = std::floor(currnode.branching_point); - double upScore = - (1 + pseudocost.getAvgInferencesUp(col)) / - pseudocost.getPseudocostUp(col, currnode.branching_point, - mipsolver.mipdata_->feastol); - double downScore = - (1 + pseudocost.getAvgInferencesDown(col)) / - pseudocost.getPseudocostDown(col, currnode.branching_point, - mipsolver.mipdata_->feastol); + double upScore = (1 + pseudocost.getAvgInferencesUp(col)) / + pseudocost.getPseudocostUp( + col, currnode.branching_point, getFeasTol()); + double downScore = (1 + pseudocost.getAvgInferencesDown(col)) / + pseudocost.getPseudocostDown( + col, currnode.branching_point, getFeasTol()); if (upScore >= downScore) { currnode.branchingdecision.boundtype = HighsBoundType::kLower; @@ -1340,7 +1350,7 @@ HighsSearch::NodeResult HighsSearch::branch() { // fail in the LP solution process pseudocost.setDegeneracyFactor(1e6); - for (HighsInt i : mipsolver.mipdata_->integral_cols) { + for (HighsInt i : getIntegralCols()) { if (localdom.col_upper_[i] - localdom.col_lower_[i] < 0.5) continue; double fracval; @@ -1365,20 +1375,17 @@ HighsSearch::NodeResult HighsSearch::branch() { double cost = lp->unscaledDualFeasible(lp->getStatus()) ? lp->getSolution().col_dual[i] : mipsolver.colCost(i); - if (std::fabs(cost) > mipsolver.mipdata_->feastol && - getCutoffBound() < kHighsInf) { + if (std::fabs(cost) > getFeasTol() && getCutoffBound() < kHighsInf) { // branch in direction of worsening cost first in case the column has // cost and we do have an upper bound branchUpwards = cost > 0; } else if (pseudocost.getAvgInferencesUp(i) > - pseudocost.getAvgInferencesDown(i) + - mipsolver.mipdata_->feastol) { + pseudocost.getAvgInferencesDown(i) + getFeasTol()) { // column does not have (reduced) cost above tolerance so branch in // direction of more inferences branchUpwards = true; } else if (pseudocost.getAvgInferencesUp(i) < - pseudocost.getAvgInferencesDown(i) - - mipsolver.mipdata_->feastol) { + pseudocost.getAvgInferencesDown(i) - getFeasTol()) { branchUpwards = false; } else { // number of inferences give a tie, so we branch in the direction that @@ -1426,7 +1433,10 @@ HighsSearch::NodeResult HighsSearch::branch() { // create a fresh LP only with model rows since all integer columns are // fixed, the cutting planes are not required and the LP could not be solved // so we want to make it as easy as possible + // + // LP relaxation instantiation HighsLpRelaxation lpCopy(mipsolver); + lpCopy.setProfiling(mipsolver.profiling_); lpCopy.loadModel(); lpCopy.getLpSolver().changeColsBounds(0, mipsolver.numCol() - 1, localdom.col_lower_.data(), @@ -1524,7 +1534,7 @@ bool HighsSearch::backtrack(bool recoverBasis) { if (nodestack.back().stabilizerOrbits) nodestack.back().stabilizerOrbits->orbitalFixing(localdom); else - mipsolver.mipdata_->symmetries.propagateOrbitopes(localdom); + getSymmetries().propagateOrbitopes(localdom); } if (localdom.infeasible()) { localdom.clearChangedCols(oldNumChangedCols); @@ -1573,10 +1583,12 @@ bool HighsSearch::backtrack(bool recoverBasis) { if (!prune) { localdom.propagate(); prune = localdom.infeasible(); - if (prune) localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool); + if (prune) + localdom.conflictAnalysis(getConflictPool(), + mipworker.getGlobalDomain(), pseudocost); } if (!prune) { - mipsolver.mipdata_->symmetries.propagateOrbitopes(localdom); + getSymmetries().propagateOrbitopes(localdom); prune = localdom.infeasible(); } if (!prune && passStabilizerToChildNode && currnode.stabilizerOrbits) { @@ -1647,7 +1659,7 @@ bool HighsSearch::backtrackPlunge(HighsNodeQueue& nodequeue) { if (nodestack.back().stabilizerOrbits) nodestack.back().stabilizerOrbits->orbitalFixing(localdom); else - mipsolver.mipdata_->symmetries.propagateOrbitopes(localdom); + getSymmetries().propagateOrbitopes(localdom); } if (localdom.infeasible()) { localdom.clearChangedCols(oldNumChangedCols); @@ -1703,10 +1715,12 @@ bool HighsSearch::backtrackPlunge(HighsNodeQueue& nodequeue) { if (!prune) { localdom.propagate(); prune = localdom.infeasible(); - if (prune) localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool); + if (prune) + localdom.conflictAnalysis(getConflictPool(), + mipworker.getGlobalDomain(), pseudocost); } if (!prune) { - mipsolver.mipdata_->symmetries.propagateOrbitopes(localdom); + getSymmetries().propagateOrbitopes(localdom); prune = localdom.infeasible(); } if (!prune && passStabilizerToChildNode && currnode.stabilizerOrbits) { @@ -1721,7 +1735,7 @@ bool HighsSearch::backtrackPlunge(HighsNodeQueue& nodequeue) { } nodelb = std::max(nodelb, localdom.getObjectiveLowerBound()); - bool nodeToQueue = nodelb > mipsolver.mipdata_->optimality_limit; + bool nodeToQueue = nodelb > 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. @@ -1752,7 +1766,7 @@ bool HighsSearch::backtrackPlunge(HighsNodeQueue& nodequeue) { // printf("nodeScore: %g, ancestorScore: %g\n", nodeScore, // ancestorScore); nodeToQueue = ancestorScoreInactive - ancestorScoreActive > - nodeScore + mipsolver.mipdata_->feastol; + nodeScore + getFeasTol(); break; } } @@ -1851,19 +1865,20 @@ bool HighsSearch::backtrackUntilDepth(HighsInt targetDepth) { return true; } -HighsSearch::NodeResult HighsSearch::dive() { +HighsSearch::NodeResult HighsSearch::dive(bool ramp) { reliableatnode.clear(); do { ++nnodes; NodeResult result = evaluateNode(); - if (mipsolver.mipdata_->checkLimits(nnodes)) return result; + if (checkLimits(nnodes)) return result; if (result != NodeResult::kOpen) return result; result = branch(); if (result != NodeResult::kBranched) return result; + if (ramp) return result; } while (true); } @@ -1879,3 +1894,123 @@ void HighsSearch::solveDepthFirst(int64_t maxbacktracks) { } while (backtrack()); } + +double HighsSearch::getFeasTol() const { return mipsolver.mipdata_->feastol; } + +double HighsSearch::getUpperLimit() const { + if (!mipsolver.mipdata_->parallelLockActive()) { + return mipsolver.mipdata_->upper_limit; + } else { + return mipworker.upper_limit; + } +} + +double HighsSearch::getEpsilon() const { return mipsolver.mipdata_->epsilon; } + +double HighsSearch::getOptimalityLimit() const { + if (!mipsolver.mipdata_->parallelLockActive()) { + return mipsolver.mipdata_->optimality_limit; + } else { + return mipworker.optimality_limit; + } +} + +const std::vector& HighsSearch::getRootLpSol() const { + return mipsolver.mipdata_->rootlpsol; +} + +const std::vector& HighsSearch::getIntegralCols() const { + return mipsolver.mipdata_->integral_cols; +} + +HighsDomain& HighsSearch::getDomain() const { + return mipworker.getGlobalDomain(); +} + +HighsConflictPool& HighsSearch::getConflictPool() const { + return mipworker.getConflictPool(); +} + +HighsCutPool& HighsSearch::getCutPool() const { return mipworker.getCutPool(); } + +const HighsNodeQueue& HighsSearch::getNodeQueue() const { + return mipsolver.mipdata_->nodequeue; +} + +bool HighsSearch::checkLimits(int64_t nodeOffset) const { + if (mipsolver.mipdata_->parallelLockActive()) { + return checkLocalLimits(); + }; + return mipsolver.mipdata_->checkLimits(nodeOffset); +} + +bool HighsSearch::checkLocalLimits() const { + if (!mipsolver.submip && mipworker.upper_bound < kHighsInf && + mipsolver.options_mip_->objective_target > -kHighsInf) { + const double internal_target = + static_cast(mipsolver.orig_model_->sense_) * + mipsolver.options_mip_->objective_target - + mipsolver.model_->offset_; + if (mipworker.upper_bound < internal_target) { + return true; + } + } + + if (mipsolver.options_mip_->mip_max_nodes != kHighsIInf && + mipsolver.mipdata_->num_nodes + nnodes >= + mipsolver.options_mip_->mip_max_nodes) { + return true; + } + + if (mipsolver.options_mip_->mip_max_leaves != kHighsIInf && + mipsolver.mipdata_->num_leaves + nleaves >= + mipsolver.options_mip_->mip_max_leaves) { + return true; + } + + if (mipsolver.options_mip_->time_limit < kHighsInf && + mipsolver.timer_.read() >= mipsolver.options_mip_->time_limit) { + return true; + } + + return false; +} + +HighsSymmetries& HighsSearch::getSymmetries() const { + return mipsolver.mipdata_->symmetries; +} + +bool HighsSearch::addIncumbent(const std::vector& sol, double solobj, + const int solution_source, + const bool print_display_line) { + if (mipsolver.mipdata_->parallelLockActive()) { + return mipworker.addIncumbent(sol, solobj, solution_source); + } else { + return mipsolver.mipdata_->addIncumbent(sol, solobj, solution_source, + print_display_line); + } +} + +int64_t& HighsSearch::getNumNodes() { return mipsolver.mipdata_->num_nodes; } + +int64_t& HighsSearch::getNumLeaves() { return mipsolver.mipdata_->num_leaves; } + +HighsCDouble& HighsSearch::getPrunedTreeweight() { + return mipsolver.mipdata_->pruned_treeweight; +} + +int64_t& HighsSearch::getTotalLpIterations() { + return mipsolver.mipdata_->total_lp_iterations; +} + +int64_t& HighsSearch::getHeuristicLpIterations() { + return mipsolver.mipdata_->heuristic_lp_iterations; +} + +int64_t& HighsSearch::getSbLpIterations() { + return mipsolver.mipdata_->sb_lp_iterations; +} + +int64_t& HighsSearch::getSbLpIterations() const { + return mipsolver.mipdata_->sb_lp_iterations; +} diff --git a/highs/mip/HighsSearch.h b/highs/mip/HighsSearch.h index 50bf0d25bc9..8f0a36f0445 100644 --- a/highs/mip/HighsSearch.h +++ b/highs/mip/HighsSearch.h @@ -13,9 +13,11 @@ #include #include "mip/HighsConflictPool.h" +#include "mip/HighsDebugSol.h" #include "mip/HighsDomain.h" #include "mip/HighsLpRelaxation.h" #include "mip/HighsMipSolver.h" +#include "mip/HighsMipWorker.h" #include "mip/HighsNodeQueue.h" #include "mip/HighsPseudocost.h" #include "mip/HighsSeparation.h" @@ -23,16 +25,21 @@ #include "util/HighsHash.h" class HighsMipSolver; +class HighsMipWorker; class HighsImplications; class HighsCliqueTable; class HighsSearch { - HighsMipSolver& mipsolver; + public: + HighsMipWorker& mipworker; + + const HighsMipSolver& mipsolver; HighsLpRelaxation* lp; HighsDomain localdom; HighsPseudocost& pseudocost; HighsRandom random; int64_t nnodes; + int64_t nleaves; int64_t lpiterations; int64_t heurlpiterations; int64_t sblpiterations; @@ -112,6 +119,7 @@ class HighsSearch { std::vector subrootsol; std::vector nodestack; + StabilizerOrbitWorkspace stabilizerOrbitWorkspace; HighsHashTable reliableatnode; int branchingVarReliableAtNodeFlags(HighsInt col) const { @@ -138,7 +146,7 @@ class HighsSearch { bool orbitsValidInChildNode(const HighsDomainChange& branchChg) const; public: - HighsSearch(HighsMipSolver& mipsolver, HighsPseudocost& pseudocost); + HighsSearch(HighsMipWorker& mipworker, HighsPseudocost& pseudocost); void setRINSNeighbourhood(const std::vector& basesol, const std::vector& relaxsol); @@ -176,7 +184,9 @@ class HighsSearch { int64_t getLocalLpIterations() const; - int64_t getLocalNodes() const; + int64_t& getLocalNodes(); + + int64_t& getLocalLeaves(); int64_t getStrongBranchingLpIterations() const; @@ -225,7 +235,7 @@ class HighsSearch { void printDisplayLine(char first, bool header = false); - NodeResult dive(); + NodeResult dive(const bool ramp = false); HighsDomain& getLocalDomain() { return localdom; } @@ -236,6 +246,38 @@ class HighsSearch { const HighsPseudocost& getPseudoCost() const { return pseudocost; } void solveDepthFirst(int64_t maxbacktracks = 1); + + double getFeasTol() const; + double getUpperLimit() const; + double getEpsilon() const; + double getOptimalityLimit() const; + + const std::vector& getRootLpSol() const; + const std::vector& getIntegralCols() const; + + HighsDomain& getDomain() const; + HighsConflictPool& getConflictPool() const; + HighsCutPool& getCutPool() const; + + const HighsNodeQueue& getNodeQueue() const; + + bool checkLimits(int64_t nodeOffset = 0) const; + + bool checkLocalLimits() const; + + HighsSymmetries& getSymmetries() const; + + bool addIncumbent(const std::vector& sol, double solobj, + const int solution_source, + const bool print_display_line = true); + + int64_t& getNumNodes(); + int64_t& getNumLeaves(); + HighsCDouble& getPrunedTreeweight(); + int64_t& getTotalLpIterations(); + int64_t& getHeuristicLpIterations(); + int64_t& getSbLpIterations(); + int64_t& getSbLpIterations() const; }; #endif diff --git a/highs/mip/HighsSeparation.cpp b/highs/mip/HighsSeparation.cpp index a85691a1d02..485f10eca57 100644 --- a/highs/mip/HighsSeparation.cpp +++ b/highs/mip/HighsSeparation.cpp @@ -22,12 +22,19 @@ #include "mip/HighsTableauSeparator.h" #include "mip/HighsTransformedLp.h" -HighsSeparation::HighsSeparation(const HighsMipSolver& mipsolver) { - if (mipsolver.analysis_.analyse_mip_time) { +HighsSeparation::HighsSeparation(HighsMipWorker& mipworker) + : mipworker_(mipworker) { + /* + if (mipworker.mipsolver_.profiling_->mip_) { implBoundClock = - mipsolver.analysis_.getSepaClockIndex(kImplboundSepaString); - cliqueClock = mipsolver.analysis_.getSepaClockIndex(kCliqueSepaString); + mipworker.mipsolver_.profiling_->getSepaClockIndex(kImplboundSepaString); + cliqueClock = + mipworker.mipsolver_.profiling_->getSepaClockIndex(kCliqueSepaString); } + */ + implBoundClock = 990; + cliqueClock = 991; + const HighsMipSolver& mipsolver = mipworker.getMipSolver(); separators.emplace_back(new HighsTableauSeparator(mipsolver)); separators.emplace_back(new HighsPathSeparator(mipsolver)); separators.emplace_back(new HighsModkSeparator(mipsolver)); @@ -40,7 +47,7 @@ HighsInt HighsSeparation::separationRound(HighsDomain& propdomain, HighsMipSolverData& mipdata = *lp->getMipSolver().mipdata_; auto propagateAndResolve = [&]() { - if (propdomain.infeasible() || mipdata.domain.infeasible()) { + if (propdomain.infeasible() || mipworker_.getGlobalDomain().infeasible()) { status = HighsLpRelaxation::Status::kInfeasible; propdomain.clearChangedCols(); return -1; @@ -53,8 +60,11 @@ HighsInt HighsSeparation::separationRound(HighsDomain& propdomain, return -1; } - mipdata.cliquetable.cleanupFixed(mipdata.domain); - if (mipdata.domain.infeasible()) { + // only modify cliquetable for master worker. + if (&propdomain == &mipdata.getDomain()) + mipdata.cliquetable.cleanupFixed(mipdata.getDomain()); + + if (mipworker_.getGlobalDomain().infeasible()) { status = HighsLpRelaxation::Status::kInfeasible; propdomain.clearChangedCols(); return -1; @@ -67,7 +77,8 @@ HighsInt HighsSeparation::separationRound(HighsDomain& propdomain, status = lp->resolveLp(&propdomain); if (!lp->scaledOptimal(status)) return -1; - if (&propdomain == &mipdata.domain && lp->unscaledDualFeasible(status)) { + if (&propdomain == &mipdata.getDomain() && + lp->unscaledDualFeasible(status)) { mipdata.redcostfixing.addRootRedcost( mipdata.mipsolver, lp->getSolution().col_dual, lp->getObjective()); if (mipdata.upper_limit != kHighsInf) @@ -78,10 +89,14 @@ HighsInt HighsSeparation::separationRound(HighsDomain& propdomain, return numBoundChgs; }; - lp->getMipSolver().analysis_.mipTimerStart(implBoundClock); - mipdata.implications.separateImpliedBounds(*lp, lp->getSolution().col_value, - mipdata.cutpool, mipdata.feastol); - lp->getMipSolver().analysis_.mipTimerStop(implBoundClock); + if (!mipdata.parallelLockActive()) + lp->getMipSolver().profiling_->start(implBoundClock); + mipdata.implications.separateImpliedBounds( + *lp, lp->getSolution().col_value, mipworker_.getCutPool(), + mipdata.feastol, mipworker_.getGlobalDomain(), + mipdata.parallelLockActive()); + if (!mipdata.parallelLockActive()) + lp->getMipSolver().profiling_->stop(implBoundClock); HighsInt ncuts = 0; HighsInt numboundchgs = propagateAndResolve(); @@ -90,10 +105,18 @@ HighsInt HighsSeparation::separationRound(HighsDomain& propdomain, else ncuts += numboundchgs; - lp->getMipSolver().analysis_.mipTimerStart(cliqueClock); - mipdata.cliquetable.separateCliques(lp->getMipSolver(), sol.col_value, - mipdata.cutpool, mipdata.feastol); - lp->getMipSolver().analysis_.mipTimerStop(cliqueClock); + if (!mipdata.parallelLockActive()) + lp->getMipSolver().profiling_->start(cliqueClock); + mipdata.cliquetable.separateCliques( + lp->getMipSolver(), sol.col_value, mipworker_.getCutPool(), + mipdata.feastol, + mipdata.parallelLockActive() ? mipworker_.randgen + : mipdata.cliquetable.getRandgen(), + mipdata.parallelLockActive() + ? mipworker_.getNumNeighbourhoodQueries() + : mipdata.cliquetable.getNumNeighbourhoodQueries()); + if (!mipdata.parallelLockActive()) + lp->getMipSolver().profiling_->stop(cliqueClock); numboundchgs = propagateAndResolve(); if (numboundchgs == -1) @@ -101,19 +124,22 @@ HighsInt HighsSeparation::separationRound(HighsDomain& propdomain, else ncuts += numboundchgs; - if (&propdomain != &mipdata.domain) - lp->computeBasicDegenerateDuals(mipdata.feastol, &propdomain); + if (&propdomain != &mipworker_.getGlobalDomain()) + lp->computeBasicDegenerateDuals(mipdata.feastol, propdomain, + mipworker_.getGlobalDomain(), + mipworker_.getConflictPool(), true); - HighsTransformedLp transLp(*lp, mipdata.implications); - if (mipdata.domain.infeasible()) { + HighsTransformedLp transLp(*lp, mipdata.implications, + mipworker_.getGlobalDomain()); + if (mipworker_.getGlobalDomain().infeasible()) { status = HighsLpRelaxation::Status::kInfeasible; return 0; } HighsLpAggregator lpAggregator(*lp); for (const std::unique_ptr& separator : separators) { - separator->run(*lp, lpAggregator, transLp, mipdata.cutpool); - if (mipdata.domain.infeasible()) { + separator->run(*lp, lpAggregator, transLp, mipworker_.getCutPool()); + if (mipworker_.getGlobalDomain().infeasible()) { status = HighsLpRelaxation::Status::kInfeasible; return 0; } @@ -125,14 +151,23 @@ HighsInt HighsSeparation::separationRound(HighsDomain& propdomain, else ncuts += numboundchgs; - mipdata.cutpool.separate(sol.col_value, propdomain, cutset, mipdata.feastol); + mipworker_.getCutPool().separate(sol.col_value, propdomain, cutset, + mipdata.feastol, mipdata.cutpools); + // Also separate the global cut pool + if (&mipworker_.getCutPool() != &mipdata.getCutPool()) { + mipdata.getCutPool().separate(sol.col_value, propdomain, cutset, + mipdata.feastol, mipdata.cutpools, true); + } if (cutset.numCuts() > 0) { ncuts += cutset.numCuts(); lp->addCuts(cutset); status = lp->resolveLp(&propdomain); lp->performAging(true); - if (&propdomain == &mipdata.domain && lp->unscaledDualFeasible(status)) { + + // only for the master domain. + if (&propdomain == &mipdata.getDomain() && + lp->unscaledDualFeasible(status)) { mipdata.redcostfixing.addRootRedcost( mipdata.mipsolver, lp->getSolution().col_dual, lp->getObjective()); if (mipdata.upper_limit != kHighsInf) @@ -154,11 +189,17 @@ void HighsSeparation::separate(HighsDomain& propdomain) { while (lp->getObjective() < mipsolver.mipdata_->optimality_limit) { double lastobj = lp->getObjective(); - size_t nlpiters = -lp->getNumLpIterations(); + int64_t nlpiters = -lp->getNumLpIterations(); HighsInt ncuts = separationRound(propdomain, status); nlpiters += lp->getNumLpIterations(); - mipsolver.mipdata_->sepa_lp_iterations += nlpiters; - mipsolver.mipdata_->total_lp_iterations += nlpiters; + + if (mipsolver.mipdata_->parallelLockActive()) { + mipworker_.getSepaLpIterations() += nlpiters; + } else { + mipsolver.mipdata_->sepa_lp_iterations += nlpiters; + mipsolver.mipdata_->total_lp_iterations += nlpiters; + } + // printf("separated %" HIGHSINT_FORMAT " cuts\n", ncuts); // printf( @@ -181,6 +222,7 @@ void HighsSeparation::separate(HighsDomain& propdomain) { // printf("no separation, just aging. status: %" HIGHSINT_FORMAT "\n", // (HighsInt)status); lp->performAging(true); - mipsolver.mipdata_->cutpool.performAging(); + + mipworker_.getCutPool().performAging(); } } diff --git a/highs/mip/HighsSeparation.h b/highs/mip/HighsSeparation.h index ea09959b61c..cb79d112275 100644 --- a/highs/mip/HighsSeparation.h +++ b/highs/mip/HighsSeparation.h @@ -16,6 +16,7 @@ #include "mip/HighsSeparator.h" class HighsMipSolver; +class HighsMipWorker; class HighsImplications; class HighsCliqueTable; @@ -28,9 +29,10 @@ class HighsSeparation { void setLpRelaxation(HighsLpRelaxation* lp) { this->lp = lp; } - HighsSeparation(const HighsMipSolver& mipsolver); + HighsSeparation(HighsMipWorker& mipworker); private: + HighsMipWorker& mipworker_; HighsInt implBoundClock; HighsInt cliqueClock; std::vector> separators; diff --git a/highs/mip/HighsSeparator.cpp b/highs/mip/HighsSeparator.cpp index 0415c556ef6..a4675a33de2 100644 --- a/highs/mip/HighsSeparator.cpp +++ b/highs/mip/HighsSeparator.cpp @@ -9,6 +9,7 @@ #include +#include "HighsMipSolverData.h" #include "mip/HighsCutPool.h" #include "mip/HighsLpRelaxation.h" #include "mip/HighsMipSolver.h" @@ -16,6 +17,7 @@ HighsSeparator::HighsSeparator(const HighsMipSolver& mipsolver, const std::string& name) : numCutsFound(0), numCalls(0) { + /* this->clockIndex = -1; // Don't get the clock index when analyse_mip_time is false - as // will generally be the case, and always so for sub-MIPs @@ -23,6 +25,8 @@ HighsSeparator::HighsSeparator(const HighsMipSolver& mipsolver, this->clockIndex = mipsolver.analysis_.getSepaClockIndex(name); assert(this->clockIndex > 0); } + */ + this->clockIndex = 999; } void HighsSeparator::run(HighsLpRelaxation& lpRelaxation, @@ -31,9 +35,11 @@ void HighsSeparator::run(HighsLpRelaxation& lpRelaxation, ++numCalls; HighsInt currNumCuts = cutpool.getNumCuts(); - lpRelaxation.getMipSolver().analysis_.mipTimerStart(clockIndex); + if (!lpRelaxation.getMipSolver().mipdata_->parallelLockActive()) + lpRelaxation.getMipSolver().profiling_->start(clockIndex); separateLpSolution(lpRelaxation, lpAggregator, transLp, cutpool); - lpRelaxation.getMipSolver().analysis_.mipTimerStop(clockIndex); + if (!lpRelaxation.getMipSolver().mipdata_->parallelLockActive()) + lpRelaxation.getMipSolver().profiling_->stop(clockIndex); numCutsFound += cutpool.getNumCuts() - currNumCuts; } diff --git a/highs/mip/HighsTableauSeparator.cpp b/highs/mip/HighsTableauSeparator.cpp index ded391dccc7..fa58ef2c7e7 100644 --- a/highs/mip/HighsTableauSeparator.cpp +++ b/highs/mip/HighsTableauSeparator.cpp @@ -230,12 +230,12 @@ void HighsTableauSeparator::separateLpSolution(HighsLpRelaxation& lpRelaxation, double rhs = 0; cutGen.generateCut(transLp, baseRowInds, baseRowVals, rhs); - if (mip.mipdata_->domain.infeasible()) break; + if (transLp.getGlobaldom().infeasible()) break; lpAggregator.getCurrentAggregation(baseRowInds, baseRowVals, true); rhs = 0; cutGen.generateCut(transLp, baseRowInds, baseRowVals, rhs); - if (mip.mipdata_->domain.infeasible()) break; + if (transLp.getGlobaldom().infeasible()) break; lpAggregator.clear(); if (bestScore == -1.0 && cutpool.getNumCuts() != numCuts) diff --git a/highs/mip/HighsTransformedLp.cpp b/highs/mip/HighsTransformedLp.cpp index 703b377e091..609c7dd15f7 100644 --- a/highs/mip/HighsTransformedLp.cpp +++ b/highs/mip/HighsTransformedLp.cpp @@ -13,8 +13,9 @@ #include "util/HighsIntegers.h" HighsTransformedLp::HighsTransformedLp(const HighsLpRelaxation& lprelaxation, - HighsImplications& implications) - : lprelaxation(lprelaxation) { + HighsImplications& implications, + const HighsDomain& globaldom) + : lprelaxation(lprelaxation), globaldom_(globaldom) { assert(lprelaxation.scaledOptimal(lprelaxation.getStatus())); const HighsMipSolver& mipsolver = implications.mipsolver; const HighsSolution& lpSolution = lprelaxation.getLpSolver().getSolution(); @@ -34,22 +35,24 @@ HighsTransformedLp::HighsTransformedLp(const HighsLpRelaxation& lprelaxation, vectorsum.setDimension(numTransformedCol); for (HighsInt col : mipsolver.mipdata_->continuous_cols) { - mipsolver.mipdata_->implications.cleanupVarbounds(col); - if (mipsolver.mipdata_->domain.infeasible()) return; + if (!mipsolver.mipdata_->parallelLockActive()) + mipsolver.mipdata_->implications.cleanupVarbounds(col); - if (mipsolver.mipdata_->domain.isFixed(col)) continue; + if (globaldom_.infeasible()) return; - double bestub = mipsolver.mipdata_->domain.col_upper_[col]; + if (globaldom_.isFixed(col)) continue; + + double bestub = globaldom_.col_upper_[col]; simpleUbDist[col] = bestub - lpSolution.col_value[col]; if (simpleUbDist[col] <= mipsolver.mipdata_->feastol) simpleUbDist[col] = 0.0; - bestVub[col] = implications.getBestVub(col, lpSolution, bestub); + bestVub[col] = implications.getBestVub(col, lpSolution, bestub, globaldom_); - double bestlb = mipsolver.mipdata_->domain.col_lower_[col]; + double bestlb = globaldom_.col_lower_[col]; simpleLbDist[col] = lpSolution.col_value[col] - bestlb; if (simpleLbDist[col] <= mipsolver.mipdata_->feastol) simpleLbDist[col] = 0.0; - bestVlb[col] = implications.getBestVlb(col, lpSolution, bestlb); + bestVlb[col] = implications.getBestVlb(col, lpSolution, bestlb, globaldom_); lbDist[col] = lpSolution.col_value[col] - bestlb; if (lbDist[col] <= mipsolver.mipdata_->feastol) lbDist[col] = 0.0; @@ -60,11 +63,13 @@ HighsTransformedLp::HighsTransformedLp(const HighsLpRelaxation& lprelaxation, } for (HighsInt col : mipsolver.mipdata_->integral_cols) { - double bestub = mipsolver.mipdata_->domain.col_upper_[col]; - double bestlb = mipsolver.mipdata_->domain.col_lower_[col]; + double bestub = globaldom_.col_upper_[col]; + double bestlb = globaldom_.col_lower_[col]; + + if (!mipsolver.mipdata_->parallelLockActive()) + mipsolver.mipdata_->implications.cleanupVarbounds(col); - mipsolver.mipdata_->implications.cleanupVarbounds(col); - if (mipsolver.mipdata_->domain.infeasible()) return; + if (globaldom_.infeasible()) return; simpleUbDist[col] = bestub - lpSolution.col_value[col]; if (simpleUbDist[col] <= mipsolver.mipdata_->feastol) simpleUbDist[col] = 0.0; @@ -76,10 +81,10 @@ HighsTransformedLp::HighsTransformedLp(const HighsLpRelaxation& lprelaxation, if (simpleBndDist > 0 && std::fabs(HighsIntegers::nearestInteger(lpSolution.col_value[col]) - lpSolution.col_value[col]) < mipsolver.mipdata_->feastol) { - bestVub[col] = - mipsolver.mipdata_->implications.getBestVub(col, lpSolution, bestub); - bestVlb[col] = - mipsolver.mipdata_->implications.getBestVlb(col, lpSolution, bestlb); + bestVub[col] = mipsolver.mipdata_->implications.getBestVub( + col, lpSolution, bestub, globaldom_); + bestVlb[col] = mipsolver.mipdata_->implications.getBestVlb( + col, lpSolution, bestlb, globaldom_); lbDist[col] = lpSolution.col_value[col] - bestlb; if (lbDist[col] <= mipsolver.mipdata_->feastol) lbDist[col] = 0.0; @@ -105,8 +110,8 @@ HighsTransformedLp::HighsTransformedLp(const HighsLpRelaxation& lprelaxation, HighsInt indexOffset = mipsolver.numCol(); for (HighsInt row = 0; row != numLpRow; ++row) { HighsInt slackIndex = indexOffset + row; - double bestub = lprelaxation.slackUpper(row); - double bestlb = lprelaxation.slackLower(row); + double bestub = lprelaxation.slackUpper(row, globaldom_); + double bestlb = lprelaxation.slackLower(row, globaldom_); if (bestlb == bestub) continue; @@ -140,13 +145,15 @@ bool HighsTransformedLp::transform(std::vector& vals, HighsInt numNz = inds.size(); auto getLb = [&](HighsInt col) { - return (col < slackOffset ? mip.mipdata_->domain.col_lower_[col] - : lprelaxation.slackLower(col - slackOffset)); + return (col < slackOffset + ? globaldom_.col_lower_[col] + : lprelaxation.slackLower(col - slackOffset, globaldom_)); }; auto getUb = [&](HighsInt col) { - return (col < slackOffset ? mip.mipdata_->domain.col_upper_[col] - : lprelaxation.slackUpper(col - slackOffset)); + return (col < slackOffset + ? globaldom_.col_upper_[col] + : lprelaxation.slackUpper(col - slackOffset, globaldom_)); }; auto remove = [&](HighsInt position) { @@ -467,11 +474,11 @@ bool HighsTransformedLp::untransform(std::vector& vals, } case BoundType::kSimpleLb: { if (col < slackOffset) { - tmpRhs += vals[i] * mip.mipdata_->domain.col_lower_[col]; + tmpRhs += vals[i] * globaldom_.col_lower_[col]; vectorsum.add(col, vals[i]); } else { HighsInt row = col - slackOffset; - tmpRhs += vals[i] * lprelaxation.slackLower(row); + tmpRhs += vals[i] * lprelaxation.slackLower(row, globaldom_); HighsInt rowlen; const HighsInt* rowinds; @@ -485,11 +492,11 @@ bool HighsTransformedLp::untransform(std::vector& vals, } case BoundType::kSimpleUb: { if (col < slackOffset) { - tmpRhs -= vals[i] * mip.mipdata_->domain.col_upper_[col]; + tmpRhs -= vals[i] * globaldom_.col_upper_[col]; vectorsum.add(col, -vals[i]); } else { HighsInt row = col - slackOffset; - tmpRhs -= vals[i] * lprelaxation.slackUpper(row); + tmpRhs -= vals[i] * lprelaxation.slackUpper(row, globaldom_); vals[i] = -vals[i]; HighsInt rowlen; @@ -525,15 +532,15 @@ bool HighsTransformedLp::untransform(std::vector& vals, if (absval <= mip.mipdata_->feastol) { if (val > 0) { - if (mip.mipdata_->domain.col_lower_[col] == -kHighsInf) + if (globaldom_.col_lower_[col] == -kHighsInf) abort = true; else - tmpRhs -= val * mip.mipdata_->domain.col_lower_[col]; + tmpRhs -= val * globaldom_.col_lower_[col]; } else { - if (mip.mipdata_->domain.col_upper_[col] == kHighsInf) + if (globaldom_.col_upper_[col] == kHighsInf) abort = true; else - tmpRhs -= val * mip.mipdata_->domain.col_upper_[col]; + tmpRhs -= val * globaldom_.col_upper_[col]; } return true; } diff --git a/highs/mip/HighsTransformedLp.h b/highs/mip/HighsTransformedLp.h index 5c3d2d01db7..bb7e929224c 100644 --- a/highs/mip/HighsTransformedLp.h +++ b/highs/mip/HighsTransformedLp.h @@ -29,6 +29,7 @@ class HighsLpRelaxation; class HighsTransformedLp { private: const HighsLpRelaxation& lprelaxation; + const HighsDomain& globaldom_; std::vector> bestVub; std::vector> bestVlb; @@ -48,7 +49,8 @@ class HighsTransformedLp { public: HighsTransformedLp(const HighsLpRelaxation& lprelaxation, - HighsImplications& implications); + HighsImplications& implications, + const HighsDomain& globaldom); double boundDistance(HighsInt col) const { return boundDist[col]; } @@ -58,6 +60,8 @@ class HighsTransformedLp { bool untransform(std::vector& vals, std::vector& inds, double& rhs, bool integral = false); + + const HighsDomain& getGlobaldom() const { return globaldom_; } }; #endif diff --git a/highs/mip/MipTimer.h b/highs/mip/MipTimer.h index f1044de0658..33a195b411c 100644 --- a/highs/mip/MipTimer.h +++ b/highs/mip/MipTimer.h @@ -11,14 +11,15 @@ #ifndef MIP_MIPTIMER_H_ #define MIP_MIPTIMER_H_ -// Clocks for profiling the MIP dual mip solver -enum iClockMip { +// Clocks for profiling the MIP solver +enum iClockMip : int { kMipClockTotal = 0, - kMipClockPresolve, + kMipClockPresolve = kToSubSolver, kMipClockSolve, kMipClockPostsolve, // Level 1 - kMipClockInit, + kFromMipClock, + kMipClockInit = kFromMipClock, kMipClockRunPresolve, kMipClockRunSetup, kMipClockFeasibilityJump, @@ -111,18 +112,112 @@ enum iClockMip { kMipClockProbingImplications, - kNumMipClock //!< Number of MIP clocks + kLastMipClock = kMipClockProbingImplications, + kToMipClock = kLastMipClock + 1 }; +const HighsInt kNumThreadMipClock = kLastMipClock; + const double tolerance_percent_report = 0.1; +inline void initialiseMipProfilingNames(std::vector& name) { + assert(name.size() == static_cast(kToMipClock)); + // Level 1 - Should correspond to kMipClockTotal + name[kMipClockInit] = "Initialise"; + name[kMipClockRunPresolve] = "Run presolve"; + name[kMipClockRunSetup] = "Run setup"; + name[kMipClockFeasibilityJump] = "Feasibility jump"; + name[kMipClockTrivialHeuristics] = "Trivial heuristics"; + name[kMipClockEvaluateRootNode] = "Evaluate root node"; + name[kMipClockPerformAging0] = "Perform aging 0"; + name[kMipClockSearch] = "Search"; + // kMipClockPostsolve + + // Evaluate root node + name[kMipClockStartSymmetryDetection] = "Start symmetry detection"; + name[kMipClockStartAnalyticCentreComputation] = "A-centre - start"; + name[kMipClockEvaluateRootLp] = "Evaluate root LP"; + name[kMipClockSeparateLpCuts] = "Separate LP cuts"; + name[kMipClockRandomizedRounding] = "Randomized rounding"; + name[kMipClockPerformRestart] = "Perform restart"; + name[kMipClockRootSeparation] = "Root separation"; + name[kMipClockFinishAnalyticCentreComputation] = "A-centre - finish"; + name[kMipClockRootCentralRounding] = "Root central rounding"; + name[kMipClockRootSeparationRound0] = "Root separation round 0"; + name[kMipClockRootHeuristicsReducedCost] = "Root heuristics reduced cost"; + name[kMipClockRootSeparationRound1] = "Root separation round 1"; + name[kMipClockRootHeuristicsRens] = "Root heuristics RENS"; + name[kMipClockRootSeparationRound2] = "Root separation round 2"; + name[kMipClockRootFeasibilityPump] = "Root feasibility pump"; + name[kMipClockRootSeparationRound3] = "Root separation round 3"; + name[kMipClockEvaluateRootNode0] = "kMipClockEvaluateRootNode0"; + name[kMipClockEvaluateRootNode1] = "kMipClockEvaluateRootNode1"; + name[kMipClockEvaluateRootNode2] = "kMipClockEvaluateRootNode2"; + + // Separation + name[kMipClockRootSeparationRound] = "Separation"; + name[kMipClockRootSeparationFinishAnalyticCentreComputation] = + "A-centre - finish"; + name[kMipClockRootSeparationCentralRounding] = "Central rounding"; + name[kMipClockRootSeparationEvaluateRootLp] = "Evaluate root LP"; + + /* + clock[kMipClockImplboundSepa] = + timer_pointer->clock_def(kImplboundSepaString.c_str()); + clock[kMipClockCliqueSepa] = + timer_pointer->clock_def(kCliqueSepaString.c_str()); + clock[kMipClockTableauSepa] = + timer_pointer->clock_def(kTableauSepaString.c_str()); + clock[kMipClockPathAggrSepa] = + timer_pointer->clock_def(kPathAggrSepaString.c_str()); + clock[kMipClockModKSepa] = + timer_pointer->clock_def(kModKSepaString.c_str()); + */ + // Presolve - Should correspond to kMipClockRunPresolve + name[kMipClockProbingPresolve] = "Probing - presolve"; + name[kMipClockEnumerationPresolve] = "Enumeration - presolve"; + + // Search - Should correspond to kMipClockSearch + name[kMipClockPerformAging1] = "Perform aging 1"; + name[kMipClockDive] = "Dive"; + name[kMipClockOpenNodesToQueue0] = "Open nodes to queue 0"; + name[kMipClockDomainPropgate] = "Domain propagate"; + name[kMipClockPruneInfeasibleNodes] = "Prune infeasible nodes"; + name[kMipClockUpdateLocalDomain] = "Update local domain"; + name[kMipClockNodeSearch] = "Node search"; + + // Dive - Should correspond to kMipClockDive + name[kMipClockDiveEvaluateNode] = "Evaluate node"; + name[kMipClockDivePrimalHeuristics] = "Dive primal heuristics"; + name[kMipClockTheDive] = "The dive"; + name[kMipClockBacktrackPlunge] = "Backtrack plunge"; + name[kMipClockPerformAging2] = "Perform aging 2"; + + // Primal heuristics - Should correspond to kMipDiveClockPrimalHeuristics + name[kMipClockDiveRandomizedRounding] = "Dive Randomized rounding"; + name[kMipClockDiveRens] = "Dive RENS"; + name[kMipClockDiveRins] = "Dive RINS"; + + // Node search + name[kMipClockCurrentNodeToQueue] = "Current node to queue"; + name[kMipClockSearchBacktrack] = "Search backtrack"; + name[kMipClockNodePrunedLoop] = "Pruned loop search"; + name[kMipClockOpenNodesToQueue1] = "Open nodes to queue 1"; + name[kMipClockEvaluateNode1] = "Evaluate node 1"; + name[kMipClockNodeSearchSeparation] = "Node search separation"; + name[kMipClockStoreBasis] = "Store basis"; + + name[kMipClockProbingImplications] = "Probing - implications"; +}; + class MipTimer { public: - void initialiseMipClocks(HighsTimerClock& mip_timer_clock) { + void initialiseMipClocks(HighsTimerClock& mip_timer_clock, + const HighsInt thread_mip_clock_offset) { HighsTimer* timer_pointer = mip_timer_clock.timer_pointer_; std::vector& clock = mip_timer_clock.clock_; - clock.resize(kNumMipClock); + clock.resize(kLastMipClock); clock[kMipClockTotal] = 0; clock[kMipClockPresolve] = timer_pointer->clock_def("MIP presolve"); clock[kMipClockSolve] = timer_pointer->clock_def("MIP solve"); @@ -135,21 +230,18 @@ class MipTimer { // clock IDs that need to equal clock[kMipClockHipoSolveAnalyticCentreLp] // and clock[kMipClockIpxSolveAnalyticCentreLp] // - // Define the clocks for evaluating the LPs first, so that - // clock[kMipClockHipoSolveAnalyticCentreLp] and - // clock[kMipClockIpxSolveAnalyticCentreLp] aren't changed by inserting new - // clocks + // Define the clocks for evaluating the LPs first, so that they + // aren't changed by inserting new clocks clock[kMipClockDuSimplexBasisSolveLp] = timer_pointer->clock_def("Solve LP - du simplex basis"); + assert(clock[kMipClockDuSimplexBasisSolveLp] == + thread_mip_clock_offset + 7); clock[kMipClockDuSimplexNoBasisSolveLp] = timer_pointer->clock_def("Solve LP - du simplex no basis"); - assert(clock[kMipClockDuSimplexNoBasisSolveLp] == 8); clock[kMipClockHipoSolveAnalyticCentreLp] = timer_pointer->clock_def("Solve LP: HiPO analytic centre"); clock[kMipClockIpxSolveAnalyticCentreLp] = timer_pointer->clock_def("Solve LP: IPX analytic centre"); - assert(clock[kMipClockHipoSolveAnalyticCentreLp] == 9); - assert(clock[kMipClockIpxSolveAnalyticCentreLp] == 10); clock[kMipClockHipoSolveLp] = timer_pointer->clock_def("Solve LP: HiPO"); clock[kMipClockIpxSolveLp] = timer_pointer->clock_def("Solve LP: IPX"); clock[kMipClockPrSimplexBasisSolveLp] = @@ -223,6 +315,7 @@ class MipTimer { clock[kMipClockRootSeparationEvaluateRootLp] = timer_pointer->clock_def("Evaluate root LP"); + /* clock[kMipClockImplboundSepa] = timer_pointer->clock_def(kImplboundSepaString.c_str()); clock[kMipClockCliqueSepa] = @@ -233,7 +326,7 @@ class MipTimer { timer_pointer->clock_def(kPathAggrSepaString.c_str()); clock[kMipClockModKSepa] = timer_pointer->clock_def(kModKSepaString.c_str()); - + */ // Presolve - Should correspond to kMipClockRunPresolve clock[kMipClockProbingPresolve] = timer_pointer->clock_def("Probing - presolve"); diff --git a/highs/presolve/HPresolve.cpp b/highs/presolve/HPresolve.cpp index 4bfcb146f85..fe418c03492 100644 --- a/highs/presolve/HPresolve.cpp +++ b/highs/presolve/HPresolve.cpp @@ -162,9 +162,9 @@ bool HPresolve::okSetInput(HighsMipSolver& mipsolver, mipsolver.model_ = &mipsolver.mipdata_->presolvedModel; } else { mipsolver.mipdata_->presolvedModel.col_lower_ = - mipsolver.mipdata_->domain.col_lower_; + mipsolver.mipdata_->getDomain().col_lower_; mipsolver.mipdata_->presolvedModel.col_upper_ = - mipsolver.mipdata_->domain.col_upper_; + mipsolver.mipdata_->getDomain().col_upper_; } return okSetInput(mipsolver.mipdata_->presolvedModel, *mipsolver.options_mip_, @@ -1014,17 +1014,17 @@ void HPresolve::shrinkProblem(HighsPostsolveStack& postsolve_stack) { if (mipsolver != nullptr) { mipsolver->mipdata_->rowMatrixSet = false; mipsolver->mipdata_->objectiveFunction = HighsObjectiveFunction(*mipsolver); - mipsolver->mipdata_->domain = HighsDomain(*mipsolver); + mipsolver->mipdata_->getDomain() = HighsDomain(*mipsolver); mipsolver->mipdata_->cliquetable.rebuild(model->num_col_, postsolve_stack, - mipsolver->mipdata_->domain, + mipsolver->mipdata_->getDomain(), newColIndex, newRowIndex); mipsolver->mipdata_->implications.rebuild(model->num_col_, newColIndex, newRowIndex); - mipsolver->mipdata_->cutpool = + mipsolver->mipdata_->getCutPool() = HighsCutPool(mipsolver->model_->num_col_, mipsolver->options_mip_->mip_pool_age_limit, - mipsolver->options_mip_->mip_pool_soft_limit); - mipsolver->mipdata_->conflictPool = + mipsolver->options_mip_->mip_pool_soft_limit, 0); + mipsolver->mipdata_->getConflictPool() = HighsConflictPool(5 * mipsolver->options_mip_->mip_pool_age_limit, mipsolver->options_mip_->mip_pool_soft_limit); @@ -1451,7 +1451,7 @@ HPresolve::Result HPresolve::dominatedColumns( HPresolve::Result HPresolve::prepareProbing( HighsPostsolveStack& postsolve_stack, bool& firstCall) { - HighsDomain& domain = mipsolver->mipdata_->domain; + HighsDomain& domain = mipsolver->mipdata_->getDomain(); HighsCliqueTable& cliquetable = mipsolver->mipdata_->cliquetable; shrinkProblem(postsolve_stack); @@ -1520,7 +1520,7 @@ HPresolve::Result HPresolve::finaliseProbing( HighsPostsolveStack& postsolve_stack, bool firstCall, HighsInt& numVarsFixed, HighsInt& numBndsTightened, HighsInt& numVarsSubstituted, HighsInt& liftedNonZeros) { - HighsDomain& domain = mipsolver->mipdata_->domain; + HighsDomain& domain = mipsolver->mipdata_->getDomain(); HighsCliqueTable& cliquetable = mipsolver->mipdata_->cliquetable; cliquetable.cleanupFixed(domain); @@ -1595,7 +1595,7 @@ std::pair HPresolve::computeProbingScore( } HPresolve::Result HPresolve::runProbing(HighsPostsolveStack& postsolve_stack) { - mipsolver->analysis_.mipTimerStart(kMipClockProbingPresolve); + mipsolver->profiling_->start(kMipClockProbingPresolve); probingEarlyAbort = false; HighsInt oldNumProbed = numProbed; @@ -1604,11 +1604,11 @@ HPresolve::Result HPresolve::runProbing(HighsPostsolveStack& postsolve_stack) { bool firstCall = false; Result prepareResult = prepareProbing(postsolve_stack, firstCall); if (prepareResult != Result::kOk) { - mipsolver->analysis_.mipTimerStop(kMipClockProbingPresolve); + mipsolver->profiling_->stop(kMipClockProbingPresolve); return prepareResult; } - HighsDomain& domain = mipsolver->mipdata_->domain; + HighsDomain& domain = mipsolver->mipdata_->getDomain(); HighsCliqueTable& cliquetable = mipsolver->mipdata_->cliquetable; HighsImplications& implications = mipsolver->mipdata_->implications; @@ -1830,7 +1830,7 @@ HPresolve::Result HPresolve::runProbing(HighsPostsolveStack& postsolve_stack) { implications.storeLiftingOpportunity = nullptr; if (domain.infeasible()) { - mipsolver->analysis_.mipTimerStop(kMipClockProbingPresolve); + mipsolver->profiling_->stop(kMipClockProbingPresolve); return Result::kPrimalInfeasible; } } @@ -1864,7 +1864,7 @@ HPresolve::Result HPresolve::runProbing(HighsPostsolveStack& postsolve_stack) { } } - mipsolver->analysis_.mipTimerStop(kMipClockProbingPresolve); + mipsolver->profiling_->stop(kMipClockProbingPresolve); return checkLimits(postsolve_stack); } @@ -1874,7 +1874,7 @@ HPresolve::Result HPresolve::liftingForProbing( // al. (2019) Presolve Reductions in Mixed Integer Programming. INFORMS // Journal on Computing 32(2):473-506. HighsCliqueTable& cliquetable = mipsolver->mipdata_->cliquetable; - const HighsDomain& domain = mipsolver->mipdata_->domain; + const HighsDomain& domain = mipsolver->mipdata_->getDomain(); // collect best lifting opportunity for each row in a vector typedef std::pair liftingvar; @@ -5210,17 +5210,17 @@ HPresolve::Result HPresolve::enumerateSolutions( HighsPostsolveStack& postsolve_stack) { // enumerate all solutions for pure binary constraints with a small number of // variables - mipsolver->analysis_.mipTimerStart(kMipClockEnumerationPresolve); + mipsolver->profiling_->start(kMipClockEnumerationPresolve); // prepare probing bool firstCall = false; Result prepareResult = prepareProbing(postsolve_stack, firstCall); if (prepareResult != Result::kOk) { - mipsolver->analysis_.mipTimerStop(kMipClockEnumerationPresolve); + mipsolver->profiling_->stop(kMipClockEnumerationPresolve); return prepareResult; } - HighsDomain& domain = mipsolver->mipdata_->domain; + HighsDomain& domain = mipsolver->mipdata_->getDomain(); HighsCliqueTable& cliquetable = mipsolver->mipdata_->cliquetable; typedef std::tuple candidateRow; @@ -5465,7 +5465,7 @@ HPresolve::Result HPresolve::enumerateSolutions( auto handleInfeasibility = [&](bool infeasible) { if (infeasible) { - mipsolver->analysis_.mipTimerStop(kMipClockEnumerationPresolve); + mipsolver->profiling_->stop(kMipClockEnumerationPresolve); return Result::kPrimalInfeasible; } return Result::kOk; @@ -5676,7 +5676,7 @@ HPresolve::Result HPresolve::enumerateSolutions( static_cast(numBndsTightened), static_cast(numVarsSubstituted)); - mipsolver->analysis_.mipTimerStop(kMipClockEnumerationPresolve); + mipsolver->profiling_->stop(kMipClockEnumerationPresolve); return checkLimits(postsolve_stack); } @@ -6345,9 +6345,10 @@ HighsModelStatus HPresolve::run(HighsPostsolveStack& postsolve_stack) { if (mipsolver != nullptr) { mipsolver->mipdata_->cliquetable.setPresolveFlag(false); mipsolver->mipdata_->cliquetable.setMaxEntries(numNonzeros()); - mipsolver->mipdata_->domain.addCutpool(mipsolver->mipdata_->cutpool); - mipsolver->mipdata_->domain.addConflictPool( - mipsolver->mipdata_->conflictPool); + mipsolver->mipdata_->getDomain().addCutpool( + mipsolver->mipdata_->getCutPool()); + mipsolver->mipdata_->getDomain().addConflictPool( + mipsolver->mipdata_->getConflictPool()); if (mipsolver->mipdata_->numRestarts != 0) { std::vector cutinds; @@ -6371,7 +6372,7 @@ HighsModelStatus HPresolve::run(HighsPostsolveStack& postsolve_stack) { cutvals.push_back(Avalue[j]); } - mipsolver->mipdata_->cutpool.addCut( + mipsolver->mipdata_->getCutPool().addCut( *mipsolver, cutinds.data(), cutvals.data(), cutinds.size(), model->row_upper_[i], rowsizeInteger[i] + rowsizeImplInt[i] == rowsize[i] && diff --git a/highs/presolve/HighsPostsolveStack.h b/highs/presolve/HighsPostsolveStack.h index 9c8a58498f3..009cf282b21 100644 --- a/highs/presolve/HighsPostsolveStack.h +++ b/highs/presolve/HighsPostsolveStack.h @@ -609,8 +609,23 @@ class HighsPostsolveStack { /// undo presolve steps for primal dual solution and basis void undo(const HighsOptions& options, HighsSolution& solution, - HighsBasis& basis, const HighsInt report_col = -1) { - reductionValues.resetPosition(); + HighsBasis& basis, const HighsInt report_col = -1, + const bool thread_safe = false) { + HighsDataStack reductionValuesCopy; + std::vector colValuesCopy; + std::vector rowValuesCopy; + if (!thread_safe) { + reductionValues.resetPosition(); + } else { + reductionValuesCopy = reductionValues; + reductionValuesCopy.resetPosition(); + colValuesCopy = colValues; + rowValuesCopy = rowValues; + } + HighsDataStack& reductionValues_ = + thread_safe ? reductionValuesCopy : reductionValues; + std::vector& colValues_ = thread_safe ? colValuesCopy : colValues; + std::vector& rowValues_ = thread_safe ? rowValuesCopy : rowValues; // Verify that undo can be performed assert(solution.value_valid); @@ -648,97 +663,97 @@ class HighsPostsolveStack { switch (reductions[i - 1].first) { case ReductionType::kLinearTransform: { LinearTransform reduction; - reductionValues.pop(reduction); + reductionValues_.pop(reduction); reduction.undo(options, solution); break; } case ReductionType::kFreeColSubstitution: { FreeColSubstitution reduction; - reductionValues.pop(colValues); - reductionValues.pop(rowValues); - reductionValues.pop(reduction); - reduction.undo(options, rowValues, colValues, solution, basis); + reductionValues_.pop(colValues_); + reductionValues_.pop(rowValues_); + reductionValues_.pop(reduction); + reduction.undo(options, rowValues_, colValues_, solution, basis); break; } case ReductionType::kDoubletonEquation: { DoubletonEquation reduction; - reductionValues.pop(colValues); - reductionValues.pop(reduction); - reduction.undo(options, colValues, solution, basis); + reductionValues_.pop(colValues_); + reductionValues_.pop(reduction); + reduction.undo(options, colValues_, solution, basis); break; } case ReductionType::kEqualityRowAddition: { EqualityRowAddition reduction; - reductionValues.pop(rowValues); - reductionValues.pop(reduction); - reduction.undo(options, rowValues, solution, basis); + reductionValues_.pop(rowValues_); + reductionValues_.pop(reduction); + reduction.undo(options, rowValues_, solution, basis); break; } case ReductionType::kEqualityRowAdditions: { EqualityRowAdditions reduction; - reductionValues.pop(colValues); - reductionValues.pop(rowValues); - reductionValues.pop(reduction); - reduction.undo(options, rowValues, colValues, solution, basis); + reductionValues_.pop(colValues_); + reductionValues_.pop(rowValues_); + reductionValues_.pop(reduction); + reduction.undo(options, rowValues_, colValues_, solution, basis); break; } case ReductionType::kSingletonRow: { SingletonRow reduction; - reductionValues.pop(reduction); + reductionValues_.pop(reduction); reduction.undo(options, solution, basis); break; } case ReductionType::kFixedCol: { FixedCol reduction; - reductionValues.pop(colValues); - reductionValues.pop(reduction); - reduction.undo(options, colValues, solution, basis); + reductionValues_.pop(colValues_); + reductionValues_.pop(reduction); + reduction.undo(options, colValues_, solution, basis); break; } case ReductionType::kRedundantRow: { RedundantRow reduction; - reductionValues.pop(reduction); + reductionValues_.pop(reduction); reduction.undo(options, solution, basis); break; } case ReductionType::kForcingRow: { ForcingRow reduction; - reductionValues.pop(rowValues); - reductionValues.pop(reduction); - reduction.undo(options, rowValues, solution, basis); + reductionValues_.pop(rowValues_); + reductionValues_.pop(reduction); + reduction.undo(options, rowValues_, solution, basis); break; } case ReductionType::kForcingColumn: { ForcingColumn reduction; - reductionValues.pop(colValues); - reductionValues.pop(reduction); - reduction.undo(options, colValues, solution, basis); + reductionValues_.pop(colValues_); + reductionValues_.pop(reduction); + reduction.undo(options, colValues_, solution, basis); break; } case ReductionType::kForcingColumnRemovedRow: { ForcingColumnRemovedRow reduction; - reductionValues.pop(rowValues); - reductionValues.pop(reduction); - reduction.undo(options, rowValues, solution, basis); + reductionValues_.pop(rowValues_); + reductionValues_.pop(reduction); + reduction.undo(options, rowValues_, solution, basis); break; } case ReductionType::kDuplicateRow: { DuplicateRow reduction; - reductionValues.pop(reduction); + reductionValues_.pop(reduction); reduction.undo(options, solution, basis); break; } case ReductionType::kDuplicateColumn: { DuplicateColumn reduction; - reductionValues.pop(reduction); + reductionValues_.pop(reduction); reduction.undo(options, solution, basis); break; } case ReductionType::kSlackColSubstitution: { SlackColSubstitution reduction; - reductionValues.pop(rowValues); - reductionValues.pop(reduction); - reduction.undo(options, rowValues, solution, basis); + reductionValues_.pop(rowValues_); + reductionValues_.pop(reduction); + reduction.undo(options, rowValues_, solution, basis); break; } default: @@ -763,14 +778,15 @@ class HighsPostsolveStack { /// undo presolve steps for primal solution void undoPrimal(const HighsOptions& options, HighsSolution& solution, - const HighsInt report_col = -1) { + const HighsInt report_col = -1, + const bool thread_safe = false) { // Call to reductionValues.resetPosition(); seems unnecessary as // it's the first thing done in undo - reductionValues.resetPosition(); + if (!thread_safe) reductionValues.resetPosition(); HighsBasis basis; basis.valid = false; solution.dual_valid = false; - undo(options, solution, basis, report_col); + undo(options, solution, basis, report_col, thread_safe); } /* diff --git a/highs/presolve/HighsSymmetry.cpp b/highs/presolve/HighsSymmetry.cpp index b10ded6589e..2820fd6002f 100644 --- a/highs/presolve/HighsSymmetry.cpp +++ b/highs/presolve/HighsSymmetry.cpp @@ -109,11 +109,14 @@ void HighsSymmetries::clear() { numGenerators = 0; } -void HighsSymmetries::mergeOrbits(HighsInt v1, HighsInt v2) { +void HighsSymmetries::mergeOrbits(HighsInt v1, HighsInt v2, + std::vector& orbitPartition, + std::vector& orbitSize, + std::vector& linkCompressionStack) { if (v1 == v2) return; - HighsInt orbit1 = getOrbit(v1); - HighsInt orbit2 = getOrbit(v2); + const HighsInt orbit1 = getOrbit(v1, orbitPartition, linkCompressionStack); + const HighsInt orbit2 = getOrbit(v2, orbitPartition, linkCompressionStack); if (orbit1 == orbit2) return; @@ -124,13 +127,14 @@ void HighsSymmetries::mergeOrbits(HighsInt v1, HighsInt v2) { orbitPartition[orbit1] = orbit2; orbitSize[orbit2] += orbitSize[orbit1]; } - - return; } -HighsInt HighsSymmetries::getOrbit(HighsInt col) { +HighsInt HighsSymmetries::getOrbit( + HighsInt col, std::vector& orbitPartition, + std::vector& linkCompressionStack) { HighsInt i = columnPosition[col]; if (i == -1) return -1; + HighsInt orbit = orbitPartition[i]; if (orbit != orbitPartition[orbit]) { do { @@ -172,7 +176,8 @@ HighsInt HighsSymmetries::propagateOrbitopes(HighsDomain& domain) const { } std::shared_ptr -HighsSymmetries::computeStabilizerOrbits(const HighsDomain& localdom) { +HighsSymmetries::computeStabilizerOrbits(const HighsDomain& localdom, + StabilizerOrbitWorkspace& workspace) { const auto& domchgStack = localdom.getDomainChangeStack(); const auto& branchingPos = localdom.getBranchingPositions(); @@ -195,9 +200,11 @@ HighsSymmetries::computeStabilizerOrbits(const HighsDomain& localdom) { } HighsInt permLength = permutationColumns.size(); - orbitPartition.resize(permLength); - std::iota(orbitPartition.begin(), orbitPartition.end(), 0); - orbitSize.assign(permLength, 1); + workspace.orbitPartition.resize(permLength); + std::iota(workspace.orbitPartition.begin(), workspace.orbitPartition.end(), + 0); + workspace.orbitSize.assign(permLength, 1); + workspace.linkCompressionStack.clear(); for (HighsInt i = 0; i < numPerms; ++i) { const HighsInt* perm = permutations.data() + i * permutationColumns.size(); @@ -213,7 +220,8 @@ HighsSymmetries::computeStabilizerOrbits(const HighsDomain& localdom) { if (!permRespectsBranchings) continue; for (HighsInt j = 0; j < permLength; ++j) { - mergeOrbits(permutationColumns[j], perm[j]); + mergeOrbits(permutationColumns[j], perm[j], workspace.orbitPartition, + workspace.orbitSize, workspace.linkCompressionStack); } } @@ -224,8 +232,9 @@ HighsSymmetries::computeStabilizerOrbits(const HighsDomain& localdom) { if (localdom.variableType(permutationColumns[i]) == HighsVarType::kContinuous) continue; - HighsInt orbit = getOrbit(permutationColumns[i]); - if (orbitSize[orbit] == 1) + HighsInt orbit = getOrbit(permutationColumns[i], workspace.orbitPartition, + workspace.linkCompressionStack); + if (workspace.orbitSize[orbit] == 1) stabilizerOrbits.stabilizedCols.push_back(permutationColumns[i]); else if (localdom.isGlobalBinary(permutationColumns[i])) stabilizerOrbits.orbitCols.push_back(permutationColumns[i]); @@ -237,15 +246,20 @@ HighsSymmetries::computeStabilizerOrbits(const HighsDomain& localdom) { pdqsort(stabilizerOrbits.orbitCols.begin(), stabilizerOrbits.orbitCols.end(), [&](HighsInt col1, HighsInt col2) { - return getOrbit(col1) < getOrbit(col2); + return getOrbit(col1, workspace.orbitPartition, + workspace.linkCompressionStack) < + getOrbit(col2, workspace.orbitPartition, + workspace.linkCompressionStack); }); HighsInt numOrbitCols = stabilizerOrbits.orbitCols.size(); stabilizerOrbits.orbitStarts.reserve(numOrbitCols + 1); stabilizerOrbits.orbitStarts.push_back(0); for (HighsInt i = 1; i < numOrbitCols; ++i) { - if (getOrbit(stabilizerOrbits.orbitCols[i]) != - getOrbit(stabilizerOrbits.orbitCols[i - 1])) + if (getOrbit(stabilizerOrbits.orbitCols[i], workspace.orbitPartition, + workspace.linkCompressionStack) != + getOrbit(stabilizerOrbits.orbitCols[i - 1], workspace.orbitPartition, + workspace.linkCompressionStack)) stabilizerOrbits.orbitStarts.push_back(i); } stabilizerOrbits.orbitStarts.push_back(numOrbitCols); diff --git a/highs/presolve/HighsSymmetry.h b/highs/presolve/HighsSymmetry.h index b41685e7610..07f1e7eb23b 100644 --- a/highs/presolve/HighsSymmetry.h +++ b/highs/presolve/HighsSymmetry.h @@ -54,6 +54,12 @@ class HighsMatrixColoring { class HighsDomain; class HighsCliqueTable; struct HighsSymmetries; +struct StabilizerOrbitWorkspace { + std::vector orbitPartition; + std::vector orbitSize; + std::vector linkCompressionStack; +}; + struct StabilizerOrbits { std::vector orbitCols; std::vector orbitStarts; @@ -126,8 +132,18 @@ struct HighsSymmetries { HighsInt numGenerators = 0; void clear(); - void mergeOrbits(HighsInt col1, HighsInt col2); - HighsInt getOrbit(HighsInt col); + void mergeOrbits(HighsInt col1, HighsInt col2, + std::vector& orbitPartition, + std::vector& orbitSize, + std::vector& linkCompressionStack); + void mergeOrbits(HighsInt col1, HighsInt col2) { + mergeOrbits(col1, col2, orbitPartition, orbitSize, linkCompressionStack); + } + HighsInt getOrbit(HighsInt col, std::vector& orbitPartition, + std::vector& linkCompressionStack); + HighsInt getOrbit(HighsInt col) { + return getOrbit(col, orbitPartition, linkCompressionStack); + } HighsInt propagateOrbitopes(HighsDomain& domain) const; @@ -142,7 +158,7 @@ struct HighsSymmetries { } std::shared_ptr computeStabilizerOrbits( - const HighsDomain& localdom); + const HighsDomain& localdom, StabilizerOrbitWorkspace& workspace); }; class HighsSymmetryDetection { diff --git a/highs/simplex/HApp.h b/highs/simplex/HApp.h index fee29de2983..beac9c3f021 100644 --- a/highs/simplex/HApp.h +++ b/highs/simplex/HApp.h @@ -22,6 +22,7 @@ #include "lp_data/HighsLpUtils.h" #include "lp_data/HighsSolution.h" #include "lp_data/HighsSolve.h" +#include "parallel/HighsParallel.h" #include "simplex/HEkk.h" #include "simplex/HSimplex.h" @@ -41,41 +42,21 @@ inline HighsStatus returnFromSolveLpSimplex(HighsLpSolverObject& solver_object, // Copy the simplex iteration count to highs_info_ from ekk_instance solver_object.highs_info_.simplex_iteration_count = ekk_instance.iteration_count_; - // Identify which clock to stop. Can't inspect the basis, as there - // will generally be one after simplex, so have to deduce whether - // there was one before - HighsInt sub_solver_ix = -1; - if (std::signbit(solver_object.sub_solver_call_time_ - .run_time[kSubSolverDuSimplexBasis])) - sub_solver_ix = kSubSolverDuSimplexBasis; - if (std::signbit(solver_object.sub_solver_call_time_ - .run_time[kSubSolverDuSimplexNoBasis])) - sub_solver_ix = kSubSolverDuSimplexNoBasis; - if (std::signbit(solver_object.sub_solver_call_time_ - .run_time[kSubSolverPrSimplexBasis])) - sub_solver_ix = kSubSolverPrSimplexBasis; - if (std::signbit(solver_object.sub_solver_call_time_ - .run_time[kSubSolverPrSimplexNoBasis])) - sub_solver_ix = kSubSolverPrSimplexNoBasis; - // Ensure that one clock has been identified - assert(sub_solver_ix >= 0); - // Check that only one clock was started - if (sub_solver_ix != kSubSolverDuSimplexBasis) - assert(!std::signbit(solver_object.sub_solver_call_time_ - .run_time[kSubSolverDuSimplexBasis])); - if (sub_solver_ix != kSubSolverDuSimplexNoBasis) - assert(!std::signbit(solver_object.sub_solver_call_time_ - .run_time[kSubSolverDuSimplexNoBasis])); - if (sub_solver_ix != kSubSolverPrSimplexBasis) - assert(!std::signbit(solver_object.sub_solver_call_time_ - .run_time[kSubSolverPrSimplexBasis])); - if (sub_solver_ix != kSubSolverPrSimplexNoBasis) - assert(!std::signbit(solver_object.sub_solver_call_time_ - .run_time[kSubSolverPrSimplexNoBasis])); - // Update the call count and run time - solver_object.sub_solver_call_time_.num_call[sub_solver_ix]++; - solver_object.sub_solver_call_time_.run_time[sub_solver_ix] += - solver_object.timer_.read(); + // Stop whichever clock was running + if (solver_object.profiling_->sub_solver_) { + HighsInt profiling_clock = -1; + HighsProfilingRecord* thread_record = + solver_object.profiling_->getHighsProfilingRecord(); + if (std::signbit(thread_record->start_time[kSubSolverDuSimplexBasis])) + profiling_clock = kSubSolverDuSimplexBasis; + if (std::signbit(thread_record->start_time[kSubSolverDuSimplexNoBasis])) + profiling_clock = kSubSolverDuSimplexNoBasis; + if (std::signbit(thread_record->start_time[kSubSolverPrSimplexBasis])) + profiling_clock = kSubSolverPrSimplexBasis; + if (std::signbit(thread_record->start_time[kSubSolverPrSimplexNoBasis])) + profiling_clock = kSubSolverPrSimplexNoBasis; + solver_object.profiling_->stop(profiling_clock); + } // Ensure that the incumbent LP is neither moved, nor scaled assert(!incumbent_lp.is_moved_); assert(!incumbent_lp.is_scaled_); @@ -136,24 +117,26 @@ inline HighsStatus solveLpSimplex(HighsLpSolverObject& solver_object) { assert(retained_ekk_data_ok); return_status = HighsStatus::kError; } - HighsInt sub_solver_ix = -1; - if (options.simplex_strategy == kSimplexStrategyPrimal) { - if (basis.valid) { - sub_solver_ix = kSubSolverPrSimplexBasis; - } else { - sub_solver_ix = kSubSolverPrSimplexNoBasis; - } - } else { - if (basis.valid) { - sub_solver_ix = kSubSolverDuSimplexBasis; + if (solver_object.profiling_) { + HighsInt profiling_clock = -1; + if (options.simplex_strategy == kSimplexStrategyPrimal) { + if (basis.valid) { + profiling_clock = kSubSolverPrSimplexBasis; + } else { + profiling_clock = kSubSolverPrSimplexNoBasis; + } } else { - sub_solver_ix = kSubSolverDuSimplexNoBasis; + if (basis.valid) { + profiling_clock = kSubSolverDuSimplexBasis; + } else { + profiling_clock = kSubSolverDuSimplexNoBasis; + } } + assert(profiling_clock >= 0); + // if (solver_object.profiling_->isSubMip()) { printf("solveLpSimplex: + // sub-MIP on thread %d\n", int(solver_object.profiling_->myThread())); } + solver_object.profiling_->start(profiling_clock); } - assert(sub_solver_ix >= 0); - assert(solver_object.sub_solver_call_time_.run_time.size() > 0); - solver_object.sub_solver_call_time_.run_time[sub_solver_ix] = - -solver_object.timer_.read(); // Copy the simplex iteration count from highs_info_ to ekk_instance, just for // convenience ekk_instance.iteration_count_ = highs_info.simplex_iteration_count; diff --git a/highs/util/HighsHash.h b/highs/util/HighsHash.h index b879ebed906..f74034d823e 100644 --- a/highs/util/HighsHash.h +++ b/highs/util/HighsHash.h @@ -990,6 +990,21 @@ class HighsHashTable { makeEmptyTable(initCapacity); } + HighsHashTable(const HighsHashTable& hashTable) + : tableSizeMask(hashTable.tableSizeMask), + numHashShift(hashTable.numHashShift), + numElements(hashTable.numElements) { + u64 capacity = tableSizeMask + 1; + metadata = decltype(metadata)(new u8[capacity]); + entries = + decltype(entries)((Entry*)::operator new(sizeof(Entry) * capacity)); + + std::copy(hashTable.metadata.get(), hashTable.metadata.get() + capacity, + metadata.get()); + std::copy(hashTable.entries.get(), hashTable.entries.get() + capacity, + entries.get()); + } + iterator end() { u64 capacity = tableSizeMask + 1; return iterator{metadata.get() + capacity, metadata.get() + capacity,