From e4003b5d4205a2132697039a3f2c18a80700ab94 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Tue, 15 Jul 2025 12:23:26 +0100 Subject: [PATCH 01/58] Created structs MipRaceIncumbent and MipRaceRecord --- highs/lp_data/HStruct.h | 17 ++++++++++++ highs/mip/HighsMipSolverData.cpp | 46 ++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/highs/lp_data/HStruct.h b/highs/lp_data/HStruct.h index 3b54629832c..783f6ae2389 100644 --- a/highs/lp_data/HStruct.h +++ b/highs/lp_data/HStruct.h @@ -61,6 +61,23 @@ struct HotStart { std::vector nonbasicMove; }; +struct MipRaceIncumbent { + HighsInt start_write_incumbent = -1; + HighsInt finish_write_incumbent = -1; + double best_incumbent_objective = -kHighsInf; + std::vector best_incumbent_solution; + void clear(); + void initialise(const HighsInt num_col); + void write(const double objective, const std::vector& solution); + bool readOk(double& objective, std::vector& solution) const; +}; + +struct MipRaceRecord { + std::vector record; + void clear(); + void initialise(const HighsInt num_race_instance, const HighsInt num_col); +}; + struct HighsBasis { // Logical flags for a HiGHS basis: // diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index e5c5b43e9d1..6e2eac44892 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -2785,3 +2785,49 @@ void HighsMipSolverData::updatePrimalDualIntegral(const double from_lower_bound, } void HighsPrimaDualIntegral::initialise() { this->value = -kHighsInf; } + +void MipRaceIncumbent::clear() { + this->start_write_incumbent = -1; + this->finish_write_incumbent = -1; + this->best_incumbent_objective = -kHighsInf; + this->best_incumbent_solution.clear(); +} + +void MipRaceIncumbent::initialise(const HighsInt num_col) { + this->clear(); + this->best_incumbent_solution.resize(num_col); +} + +void MipRaceIncumbent::write(const double objective, + const std::vector& solution) { + assert(this->best_incumbent_solution.size() == solution.size()); + this->start_write_incumbent++; + this->best_incumbent_objective = objective; + this->best_incumbent_solution = solution; + this->finish_write_incumbent++; + assert(this->start_write_incumbent == this->finish_write_incumbent); +} + +bool MipRaceIncumbent::readOk(double& objective, + std::vector& solution) const { + const HighsInt start_write_incumbent = this->start_write_incumbent; + assert(this->finish_write_incumbent <= start_write_incumbent); + // If a write call has not completed, return failure + if (this->finish_write_incumbent < start_write_incumbent) return false; + // finish_write_incumbent = start_write_incumbent so start reading + objective = this->best_incumbent_objective; + solution = this->best_incumbent_solution; + // Read is OK if no new write has started + return this->start_write_incumbent == start_write_incumbent; +} + +void MipRaceRecord::clear() { this->record.clear(); } + +void MipRaceRecord::initialise(const HighsInt num_race_instance, + const HighsInt num_col) { + this->clear(); + MipRaceIncumbent mip_race_incumbent; + mip_race_incumbent.initialise(num_col); + for (HighsInt instance = 0; instance < num_race_instance; instance++) + this->record.push_back(mip_race_incumbent); +} From a66b6418120be2824b5bbf15174a26004f70f7f7 Mon Sep 17 00:00:00 2001 From: Julian Hall Date: Tue, 15 Jul 2025 18:12:36 +0100 Subject: [PATCH 02/58] Writing incumbent solutions to shared memory --- highs/lp_data/HStruct.h | 2 ++ highs/lp_data/Highs.cpp | 10 +++++++++ highs/mip/HighsMipSolver.cpp | 6 ++++++ highs/mip/HighsMipSolver.h | 4 ++++ highs/mip/HighsMipSolverData.cpp | 37 +++++++++++++++++++++++++++++++- 5 files changed, 58 insertions(+), 1 deletion(-) diff --git a/highs/lp_data/HStruct.h b/highs/lp_data/HStruct.h index 783f6ae2389..01183b4bd75 100644 --- a/highs/lp_data/HStruct.h +++ b/highs/lp_data/HStruct.h @@ -73,9 +73,11 @@ struct MipRaceIncumbent { }; struct MipRaceRecord { + std::vector terminate; std::vector record; void clear(); void initialise(const HighsInt num_race_instance, const HighsInt num_col); + void report() const; }; struct HighsBasis { diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index bbc9c47105c..96924c590f4 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -4027,7 +4027,17 @@ HighsStatus Highs::callSolveMip() { options_.primal_feasibility_tolerance); } HighsLp& lp = has_semi_variables ? use_lp : model_.lp_; + // + // Set up the shared memory for the MIP solver race + const HighsInt num_num_race_instance = 2; + MipRaceRecord mip_race_record; + mip_race_record.initialise(num_num_race_instance, lp.num_col_); + mip_race_record.report(); HighsMipSolver solver(callback_, options_, lp, solution_); + // Initialise the pointer to the shared memory space + solver.mip_race_record_ = &mip_race_record; + solver.my_mip_race_instance_ = 0; + // Run the MIP solver! solver.run(); options_.log_dev_level = log_dev_level; // Set the return_status, model status and, for completeness, scaled diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index ff08e5da09e..7c4856b3974 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -972,3 +972,9 @@ bool HighsMipSolver::solutionFeasible(const HighsLp* lp, row_violation <= mip_feasibility_tolerance; return feasible; } + +void HighsMipSolver::makeMipRaceRecord(const double objective, + const std::vector& solution) { + this->mip_race_record_->record[this->my_mip_race_instance_].write(objective, solution); + this->mip_race_record_->report(); +} diff --git a/highs/mip/HighsMipSolver.h b/highs/mip/HighsMipSolver.h index d2d0c33b823..58775817457 100644 --- a/highs/mip/HighsMipSolver.h +++ b/highs/mip/HighsMipSolver.h @@ -55,6 +55,9 @@ class HighsMipSolver { HighsMipAnalysis analysis_; + HighsInt my_mip_race_instance_; + MipRaceRecord* mip_race_record_; + void run(); HighsInt numCol() const { return model_->num_col_; } @@ -107,6 +110,7 @@ class HighsMipSolver { const std::vector* pass_row_value, double& bound_violation, double& row_violation, double& integrality_violation, HighsCDouble& obj) const; + void makeMipRaceRecord(const double objective, const std::vector& solution); }; #endif diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index 6e2eac44892..c2ee5618008 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -736,6 +736,7 @@ void HighsMipSolverData::runSetup() { upper_bound -= mipsolver.model_->offset_; if (mipsolver.solution_objective_ != kHighsInf) { + // Assigning new incumbent incumbent = postSolveStack.getReducedPrimalSolution(mipsolver.solution_); // return the objective value in the transformed space double solobj = @@ -764,6 +765,10 @@ void HighsMipSolverData::runSetup() { upper_bound); double new_upper_limit = computeNewUpperLimit(solobj, 0.0, 0.0); + // Possibly write the improving solution to the shared memory space + if (!mipsolver.submip && mipsolver.mip_race_record_) + mipsolver.makeMipRaceRecord(solobj, incumbent); + saveReportMipSolution(new_upper_limit); if (new_upper_limit < upper_limit) { upper_limit = new_upper_limit; @@ -1399,6 +1404,7 @@ bool HighsMipSolverData::addIncumbent(const std::vector& sol, updatePrimalDualIntegral(lower_bound, lower_bound, prev_upper_bound, upper_bound); + // Assigning new incumbent incumbent = sol; double new_upper_limit = computeNewUpperLimit(solobj, 0.0, 0.0); @@ -1406,6 +1412,9 @@ bool HighsMipSolverData::addIncumbent(const std::vector& sol, saveReportMipSolution(new_upper_limit); if (new_upper_limit < upper_limit) { ++numImprovingSols; + // Possibly write the improving solution to the shared memory space + if (!mipsolver.submip && mipsolver.mip_race_record_) + mipsolver.makeMipRaceRecord(solobj, incumbent); upper_limit = new_upper_limit; optimality_limit = computeNewUpperLimit(solobj, mipsolver.options_mip_->mip_abs_gap, @@ -1438,6 +1447,7 @@ bool HighsMipSolverData::addIncumbent(const std::vector& sol, printDisplayLine(solution_source); } } else if (incumbent.empty()) + // Assigning new incumbent incumbent = sol; return true; @@ -2821,13 +2831,38 @@ bool MipRaceIncumbent::readOk(double& objective, return this->start_write_incumbent == start_write_incumbent; } -void MipRaceRecord::clear() { this->record.clear(); } +void MipRaceRecord::clear() { + this->terminate.clear(); + this->record.clear(); +} void MipRaceRecord::initialise(const HighsInt num_race_instance, const HighsInt num_col) { this->clear(); + this->terminate.assign(num_race_instance, false); MipRaceIncumbent mip_race_incumbent; mip_race_incumbent.initialise(num_col); for (HighsInt instance = 0; instance < num_race_instance; instance++) this->record.push_back(mip_race_incumbent); } + +void MipRaceRecord::report() const { + HighsInt num_race_instance = this->terminate.size(); + printf("\nMipRaceRecord:"); + for (HighsInt instance = 0; instance < num_race_instance; instance++) + printf(" %11d", int(instance)); + printf("\nTerminate: "); + for (HighsInt instance = 0; instance < num_race_instance; instance++) + printf(" %11s", this->terminate[instance] ? "T" : "F"); + printf("\nStartWrite: "); + for (HighsInt instance = 0; instance < num_race_instance; instance++) + printf(" %11d", this->record[instance].start_write_incumbent); + printf("\nObjective: "); + for (HighsInt instance = 0; instance < num_race_instance; instance++) + printf(" %11.4g", this->record[instance].best_incumbent_objective); + printf("\nFinishWrite: "); + for (HighsInt instance = 0; instance < num_race_instance; instance++) + printf(" %11d", this->record[instance].finish_write_incumbent); + printf("\n\n"); +} + From 3f115b6f8d3371f0a98877f51f266a05952f6a32 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Tue, 15 Jul 2025 22:56:07 +0100 Subject: [PATCH 03/58] Introduced option for mip_race_concurrency, struct MipRace, last_incumbent_read and refactored --- highs/lp_data/HStruct.h | 31 +++++++-- highs/lp_data/Highs.cpp | 15 ++-- highs/lp_data/HighsOptions.h | 7 ++ highs/mip/HighsMipSolver.cpp | 6 -- highs/mip/HighsMipSolver.h | 4 +- highs/mip/HighsMipSolverData.cpp | 114 ++++++++++++++++++++----------- 6 files changed, 117 insertions(+), 60 deletions(-) diff --git a/highs/lp_data/HStruct.h b/highs/lp_data/HStruct.h index 01183b4bd75..46be2f86fdb 100644 --- a/highs/lp_data/HStruct.h +++ b/highs/lp_data/HStruct.h @@ -64,19 +64,38 @@ struct HotStart { struct MipRaceIncumbent { HighsInt start_write_incumbent = -1; HighsInt finish_write_incumbent = -1; - double best_incumbent_objective = -kHighsInf; - std::vector best_incumbent_solution; + double objective = -kHighsInf; + std::vector solution; void clear(); void initialise(const HighsInt num_col); - void write(const double objective, const std::vector& solution); - bool readOk(double& objective, std::vector& solution) const; + void update(const double objective, + const std::vector& solution); + bool readOk(double& objective_, + std::vector& solution_) const; }; struct MipRaceRecord { std::vector terminate; - std::vector record; + std::vector incumbent; void clear(); - void initialise(const HighsInt num_race_instance, const HighsInt num_col); + void initialise(const HighsInt mip_race_concurrency, + const HighsInt num_col); + void update(const HighsInt instance, + const double objective, + const std::vector& solution); + void report() const; +}; + +struct MipRace { + HighsInt my_instance; + MipRaceRecord* record; + std::vector last_incumbent_read; + void clear(); + void initialise(const HighsInt mip_race_concurrency, + const HighsInt my_instance_, + MipRaceRecord* record_); + void update(const double objective, + const std::vector& solution); void report() const; }; diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index 96924c590f4..3ae3dcbe7ca 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -4027,16 +4027,17 @@ HighsStatus Highs::callSolveMip() { options_.primal_feasibility_tolerance); } HighsLp& lp = has_semi_variables ? use_lp : model_.lp_; - // // Set up the shared memory for the MIP solver race - const HighsInt num_num_race_instance = 2; + const HighsInt mip_race_concurrency = this->options_.mip_race_concurrency; + const bool mip_race = mip_race_concurrency > 1; MipRaceRecord mip_race_record; - mip_race_record.initialise(num_num_race_instance, lp.num_col_); - mip_race_record.report(); + if (mip_race) mip_race_record.initialise(mip_race_concurrency, lp.num_col_); HighsMipSolver solver(callback_, options_, lp, solution_); - // Initialise the pointer to the shared memory space - solver.mip_race_record_ = &mip_race_record; - solver.my_mip_race_instance_ = 0; + if (mip_race) { + // Initialise the MIP race data for this instance + const HighsInt my_mip_race_instance = 0; + solver.mip_race_.initialise(mip_race_concurrency, my_mip_race_instance, &mip_race_record); + } // Run the MIP solver! solver.run(); options_.log_dev_level = log_dev_level; diff --git a/highs/lp_data/HighsOptions.h b/highs/lp_data/HighsOptions.h index e4d228b6edd..40e083a15d8 100644 --- a/highs/lp_data/HighsOptions.h +++ b/highs/lp_data/HighsOptions.h @@ -424,6 +424,7 @@ struct HighsOptionsStruct { // Options for MIP solver bool mip_detect_symmetry; bool mip_allow_restart; + HighsInt mip_race_concurrency; HighsInt mip_max_nodes; HighsInt mip_max_stall_nodes; HighsInt mip_max_start_nodes; @@ -575,6 +576,7 @@ struct HighsOptionsStruct { icrash_breakpoints(false), mip_detect_symmetry(false), mip_allow_restart(false), + mip_race_concurrency(0), mip_max_nodes(0), mip_max_stall_nodes(0), mip_max_start_nodes(0), @@ -1017,6 +1019,11 @@ class HighsOptions : public HighsOptionsStruct { advanced, &mip_allow_restart, true); records.push_back(record_bool); + record_int = new OptionRecordInt("mip_race_concurrency", + "Concurrency for non-deterministic MIP race", advanced, + &mip_race_concurrency, 0, 2, kHighsIInf); + records.push_back(record_int); + record_int = new OptionRecordInt("mip_max_nodes", "MIP solver max number of nodes", advanced, &mip_max_nodes, 0, kHighsIInf, kHighsIInf); diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index 7c4856b3974..ff08e5da09e 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -972,9 +972,3 @@ bool HighsMipSolver::solutionFeasible(const HighsLp* lp, row_violation <= mip_feasibility_tolerance; return feasible; } - -void HighsMipSolver::makeMipRaceRecord(const double objective, - const std::vector& solution) { - this->mip_race_record_->record[this->my_mip_race_instance_].write(objective, solution); - this->mip_race_record_->report(); -} diff --git a/highs/mip/HighsMipSolver.h b/highs/mip/HighsMipSolver.h index 58775817457..4288719be12 100644 --- a/highs/mip/HighsMipSolver.h +++ b/highs/mip/HighsMipSolver.h @@ -55,8 +55,7 @@ class HighsMipSolver { HighsMipAnalysis analysis_; - HighsInt my_mip_race_instance_; - MipRaceRecord* mip_race_record_; + MipRace mip_race_; void run(); @@ -110,7 +109,6 @@ class HighsMipSolver { const std::vector* pass_row_value, double& bound_violation, double& row_violation, double& integrality_violation, HighsCDouble& obj) const; - void makeMipRaceRecord(const double objective, const std::vector& solution); }; #endif diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index c2ee5618008..cb9e49b9883 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -766,8 +766,8 @@ void HighsMipSolverData::runSetup() { double new_upper_limit = computeNewUpperLimit(solobj, 0.0, 0.0); // Possibly write the improving solution to the shared memory space - if (!mipsolver.submip && mipsolver.mip_race_record_) - mipsolver.makeMipRaceRecord(solobj, incumbent); + if (!mipsolver.submip && mipsolver.mip_race_.record) + mipsolver.mip_race_.update(solobj, incumbent); saveReportMipSolution(new_upper_limit); if (new_upper_limit < upper_limit) { @@ -1413,8 +1413,8 @@ bool HighsMipSolverData::addIncumbent(const std::vector& sol, if (new_upper_limit < upper_limit) { ++numImprovingSols; // Possibly write the improving solution to the shared memory space - if (!mipsolver.submip && mipsolver.mip_race_record_) - mipsolver.makeMipRaceRecord(solobj, incumbent); + if (!mipsolver.submip && mipsolver.mip_race_.record) + mipsolver.mip_race_.update(solobj, incumbent); upper_limit = new_upper_limit; optimality_limit = computeNewUpperLimit(solobj, mipsolver.options_mip_->mip_abs_gap, @@ -2799,70 +2799,108 @@ void HighsPrimaDualIntegral::initialise() { this->value = -kHighsInf; } void MipRaceIncumbent::clear() { this->start_write_incumbent = -1; this->finish_write_incumbent = -1; - this->best_incumbent_objective = -kHighsInf; - this->best_incumbent_solution.clear(); + this->objective = -kHighsInf; + this->solution.clear(); } void MipRaceIncumbent::initialise(const HighsInt num_col) { this->clear(); - this->best_incumbent_solution.resize(num_col); + this->solution.resize(num_col); } -void MipRaceIncumbent::write(const double objective, - const std::vector& solution) { - assert(this->best_incumbent_solution.size() == solution.size()); +void MipRaceIncumbent::update(const double objective_, + const std::vector& solution_) { + assert(this->solution.size() == solution_.size()); this->start_write_incumbent++; - this->best_incumbent_objective = objective; - this->best_incumbent_solution = solution; + this->objective = objective_; + this->solution = solution_; this->finish_write_incumbent++; assert(this->start_write_incumbent == this->finish_write_incumbent); } -bool MipRaceIncumbent::readOk(double& objective, - std::vector& solution) const { +bool MipRaceIncumbent::readOk(double& objective_, + std::vector& solution_) const { const HighsInt start_write_incumbent = this->start_write_incumbent; assert(this->finish_write_incumbent <= start_write_incumbent); // If a write call has not completed, return failure if (this->finish_write_incumbent < start_write_incumbent) return false; // finish_write_incumbent = start_write_incumbent so start reading - objective = this->best_incumbent_objective; - solution = this->best_incumbent_solution; + objective_ = this->objective; + solution_ = this->solution; // Read is OK if no new write has started return this->start_write_incumbent == start_write_incumbent; } void MipRaceRecord::clear() { this->terminate.clear(); - this->record.clear(); + this->incumbent.clear(); } -void MipRaceRecord::initialise(const HighsInt num_race_instance, +void MipRaceRecord::initialise(const HighsInt mip_race_concurrency, const HighsInt num_col) { this->clear(); - this->terminate.assign(num_race_instance, false); - MipRaceIncumbent mip_race_incumbent; - mip_race_incumbent.initialise(num_col); - for (HighsInt instance = 0; instance < num_race_instance; instance++) - this->record.push_back(mip_race_incumbent); + this->terminate.assign(mip_race_concurrency, false); + MipRaceIncumbent incumbent_; + incumbent_.initialise(num_col); + for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) + this->incumbent.push_back(incumbent_); +} + +void MipRaceRecord::update(const HighsInt instance, + const double objective, + const std::vector& solution) { + this->incumbent[instance].update(objective, solution); } void MipRaceRecord::report() const { - HighsInt num_race_instance = this->terminate.size(); - printf("\nMipRaceRecord:"); - for (HighsInt instance = 0; instance < num_race_instance; instance++) + HighsInt mip_race_concurrency = this->terminate.size(); + printf("\nMipRaceRecord: "); + for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) printf(" %11d", int(instance)); - printf("\nTerminate: "); - for (HighsInt instance = 0; instance < num_race_instance; instance++) + printf("\nTerminate: "); + for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) printf(" %11s", this->terminate[instance] ? "T" : "F"); - printf("\nStartWrite: "); - for (HighsInt instance = 0; instance < num_race_instance; instance++) - printf(" %11d", this->record[instance].start_write_incumbent); - printf("\nObjective: "); - for (HighsInt instance = 0; instance < num_race_instance; instance++) - printf(" %11.4g", this->record[instance].best_incumbent_objective); - printf("\nFinishWrite: "); - for (HighsInt instance = 0; instance < num_race_instance; instance++) - printf(" %11d", this->record[instance].finish_write_incumbent); - printf("\n\n"); + printf("\nStartWrite: "); + for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) + printf(" %11d", this->incumbent[instance].start_write_incumbent); + printf("\nObjective: "); + for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) + printf(" %11.4g", this->incumbent[instance].objective); + printf("\nFinishWrite: "); + for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) + printf(" %11d", this->incumbent[instance].finish_write_incumbent); + printf("\n"); +} + +void MipRace::clear() { + this->my_instance = -1; + this->record = nullptr; + this->last_incumbent_read.clear(); +} + +void MipRace::initialise(const HighsInt mip_race_concurrency, + const HighsInt my_instance_, + MipRaceRecord* record_) { + this->clear(); + assert(mip_race_concurrency > 0); + this->my_instance = my_instance_; + this->record = record_; + this->last_incumbent_read.assign(mip_race_concurrency, -1); +} + +void MipRace::update(const double objective, + const std::vector& solution) { + assert(this->record); + this->record->update(this->my_instance, objective, solution); + this->report(); } +void MipRace::report() const { + assert(this->record); + this->record->report(); + HighsInt mip_race_concurrency = this->last_incumbent_read.size(); + printf("LastIncumbentRead: "); + for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) + printf(" %11d", this->last_incumbent_read[instance]); + printf("\n\n"); +} From d5f9db224176a883a90ca2c6a35e2981c415647a Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 17 Jul 2025 10:52:09 +0100 Subject: [PATCH 04/58] Added newSolution, terminate and terminated to MipRace and HighsMipSolverData --- highs/lp_data/HStruct.h | 4 +++ highs/mip/HighsMipSolverData.cpp | 46 ++++++++++++++++++++++++++++---- highs/mip/HighsMipSolverData.h | 5 ++++ 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/highs/lp_data/HStruct.h b/highs/lp_data/HStruct.h index 46be2f86fdb..f87e143cdbf 100644 --- a/highs/lp_data/HStruct.h +++ b/highs/lp_data/HStruct.h @@ -96,6 +96,10 @@ struct MipRace { MipRaceRecord* record_); void update(const double objective, const std::vector& solution); + bool newSolution(double objective, + std::vector& solution) const; + void terminate(); + bool terminated() const; void report() const; }; diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index cb9e49b9883..addb61ac9a9 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -765,9 +765,9 @@ void HighsMipSolverData::runSetup() { upper_bound); double new_upper_limit = computeNewUpperLimit(solobj, 0.0, 0.0); + // Possibly write the improving solution to the shared memory space - if (!mipsolver.submip && mipsolver.mip_race_.record) - mipsolver.mip_race_.update(solobj, incumbent); + if (!mipsolver.submip) this->mipRaceUpdate(); saveReportMipSolution(new_upper_limit); if (new_upper_limit < upper_limit) { @@ -1412,9 +1412,6 @@ bool HighsMipSolverData::addIncumbent(const std::vector& sol, saveReportMipSolution(new_upper_limit); if (new_upper_limit < upper_limit) { ++numImprovingSols; - // Possibly write the improving solution to the shared memory space - if (!mipsolver.submip && mipsolver.mip_race_.record) - mipsolver.mip_race_.update(solobj, incumbent); upper_limit = new_upper_limit; optimality_limit = computeNewUpperLimit(solobj, mipsolver.options_mip_->mip_abs_gap, @@ -2546,6 +2543,9 @@ void HighsMipSolverData::saveReportMipSolution(const double new_upper_limit) { if (mipsolver.submip) return; if (non_improving) return; + // Possibly write the improving solution to the shared memory space + this->mipRaceUpdate(); + if (mipsolver.callback_->user_callback) { if (mipsolver.callback_->active[kCallbackMipImprovingSolution]) { mipsolver.callback_->clearHighsCallbackOutput(); @@ -2660,6 +2660,28 @@ void HighsMipSolverData::callbackUserSolution( } } +void HighsMipSolverData::mipRaceUpdate() { + if (!mipsolver.mip_race_.record) return; + assert(!mipsolver.submip); + mipsolver.mip_race_.update(mipsolver.solution_objective_, mipsolver.solution_); +} + +bool HighsMipSolverData::mipRaceNewSolution(double& objective_value, std::vector& solution) { + return false; +} + +void HighsMipSolverData::mipRaceTerminate() { + if (!mipsolver.mip_race_.record) return; + assert(!mipsolver.submip); + mipsolver.mip_race_.terminate(); +} + +bool HighsMipSolverData::mipRaceTerminated() const { + if (!mipsolver.mip_race_.record) return; + assert(!mipsolver.submip); + return mipsolver.mip_race_.terminated(); +} + static double possInfRelDiff(const double v0, const double v1, const double den) { double rel_diff; @@ -2895,6 +2917,19 @@ void MipRace::update(const double objective, this->report(); } +bool MipRace::newSolution(double objective, + std::vector& solution) const { + assert(this->record); + return false; +} + +void MipRace::terminate() { +} + +bool MipRace::terminated() const { + return false; +} + void MipRace::report() const { assert(this->record); this->record->report(); @@ -2904,3 +2939,4 @@ void MipRace::report() const { printf(" %11d", this->last_incumbent_read[instance]); printf("\n\n"); } + diff --git a/highs/mip/HighsMipSolverData.h b/highs/mip/HighsMipSolverData.h index da05090374b..bd1b022ef11 100644 --- a/highs/mip/HighsMipSolverData.h +++ b/highs/mip/HighsMipSolverData.h @@ -299,6 +299,11 @@ struct HighsMipSolverData { void callbackUserSolution( const double mipsolver_objective_value, const userMipSolutionCallbackOrigin user_solution_callback_origin); + + void mipRaceUpdate(); + bool mipRaceNewSolution(double& objective_value, std::vector& solution); + void mipRaceTerminate(); + bool mipRaceTerminated() const; }; #endif From 865628c75b1a1d1a7cb0a2826724eaba99faab5c Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 17 Jul 2025 13:56:16 +0100 Subject: [PATCH 05/58] Now to extend callbackUserSolution to generic extenalsolution method --- highs/lp_data/HStruct.h | 2 ++ highs/mip/HighsMipSolver.cpp | 6 ++++++ highs/mip/HighsMipSolverData.cpp | 35 ++++++++++++++++++++++++++++---- highs/mip/HighsMipSolverData.h | 2 ++ 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/highs/lp_data/HStruct.h b/highs/lp_data/HStruct.h index f87e143cdbf..64dffd18e99 100644 --- a/highs/lp_data/HStruct.h +++ b/highs/lp_data/HStruct.h @@ -80,6 +80,7 @@ struct MipRaceRecord { void clear(); void initialise(const HighsInt mip_race_concurrency, const HighsInt num_col); + HighsInt concurrency() const; void update(const HighsInt instance, const double objective, const std::vector& solution); @@ -94,6 +95,7 @@ struct MipRace { void initialise(const HighsInt mip_race_concurrency, const HighsInt my_instance_, MipRaceRecord* record_); + HighsInt concurrency() const; void update(const double objective, const std::vector& solution); bool newSolution(double objective, diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index ff08e5da09e..133e6f74a04 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -690,6 +690,12 @@ void HighsMipSolver::run() { } void HighsMipSolver::cleanupSolve() { + // Terminate any MIP race + if (!submip) { + mipdata_->mipRaceTerminate(); + mipdata_->mipRaceReport(); + } + // Force a final logging line mipdata_->printDisplayLine(kSolutionSourceCleanup); // Stop the solve clock - which won't be running if presolve diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index addb61ac9a9..086cd3c1ce7 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -2414,6 +2414,9 @@ void HighsMipSolverData::evaluateRootNode() { bool HighsMipSolverData::checkLimits(int64_t nodeOffset) const { const HighsOptions& options = *mipsolver.options_mip_; + // Possible termination of MIP race + if (!mipsolver.submip && this->mipRaceTerminated()) return true; + // Possible user interrupt if (!mipsolver.submip && mipsolver.callback_->user_callback) { mipsolver.callback_->clearHighsCallbackOutput(); @@ -2660,6 +2663,12 @@ void HighsMipSolverData::callbackUserSolution( } } +HighsInt HighsMipSolverData::mipRaceConcurrency() const { + if (!mipsolver.mip_race_.record) return; + assert(!mipsolver.submip); + return mipsolver.mip_race_.concurrency(); +} + void HighsMipSolverData::mipRaceUpdate() { if (!mipsolver.mip_race_.record) return; assert(!mipsolver.submip); @@ -2667,6 +2676,8 @@ void HighsMipSolverData::mipRaceUpdate() { } bool HighsMipSolverData::mipRaceNewSolution(double& objective_value, std::vector& solution) { + if (!mipsolver.mip_race_.record) return false; + assert(!mipsolver.submip); return false; } @@ -2677,11 +2688,16 @@ void HighsMipSolverData::mipRaceTerminate() { } bool HighsMipSolverData::mipRaceTerminated() const { - if (!mipsolver.mip_race_.record) return; + if (!mipsolver.mip_race_.record) return false; assert(!mipsolver.submip); return mipsolver.mip_race_.terminated(); } +void HighsMipSolverData::mipRaceReport() const { + if (!mipsolver.mip_race_.record) return; + assert(!mipsolver.submip); + mipsolver.mip_race_.report(); +} static double possInfRelDiff(const double v0, const double v1, const double den) { double rel_diff; @@ -2868,6 +2884,10 @@ void MipRaceRecord::initialise(const HighsInt mip_race_concurrency, this->incumbent.push_back(incumbent_); } +HighsInt MipRaceRecord::concurrency() const { + return static_cast(this->incumbent.size()); +} + void MipRaceRecord::update(const HighsInt instance, const double objective, const std::vector& solution) { @@ -2875,7 +2895,7 @@ void MipRaceRecord::update(const HighsInt instance, } void MipRaceRecord::report() const { - HighsInt mip_race_concurrency = this->terminate.size(); + HighsInt mip_race_concurrency = this->concurrency(); printf("\nMipRaceRecord: "); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) printf(" %11d", int(instance)); @@ -2910,6 +2930,11 @@ void MipRace::initialise(const HighsInt mip_race_concurrency, this->last_incumbent_read.assign(mip_race_concurrency, -1); } +HighsInt MipRace::concurrency() const { + assert(this->record); + return static_cast(this->last_incumbent_read.size()); +} + void MipRace::update(const double objective, const std::vector& solution) { assert(this->record); @@ -2924,18 +2949,20 @@ bool MipRace::newSolution(double objective, } void MipRace::terminate() { + assert(this->record); + this->record->terminate.assign(this->concurrency(), true); } bool MipRace::terminated() const { + assert(this->record); return false; } void MipRace::report() const { assert(this->record); this->record->report(); - HighsInt mip_race_concurrency = this->last_incumbent_read.size(); printf("LastIncumbentRead: "); - for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) + for (HighsInt instance = 0; instance < this->concurrency(); instance++) printf(" %11d", this->last_incumbent_read[instance]); printf("\n\n"); } diff --git a/highs/mip/HighsMipSolverData.h b/highs/mip/HighsMipSolverData.h index bd1b022ef11..d698b3d613a 100644 --- a/highs/mip/HighsMipSolverData.h +++ b/highs/mip/HighsMipSolverData.h @@ -300,10 +300,12 @@ struct HighsMipSolverData { const double mipsolver_objective_value, const userMipSolutionCallbackOrigin user_solution_callback_origin); + HighsInt mipRaceConcurrency() const; void mipRaceUpdate(); bool mipRaceNewSolution(double& objective_value, std::vector& solution); void mipRaceTerminate(); bool mipRaceTerminated() const; + void mipRaceReport() const; }; #endif From ae572dc330ccdec838eb69105a1e6d64c1ba78a6 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 17 Jul 2025 14:03:21 +0100 Subject: [PATCH 06/58] Added mip-race unit test and restored default value of mip_race_concurrency to 0 --- check/TestMipSolver.cpp | 15 +++++++++++++++ highs/lp_data/HighsOptions.h | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index a65508fae58..c581d7f18cc 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -999,3 +999,18 @@ TEST_CASE("issue-2432", "[highs_test_mip_solver]") { "found\n"); solve(highs, kHighsOffString, require_model_status, optimal_objective); } + +TEST_CASE("mip-race", "[highs_test_mip_solver]") { + const std::string model = "flugpl"; + const std::string model_file = + std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; + Highs h; + // h.setOptionValue("output_flag", dev_run); + h.setOptionValue("mip_race_concurrency", 2); + REQUIRE(h.readModel(model_file) == HighsStatus::kOk); + REQUIRE(h.run() == HighsStatus::kOk); + + + +} + diff --git a/highs/lp_data/HighsOptions.h b/highs/lp_data/HighsOptions.h index 40e083a15d8..a23eea41443 100644 --- a/highs/lp_data/HighsOptions.h +++ b/highs/lp_data/HighsOptions.h @@ -1021,7 +1021,7 @@ class HighsOptions : public HighsOptionsStruct { record_int = new OptionRecordInt("mip_race_concurrency", "Concurrency for non-deterministic MIP race", advanced, - &mip_race_concurrency, 0, 2, kHighsIInf); + &mip_race_concurrency, 0, 0, kHighsIInf); records.push_back(record_int); record_int = new OptionRecordInt("mip_max_nodes", From 9c750ec5930680e2ec94f5b91406ad75e2b97fd2 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 17 Jul 2025 14:32:43 +0100 Subject: [PATCH 07/58] Now to remove callback-related logic from call to queryExternalSolution --- check/TestCallbacks.cpp | 24 +++---- highs/lp_data/HighsCallback.cpp | 10 +-- highs/lp_data/HighsCallback.h | 18 ++--- highs/lp_data/HighsCallbackStruct.h | 2 +- highs/mip/HighsMipSolver.cpp | 8 +-- highs/mip/HighsMipSolverData.cpp | 101 +++++++++++++++------------- highs/mip/HighsMipSolverData.h | 4 +- 7 files changed, 87 insertions(+), 80 deletions(-) diff --git a/check/TestCallbacks.cpp b/check/TestCallbacks.cpp index 0d4364c3a59..a9737d7f9cd 100644 --- a/check/TestCallbacks.cpp +++ b/check/TestCallbacks.cpp @@ -36,7 +36,7 @@ struct MipData { struct UserMipSolution { double optimal_objective_value; std::vector optimal_solution; - HighsInt require_user_solution_callback_origin; + HighsInt require_external_solution_query_origin; }; // Callback that saves message for comparison @@ -193,8 +193,8 @@ HighsCallbackFunctionType userkMipUserSolution = void* user_callback_data) { UserMipSolution callback_data = *(static_cast(user_callback_data)); - if (data_out->user_solution_callback_origin == - callback_data.require_user_solution_callback_origin) { + if (data_out->external_solution_query_origin == + callback_data.require_external_solution_query_origin) { if (data_out->mip_primal_bound > callback_data.optimal_objective_value) { // If current objective value is not optimal, pass the @@ -203,7 +203,7 @@ HighsCallbackFunctionType userkMipUserSolution = printf( "userkMipUserSolution: origin = %d; %g = mip_primal_bound > " "optimal_objective_value = %g\n", - int(data_out->user_solution_callback_origin), + int(data_out->external_solution_query_origin), data_out->mip_primal_bound, callback_data.optimal_objective_value); data_in->user_has_solution = true; @@ -218,13 +218,13 @@ HighsCallbackFunctionType userkMipUserSetSolution = void* user_callback_data) { const auto& callback_data = *(static_cast(user_callback_data)); - if (data_out->user_solution_callback_origin == - callback_data.require_user_solution_callback_origin) { + if (data_out->external_solution_query_origin == + callback_data.require_external_solution_query_origin) { if (dev_run) printf( "userkMipUserSetSolution: origin = %d; %g = mip_primal_bound > " "optimal_objective_value = %g\n", - int(data_out->user_solution_callback_origin), + int(data_out->external_solution_query_origin), data_out->mip_primal_bound, callback_data.optimal_objective_value); @@ -239,14 +239,14 @@ HighsCallbackFunctionType userkMipUserSetPartialSolution = void* user_callback_data) { const auto& callback_data = *(static_cast(user_callback_data)); - if (data_out->user_solution_callback_origin == - callback_data.require_user_solution_callback_origin) { + if (data_out->external_solution_query_origin == + callback_data.require_external_solution_query_origin) { if (dev_run) printf( "userkMipUserSetPartialSolution: origin = %d; %g = " "mip_primal_bound > " "optimal_objective_value = %g\n", - int(data_out->user_solution_callback_origin), + int(data_out->external_solution_query_origin), data_out->mip_primal_bound, callback_data.optimal_objective_value); @@ -511,7 +511,7 @@ static void runMipUserSolutionTest( UserMipSolution user_callback_data; user_callback_data.optimal_objective_value = objective_function_value0; user_callback_data.optimal_solution = optimal_solution; - user_callback_data.require_user_solution_callback_origin = + user_callback_data.require_external_solution_query_origin = require_origin[iModel]; void* p_user_callback_data = (void*)(&user_callback_data); @@ -597,4 +597,4 @@ TEST_CASE("highs-callback-mip-user-solution-c", "[highs-callback]") { highs.run(); highs.resetGlobalScheduler(true); -} \ No newline at end of file +} diff --git a/highs/lp_data/HighsCallback.cpp b/highs/lp_data/HighsCallback.cpp index 1a12a93f65c..4d7965a49e0 100644 --- a/highs/lp_data/HighsCallback.cpp +++ b/highs/lp_data/HighsCallback.cpp @@ -35,8 +35,8 @@ void HighsCallback::clearHighsCallbackOutput() { this->data_out.cutpool_value.clear(); this->data_out.cutpool_lower.clear(); this->data_out.cutpool_upper.clear(); - this->data_out.user_solution_callback_origin = - userMipSolutionCallbackOrigin::kUserMipSolutionCallbackOriginAfterSetup; + this->data_out.external_solution_query_origin = + ExternalMipSolutionQueryOrigin::kExternalMipSolutionQueryOriginAfterSetup; } void HighsCallback::clearHighsCallbackInput() { @@ -134,8 +134,8 @@ HighsCallbackOutput::operator HighsCallbackDataOut() const { ? nullptr : const_cast(cutpool_upper.data()); - data.user_solution_callback_origin = - static_cast(user_solution_callback_origin); + data.external_solution_query_origin = + static_cast(external_solution_query_origin); return data; } @@ -320,4 +320,4 @@ HighsStatus HighsCallbackInput::repairSolution() { return HighsStatus::kError; } } -} \ No newline at end of file +} diff --git a/highs/lp_data/HighsCallback.h b/highs/lp_data/HighsCallback.h index 9be0e9f421b..6b350dbea9d 100644 --- a/highs/lp_data/HighsCallback.h +++ b/highs/lp_data/HighsCallback.h @@ -20,14 +20,14 @@ class Highs; #include "lp_data/HStruct.h" #include "lp_data/HighsCallbackStruct.h" -enum userMipSolutionCallbackOrigin { - kUserMipSolutionCallbackOriginAfterSetup = 0, - kUserMipSolutionCallbackOriginBeforeDive, - kUserMipSolutionCallbackOriginEvaluateRootNode0, - kUserMipSolutionCallbackOriginEvaluateRootNode1, - kUserMipSolutionCallbackOriginEvaluateRootNode2, - kUserMipSolutionCallbackOriginEvaluateRootNode3, - kUserMipSolutionCallbackOriginEvaluateRootNode4 +enum ExternalMipSolutionQueryOrigin { + kExternalMipSolutionQueryOriginAfterSetup = 0, + kExternalMipSolutionQueryOriginBeforeDive, + kExternalMipSolutionQueryOriginEvaluateRootNode0, + kExternalMipSolutionQueryOriginEvaluateRootNode1, + kExternalMipSolutionQueryOriginEvaluateRootNode2, + kExternalMipSolutionQueryOriginEvaluateRootNode3, + kExternalMipSolutionQueryOriginEvaluateRootNode4 }; /** @@ -54,7 +54,7 @@ struct HighsCallbackOutput { std::vector cutpool_value; std::vector cutpool_lower; std::vector cutpool_upper; - userMipSolutionCallbackOrigin user_solution_callback_origin; + ExternalMipSolutionQueryOrigin external_solution_query_origin; operator HighsCallbackDataOut() const; }; diff --git a/highs/lp_data/HighsCallbackStruct.h b/highs/lp_data/HighsCallbackStruct.h index 211dc82b188..eca86e21b52 100644 --- a/highs/lp_data/HighsCallbackStruct.h +++ b/highs/lp_data/HighsCallbackStruct.h @@ -44,7 +44,7 @@ typedef struct { double* cutpool_value; double* cutpool_lower; double* cutpool_upper; - HighsInt user_solution_callback_origin; + HighsInt external_solution_query_origin; } HighsCallbackDataOut; // Some external packages (e.g., jump) currently assume that the first 2 fields diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index 133e6f74a04..8eb41079198 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -134,8 +134,8 @@ void HighsMipSolver::run() { // Possibly look for primal solution from the user if (!submip && callback_->user_callback && callback_->active[kCallbackMipUserSolution]) - mipdata_->callbackUserSolution(solution_objective_, - kUserMipSolutionCallbackOriginAfterSetup); + mipdata_->queryExternalSolution(solution_objective_, + kExternalMipSolutionQueryOriginAfterSetup); if (options_mip_->mip_heuristic_run_feasibility_jump) { // Apply the feasibility jump before evaluating the root node @@ -237,8 +237,8 @@ void HighsMipSolver::run() { // Possibly look for primal solution from the user if (!submip && callback_->user_callback && callback_->active[kCallbackMipUserSolution]) - mipdata_->callbackUserSolution(solution_objective_, - kUserMipSolutionCallbackOriginBeforeDive); + mipdata_->queryExternalSolution(solution_objective_, + kExternalMipSolutionQueryOriginBeforeDive); analysis_.mipTimerStart(kMipClockPerformAging1); mipdata_->conflictPool.performAging(); diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index 086cd3c1ce7..a44806d9c81 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -1902,9 +1902,9 @@ void HighsMipSolverData::evaluateRootNode() { // Possibly look for primal solution from the user if (!mipsolver.submip && mipsolver.callback_->user_callback && mipsolver.callback_->active[kCallbackMipUserSolution]) - mipsolver.mipdata_->callbackUserSolution( + mipsolver.mipdata_->queryExternalSolution( mipsolver.solution_objective_, - kUserMipSolutionCallbackOriginEvaluateRootNode0); + kExternalMipSolutionQueryOriginEvaluateRootNode0); // check if only root presolve is allowed if (firstrootbasis.valid) @@ -2155,9 +2155,9 @@ void HighsMipSolverData::evaluateRootNode() { // Possibly look for primal solution from the user if (!mipsolver.submip && mipsolver.callback_->user_callback && mipsolver.callback_->active[kCallbackMipUserSolution]) - mipsolver.mipdata_->callbackUserSolution( + mipsolver.mipdata_->queryExternalSolution( mipsolver.solution_objective_, - kUserMipSolutionCallbackOriginEvaluateRootNode1); + kExternalMipSolutionQueryOriginEvaluateRootNode1); } analysis.mipTimerStop(kMipClockRootSeparation); if (analysis.analyse_mip_time) { @@ -2225,9 +2225,9 @@ void HighsMipSolverData::evaluateRootNode() { // Possibly look for primal solution from the user if (!mipsolver.submip && mipsolver.callback_->user_callback && mipsolver.callback_->active[kCallbackMipUserSolution]) - mipsolver.mipdata_->callbackUserSolution( + mipsolver.mipdata_->queryExternalSolution( mipsolver.solution_objective_, - kUserMipSolutionCallbackOriginEvaluateRootNode2); + kExternalMipSolutionQueryOriginEvaluateRootNode2); // Possible cut extraction callback if (!mipsolver.submip && mipsolver.callback_->user_callback && @@ -2301,9 +2301,9 @@ void HighsMipSolverData::evaluateRootNode() { // Possibly look for primal solution from the user if (!mipsolver.submip && mipsolver.callback_->user_callback && mipsolver.callback_->active[kCallbackMipUserSolution]) - mipsolver.mipdata_->callbackUserSolution( + mipsolver.mipdata_->queryExternalSolution( mipsolver.solution_objective_, - kUserMipSolutionCallbackOriginEvaluateRootNode3); + kExternalMipSolutionQueryOriginEvaluateRootNode3); } if (upper_limit != kHighsInf || mipsolver.submip) break; @@ -2354,9 +2354,9 @@ void HighsMipSolverData::evaluateRootNode() { // Possibly look for primal solution from the user if (!mipsolver.submip && mipsolver.callback_->user_callback && mipsolver.callback_->active[kCallbackMipUserSolution]) - mipsolver.mipdata_->callbackUserSolution( + mipsolver.mipdata_->queryExternalSolution( mipsolver.solution_objective_, - kUserMipSolutionCallbackOriginEvaluateRootNode4); + kExternalMipSolutionQueryOriginEvaluateRootNode4); removeFixedIndices(); if (lp.getLpSolver().getBasis().valid) lp.removeObsoleteRows(); @@ -2620,46 +2620,53 @@ bool HighsMipSolverData::interruptFromCallbackWithData( return mipsolver.callback_->callbackAction(callback_type, message); } -void HighsMipSolverData::callbackUserSolution( +void HighsMipSolverData::queryExternalSolution( const double mipsolver_objective_value, - const userMipSolutionCallbackOrigin user_solution_callback_origin) { - setCallbackDataOut(mipsolver_objective_value); - mipsolver.callback_->data_out.user_solution_callback_origin = - user_solution_callback_origin; - mipsolver.callback_->clearHighsCallbackInput(); - - const bool interrupt = mipsolver.callback_->callbackAction( - kCallbackMipUserSolution, "MIP User solution"); - assert(!interrupt); - if (mipsolver.callback_->data_in.user_has_solution) { - const auto& user_solution = mipsolver.callback_->data_in.user_solution; - double bound_violation_ = 0; - double row_violation_ = 0; - double integrality_violation_ = 0; - HighsCDouble user_solution_quad_objective_value = 0; - const bool feasible = mipsolver.solutionFeasible( - mipsolver.orig_model_, user_solution, nullptr, bound_violation_, - row_violation_, integrality_violation_, - user_solution_quad_objective_value); - double user_solution_objective_value = + const ExternalMipSolutionQueryOrigin external_solution_query_origin) { + + const bool callback = mipsolver.callback_->user_callback && + mipsolver.callback_->active[kCallbackMipUserSolution]; + assert(callback); + if (callback) { + setCallbackDataOut(mipsolver_objective_value); + mipsolver.callback_->data_out.external_solution_query_origin = + external_solution_query_origin; + mipsolver.callback_->clearHighsCallbackInput(); + + const bool interrupt = + mipsolver.callback_->callbackAction(kCallbackMipUserSolution, "MIP User solution"); + assert(!interrupt); + if (mipsolver.callback_->data_in.user_has_solution) { + const auto& user_solution = mipsolver.callback_->data_in.user_solution; + double bound_violation_ = 0; + double row_violation_ = 0; + double integrality_violation_ = 0; + HighsCDouble user_solution_quad_objective_value = 0; + const bool feasible = + mipsolver.solutionFeasible(mipsolver.orig_model_, user_solution, + nullptr, bound_violation_, + row_violation_, integrality_violation_, + user_solution_quad_objective_value); + double user_solution_objective_value = double(user_solution_quad_objective_value); - if (!feasible) { - highsLogUser( - mipsolver.options_mip_->log_options, HighsLogType::kWarning, - "User-supplied solution has with objective %g has violations: " - "bound = %.4g; integrality = %.4g; row = %.4g\n", - user_solution_objective_value, bound_violation_, - integrality_violation_, row_violation_); - return; - } - std::vector reduced_user_solution; - reduced_user_solution = + if (!feasible) { + highsLogUser( + mipsolver.options_mip_->log_options, HighsLogType::kWarning, + "User-supplied solution has with objective %g has violations: " + "bound = %.4g; integrality = %.4g; row = %.4g\n", + user_solution_objective_value, bound_violation_, + integrality_violation_, row_violation_); + return; + } + std::vector reduced_user_solution; + reduced_user_solution = postSolveStack.getReducedPrimalSolution(user_solution); - const bool print_display_line = true; - const bool is_user_solution = true; - addIncumbent(reduced_user_solution, user_solution_objective_value, - kSolutionSourceUserSolution, print_display_line, - is_user_solution); + const bool print_display_line = true; + const bool is_user_solution = true; + addIncumbent(reduced_user_solution, user_solution_objective_value, + kSolutionSourceUserSolution, print_display_line, + is_user_solution); + } } } diff --git a/highs/mip/HighsMipSolverData.h b/highs/mip/HighsMipSolverData.h index d698b3d613a..98e9078c305 100644 --- a/highs/mip/HighsMipSolverData.h +++ b/highs/mip/HighsMipSolverData.h @@ -296,9 +296,9 @@ struct HighsMipSolverData { bool interruptFromCallbackWithData(const int callback_type, const double mipsolver_objective_value, const std::string message = "") const; - void callbackUserSolution( + void queryExternalSolution( const double mipsolver_objective_value, - const userMipSolutionCallbackOrigin user_solution_callback_origin); + const ExternalMipSolutionQueryOrigin external_solution_query_origin); HighsInt mipRaceConcurrency() const; void mipRaceUpdate(); From a4e9290012d8d558d576e45603edc655882f740d Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 17 Jul 2025 15:02:30 +0100 Subject: [PATCH 08/58] can ExternalMipSolutionQueryOrigin be moved to HighsMipSolver.h? --- check/TestMipSolver.cpp | 2 ++ check/TestRays.cpp | 2 +- highs/lp_data/HStruct.h | 2 +- highs/mip/HighsMipSolver.cpp | 10 +++----- highs/mip/HighsMipSolverData.cpp | 44 +++++++++++++++----------------- 5 files changed, 28 insertions(+), 32 deletions(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index c581d7f18cc..6c6703e27d6 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -946,6 +946,7 @@ TEST_CASE("issue-2409", "[highs_test_mip_solver]") { const HighsModelStatus require_model_status = HighsModelStatus::kOptimal; const double optimal_objective = 0.1; Highs highs; + highs.setOptionValue("output_flag", dev_run); REQUIRE(highs.passModel(lp) == HighsStatus::kOk); if (dev_run) printf("Testing that presolve reduces the problem to empty\n"); REQUIRE(highs.presolve() == HighsStatus::kOk); @@ -982,6 +983,7 @@ TEST_CASE("issue-2432", "[highs_test_mip_solver]") { const HighsModelStatus require_model_status = HighsModelStatus::kOptimal; const double optimal_objective = -3777.57124352; Highs highs; + highs.setOptionValue("output_flag", dev_run); REQUIRE(highs.passModel(lp) == HighsStatus::kOk); if (dev_run) printf("Testing that presolve reduces the problem\n"); REQUIRE(highs.presolve() == HighsStatus::kOk); diff --git a/check/TestRays.cpp b/check/TestRays.cpp index 4b3d2a48047..4456e52c896 100644 --- a/check/TestRays.cpp +++ b/check/TestRays.cpp @@ -4,7 +4,7 @@ #include "catch.hpp" #include "lp_data/HConst.h" -const bool dev_run = true; // false; +const bool dev_run = false;//true; // const double zero_ray_value_tolerance = 1e-14; void reportRay(std::string message, HighsInt dim, double* computed, diff --git a/highs/lp_data/HStruct.h b/highs/lp_data/HStruct.h index 64dffd18e99..d0d6f46d380 100644 --- a/highs/lp_data/HStruct.h +++ b/highs/lp_data/HStruct.h @@ -89,7 +89,7 @@ struct MipRaceRecord { struct MipRace { HighsInt my_instance; - MipRaceRecord* record; + MipRaceRecord* record = nullptr; std::vector last_incumbent_read; void clear(); void initialise(const HighsInt mip_race_concurrency, diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index 8eb41079198..8c601ab8c8e 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -131,9 +131,8 @@ void HighsMipSolver::run() { cleanupSolve(); return; } - // Possibly look for primal solution from the user - if (!submip && callback_->user_callback && - callback_->active[kCallbackMipUserSolution]) + // Possibly query existence of an external solution + if (!submip) mipdata_->queryExternalSolution(solution_objective_, kExternalMipSolutionQueryOriginAfterSetup); @@ -234,9 +233,8 @@ void HighsMipSolver::run() { double lowerBoundLastCheck = mipdata_->lower_bound; analysis_.mipTimerStart(kMipClockSearch); while (search.hasNode()) { - // Possibly look for primal solution from the user - if (!submip && callback_->user_callback && - callback_->active[kCallbackMipUserSolution]) + // Possibly query existence of an external solution + if (!submip) mipdata_->queryExternalSolution(solution_objective_, kExternalMipSolutionQueryOriginBeforeDive); diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index a44806d9c81..fcf7087a247 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -1899,9 +1899,8 @@ void HighsMipSolverData::evaluateRootNode() { printDisplayLine(); - // Possibly look for primal solution from the user - if (!mipsolver.submip && mipsolver.callback_->user_callback && - mipsolver.callback_->active[kCallbackMipUserSolution]) + // Possibly query existence of an external solution + if (!mipsolver.submip) mipsolver.mipdata_->queryExternalSolution( mipsolver.solution_objective_, kExternalMipSolutionQueryOriginEvaluateRootNode0); @@ -2152,9 +2151,8 @@ void HighsMipSolverData::evaluateRootNode() { lp.setIterationLimit(std::max(10000, int(10 * avgrootlpiters))); if (ncuts == 0) break; - // Possibly look for primal solution from the user - if (!mipsolver.submip && mipsolver.callback_->user_callback && - mipsolver.callback_->active[kCallbackMipUserSolution]) + // Possibly query existence of an external solution + if (!mipsolver.submip) mipsolver.mipdata_->queryExternalSolution( mipsolver.solution_objective_, kExternalMipSolutionQueryOriginEvaluateRootNode1); @@ -2222,9 +2220,8 @@ void HighsMipSolverData::evaluateRootNode() { } printDisplayLine(); - // Possibly look for primal solution from the user - if (!mipsolver.submip && mipsolver.callback_->user_callback && - mipsolver.callback_->active[kCallbackMipUserSolution]) + // Possibly query existence of an external solution + if (!mipsolver.submip) mipsolver.mipdata_->queryExternalSolution( mipsolver.solution_objective_, kExternalMipSolutionQueryOriginEvaluateRootNode2); @@ -2298,9 +2295,8 @@ void HighsMipSolverData::evaluateRootNode() { ++nseparounds; printDisplayLine(); - // Possibly look for primal solution from the user - if (!mipsolver.submip && mipsolver.callback_->user_callback && - mipsolver.callback_->active[kCallbackMipUserSolution]) + // Possibly query existence of an external solution + if (!mipsolver.submip) mipsolver.mipdata_->queryExternalSolution( mipsolver.solution_objective_, kExternalMipSolutionQueryOriginEvaluateRootNode3); @@ -2351,9 +2347,8 @@ void HighsMipSolverData::evaluateRootNode() { printDisplayLine(); } - // Possibly look for primal solution from the user - if (!mipsolver.submip && mipsolver.callback_->user_callback && - mipsolver.callback_->active[kCallbackMipUserSolution]) + // Possibly query existence of an external solution + if (!mipsolver.submip) mipsolver.mipdata_->queryExternalSolution( mipsolver.solution_objective_, kExternalMipSolutionQueryOriginEvaluateRootNode4); @@ -2624,20 +2619,21 @@ void HighsMipSolverData::queryExternalSolution( const double mipsolver_objective_value, const ExternalMipSolutionQueryOrigin external_solution_query_origin) { - const bool callback = mipsolver.callback_->user_callback && - mipsolver.callback_->active[kCallbackMipUserSolution]; - assert(callback); - if (callback) { + HighsCallback* callback = mipsolver.callback_; + const bool use_callback = + callback->user_callback && + callback->active[kCallbackMipUserSolution]; + if (use_callback) { setCallbackDataOut(mipsolver_objective_value); - mipsolver.callback_->data_out.external_solution_query_origin = + callback->data_out.external_solution_query_origin = external_solution_query_origin; - mipsolver.callback_->clearHighsCallbackInput(); + callback->clearHighsCallbackInput(); const bool interrupt = - mipsolver.callback_->callbackAction(kCallbackMipUserSolution, "MIP User solution"); + callback->callbackAction(kCallbackMipUserSolution, "MIP User solution"); assert(!interrupt); - if (mipsolver.callback_->data_in.user_has_solution) { - const auto& user_solution = mipsolver.callback_->data_in.user_solution; + if (callback->data_in.user_has_solution) { + const auto& user_solution = callback->data_in.user_solution; double bound_violation_ = 0; double row_violation_ = 0; double integrality_violation_ = 0; From 7a515c357cfb96510471948a308ff1274810132c Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 17 Jul 2025 15:21:25 +0100 Subject: [PATCH 09/58] Now checking for terminate instruction - time to use multi-threading! --- highs/mip/HighsMipSolverData.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index fcf7087a247..55312d5dde1 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -2958,6 +2958,8 @@ void MipRace::terminate() { bool MipRace::terminated() const { assert(this->record); + for (HighsInt instance = 0; instance < this->concurrency(); instance++) + if (this->record->terminate[instance]) return true; return false; } From 1bec8022712af9dcf3605ee95b7eefb5c15f040f Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 17 Jul 2025 17:43:19 +0100 Subject: [PATCH 10/58] Have MIP solvers running concurrently --- highs/lp_data/HStruct.h | 2 +- highs/lp_data/Highs.cpp | 46 +++++++++++++++++++++++++++++++---------- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/highs/lp_data/HStruct.h b/highs/lp_data/HStruct.h index d0d6f46d380..64dffd18e99 100644 --- a/highs/lp_data/HStruct.h +++ b/highs/lp_data/HStruct.h @@ -89,7 +89,7 @@ struct MipRaceRecord { struct MipRace { HighsInt my_instance; - MipRaceRecord* record = nullptr; + MipRaceRecord* record; std::vector last_incumbent_read; void clear(); void initialise(const HighsInt mip_race_concurrency, diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index 3ae3dcbe7ca..c8a8d309814 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -4027,19 +4027,43 @@ HighsStatus Highs::callSolveMip() { options_.primal_feasibility_tolerance); } HighsLp& lp = has_semi_variables ? use_lp : model_.lp_; - // Set up the shared memory for the MIP solver race - const HighsInt mip_race_concurrency = this->options_.mip_race_concurrency; - const bool mip_race = mip_race_concurrency > 1; - MipRaceRecord mip_race_record; - if (mip_race) mip_race_record.initialise(mip_race_concurrency, lp.num_col_); + + // Create the master MIP solver instance that will exist beyond any + // race HighsMipSolver solver(callback_, options_, lp, solution_); - if (mip_race) { - // Initialise the MIP race data for this instance - const HighsInt my_mip_race_instance = 0; - solver.mip_race_.initialise(mip_race_concurrency, my_mip_race_instance, &mip_race_record); + + const HighsInt mip_race_concurrency = this->options_.mip_race_concurrency; + if (mip_race_concurrency > 1) { + // Set up the shared memory for the MIP solver race + MipRaceRecord mip_race_record; + mip_race_record.initialise(mip_race_concurrency, lp.num_col_); + + // Don't allow callbacks for workers + HighsCallback worker_callback = callback_; + worker_callback.clear(); + HighsOptions worker_options = options_; + // No workers log to console + worker_options.log_to_console = false; + // Race the MIP solver! + highs::parallel::for_each(0, mip_race_concurrency, [&](HighsInt start, HighsInt end) { + for (HighsInt instance = start; instance < end; instance++) { + printf("MIP race thread %d\n", int(instance)); + if (instance == 0) { + solver.mip_race_.initialise(mip_race_concurrency, instance, &mip_race_record); + solver.run(); + } else { + worker_options.log_file = "mip_worker" + std::to_string(instance) + ".log"; + printf("Setting log_file to %s\n", worker_options.log_file.c_str()); + HighsMipSolver worker(worker_callback, worker_options, lp, solution_); + worker.mip_race_.initialise(mip_race_concurrency, instance, &mip_race_record); + worker.run(); + } + } + }); + } else { + // Run a single MIP solver + solver.run(); } - // Run the MIP solver! - solver.run(); options_.log_dev_level = log_dev_level; // Set the return_status, model status and, for completeness, scaled // model status From 41eb3f122ad5f64021b95fa56f026994a5990cb3 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 17 Jul 2025 18:07:13 +0100 Subject: [PATCH 11/58] Failed to add HighsLogOptions to MipRace --- highs/io/HighsIO.h | 1 - highs/lp_data/HStruct.h | 7 +++++-- highs/lp_data/Highs.cpp | 13 +++++++++++-- highs/mip/HighsMipSolverData.cpp | 5 ++++- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/highs/io/HighsIO.h b/highs/io/HighsIO.h index fb2e12bca0d..7b5e4b4dae9 100644 --- a/highs/io/HighsIO.h +++ b/highs/io/HighsIO.h @@ -15,7 +15,6 @@ #include #include "lp_data/HighsCallback.h" -// #include "util/HighsInt.h" class HighsOptions; diff --git a/highs/lp_data/HStruct.h b/highs/lp_data/HStruct.h index 64dffd18e99..9a72fb6c7a9 100644 --- a/highs/lp_data/HStruct.h +++ b/highs/lp_data/HStruct.h @@ -89,12 +89,15 @@ struct MipRaceRecord { struct MipRace { HighsInt my_instance; - MipRaceRecord* record; + MipRaceRecord* record = nullptr; + // HighsLogOptions log_options; std::vector last_incumbent_read; void clear(); void initialise(const HighsInt mip_race_concurrency, const HighsInt my_instance_, - MipRaceRecord* record_); + MipRaceRecord* record_ + // , const HighsLogOptions log_options_ + ); HighsInt concurrency() const; void update(const double objective, const std::vector& solution); diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index c8a8d309814..6498a199b7b 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -4044,18 +4044,27 @@ HighsStatus Highs::callSolveMip() { HighsOptions worker_options = options_; // No workers log to console worker_options.log_to_console = false; + worker_options.setLogOptions(); // Race the MIP solver! highs::parallel::for_each(0, mip_race_concurrency, [&](HighsInt start, HighsInt end) { for (HighsInt instance = start; instance < end; instance++) { printf("MIP race thread %d\n", int(instance)); if (instance == 0) { - solver.mip_race_.initialise(mip_race_concurrency, instance, &mip_race_record); + solver.mip_race_.initialise(mip_race_concurrency, + instance, + &mip_race_record + // , options_.log_options + ); solver.run(); } else { worker_options.log_file = "mip_worker" + std::to_string(instance) + ".log"; printf("Setting log_file to %s\n", worker_options.log_file.c_str()); HighsMipSolver worker(worker_callback, worker_options, lp, solution_); - worker.mip_race_.initialise(mip_race_concurrency, instance, &mip_race_record); + worker.mip_race_.initialise(mip_race_concurrency, + instance, + &mip_race_record + // , worker_options.log_options + ); worker.run(); } } diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index 55312d5dde1..b868e0ff811 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -2925,11 +2925,14 @@ void MipRace::clear() { void MipRace::initialise(const HighsInt mip_race_concurrency, const HighsInt my_instance_, - MipRaceRecord* record_) { + MipRaceRecord* record_ + //, const HighsLogOptions log_options_ + ) { this->clear(); assert(mip_race_concurrency > 0); this->my_instance = my_instance_; this->record = record_; + // this->log_options = log_options_; this->last_incumbent_read.assign(mip_race_concurrency, -1); } From 239bf49f675bd9c85a1663ea4f156c92bfa8bb50 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 18 Jul 2025 09:35:53 +0100 Subject: [PATCH 12/58] Moved MipRace struct definition to HighsMipSolver.h --- highs/lp_data/HStruct.h | 47 -------------------------------- highs/lp_data/Highs.cpp | 8 +++--- highs/mip/HighsMipSolver.h | 47 ++++++++++++++++++++++++++++++++ highs/mip/HighsMipSolverData.cpp | 6 ++-- 4 files changed, 54 insertions(+), 54 deletions(-) diff --git a/highs/lp_data/HStruct.h b/highs/lp_data/HStruct.h index 9a72fb6c7a9..3b54629832c 100644 --- a/highs/lp_data/HStruct.h +++ b/highs/lp_data/HStruct.h @@ -61,53 +61,6 @@ struct HotStart { std::vector nonbasicMove; }; -struct MipRaceIncumbent { - HighsInt start_write_incumbent = -1; - HighsInt finish_write_incumbent = -1; - double objective = -kHighsInf; - std::vector solution; - void clear(); - void initialise(const HighsInt num_col); - void update(const double objective, - const std::vector& solution); - bool readOk(double& objective_, - std::vector& solution_) const; -}; - -struct MipRaceRecord { - std::vector terminate; - std::vector incumbent; - void clear(); - void initialise(const HighsInt mip_race_concurrency, - const HighsInt num_col); - HighsInt concurrency() const; - void update(const HighsInt instance, - const double objective, - const std::vector& solution); - void report() const; -}; - -struct MipRace { - HighsInt my_instance; - MipRaceRecord* record = nullptr; - // HighsLogOptions log_options; - std::vector last_incumbent_read; - void clear(); - void initialise(const HighsInt mip_race_concurrency, - const HighsInt my_instance_, - MipRaceRecord* record_ - // , const HighsLogOptions log_options_ - ); - HighsInt concurrency() const; - void update(const double objective, - const std::vector& solution); - bool newSolution(double objective, - std::vector& solution) const; - void terminate(); - bool terminated() const; - void report() const; -}; - struct HighsBasis { // Logical flags for a HiGHS basis: // diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index 6498a199b7b..abc66a20868 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -4052,8 +4052,8 @@ HighsStatus Highs::callSolveMip() { if (instance == 0) { solver.mip_race_.initialise(mip_race_concurrency, instance, - &mip_race_record - // , options_.log_options + &mip_race_record, + options_.log_options ); solver.run(); } else { @@ -4062,8 +4062,8 @@ HighsStatus Highs::callSolveMip() { HighsMipSolver worker(worker_callback, worker_options, lp, solution_); worker.mip_race_.initialise(mip_race_concurrency, instance, - &mip_race_record - // , worker_options.log_options + &mip_race_record, + worker_options.log_options ); worker.run(); } diff --git a/highs/mip/HighsMipSolver.h b/highs/mip/HighsMipSolver.h index 4288719be12..107a04ef879 100644 --- a/highs/mip/HighsMipSolver.h +++ b/highs/mip/HighsMipSolver.h @@ -19,6 +19,53 @@ struct HighsPseudocostInitialization; class HighsCliqueTable; class HighsImplications; +struct MipRaceIncumbent { + HighsInt start_write_incumbent = -1; + HighsInt finish_write_incumbent = -1; + double objective = -kHighsInf; + std::vector solution; + void clear(); + void initialise(const HighsInt num_col); + void update(const double objective, + const std::vector& solution); + bool readOk(double& objective_, + std::vector& solution_) const; +}; + +struct MipRaceRecord { + std::vector terminate; + std::vector incumbent; + void clear(); + void initialise(const HighsInt mip_race_concurrency, + const HighsInt num_col); + HighsInt concurrency() const; + void update(const HighsInt instance, + const double objective, + const std::vector& solution); + void report() const; +}; + +struct MipRace { + HighsInt my_instance; + MipRaceRecord* record = nullptr; + HighsLogOptions log_options; + std::vector last_incumbent_read; + void clear(); + void initialise(const HighsInt mip_race_concurrency, + const HighsInt my_instance_, + MipRaceRecord* record_, + const HighsLogOptions log_options_ + ); + HighsInt concurrency() const; + void update(const double objective, + const std::vector& solution); + bool newSolution(double objective, + std::vector& solution) const; + void terminate(); + bool terminated() const; + void report() const; +}; + class HighsMipSolver { public: HighsCallback* callback_; diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index b868e0ff811..f52da4e98ba 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -2925,14 +2925,14 @@ void MipRace::clear() { void MipRace::initialise(const HighsInt mip_race_concurrency, const HighsInt my_instance_, - MipRaceRecord* record_ - //, const HighsLogOptions log_options_ + MipRaceRecord* record_, + const HighsLogOptions log_options_ ) { this->clear(); assert(mip_race_concurrency > 0); this->my_instance = my_instance_; this->record = record_; - // this->log_options = log_options_; + this->log_options = log_options_; this->last_incumbent_read.assign(mip_race_concurrency, -1); } From a4f4cbc59dc5ff51be59b97b6651b83dfea2821f Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 18 Jul 2025 09:43:07 +0100 Subject: [PATCH 13/58] Worker logging only to mip_worker*.log --- highs/lp_data/Highs.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index abc66a20868..0063362957d 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -4057,7 +4057,8 @@ HighsStatus Highs::callSolveMip() { ); solver.run(); } else { - worker_options.log_file = "mip_worker" + std::to_string(instance) + ".log"; + std::string worker_log_file = "mip_worker" + std::to_string(instance) + ".log"; + highsOpenLogFile(worker_options, worker_log_file); printf("Setting log_file to %s\n", worker_options.log_file.c_str()); HighsMipSolver worker(worker_callback, worker_options, lp, solution_); worker.mip_race_.initialise(mip_race_concurrency, From 0985562bb18c4ef9058adf7c2f76cefbf8f6d51e Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 18 Jul 2025 09:56:48 +0100 Subject: [PATCH 14/58] Now flip terminate<->terminated --- highs/mip/HighsMipSolver.h | 2 +- highs/mip/HighsMipSolverData.cpp | 34 ++++++++++++++++---------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/highs/mip/HighsMipSolver.h b/highs/mip/HighsMipSolver.h index 107a04ef879..88de740e485 100644 --- a/highs/mip/HighsMipSolver.h +++ b/highs/mip/HighsMipSolver.h @@ -42,7 +42,7 @@ struct MipRaceRecord { void update(const HighsInt instance, const double objective, const std::vector& solution); - void report() const; + void report(const HighsLogOptions log_options) const; }; struct MipRace { diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index f52da4e98ba..5c52e155663 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -2897,24 +2897,24 @@ void MipRaceRecord::update(const HighsInt instance, this->incumbent[instance].update(objective, solution); } -void MipRaceRecord::report() const { +void MipRaceRecord::report(const HighsLogOptions log_options) const { HighsInt mip_race_concurrency = this->concurrency(); - printf("\nMipRaceRecord: "); + highsLogUser(log_options, HighsLogType::kInfo, "\nMipRaceRecord: "); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) - printf(" %11d", int(instance)); - printf("\nTerminate: "); + highsLogUser(log_options, HighsLogType::kInfo, " %11d", int(instance)); + highsLogUser(log_options, HighsLogType::kInfo, "\nTerminate: "); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) - printf(" %11s", this->terminate[instance] ? "T" : "F"); - printf("\nStartWrite: "); + highsLogUser(log_options, HighsLogType::kInfo, " %11s", this->terminate[instance] ? "T" : "F"); + highsLogUser(log_options, HighsLogType::kInfo, "\nStartWrite: "); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) - printf(" %11d", this->incumbent[instance].start_write_incumbent); - printf("\nObjective: "); + highsLogUser(log_options, HighsLogType::kInfo, " %11d", this->incumbent[instance].start_write_incumbent); + highsLogUser(log_options, HighsLogType::kInfo, "\nObjective: "); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) - printf(" %11.4g", this->incumbent[instance].objective); - printf("\nFinishWrite: "); + highsLogUser(log_options, HighsLogType::kInfo, " %11.4g", this->incumbent[instance].objective); + highsLogUser(log_options, HighsLogType::kInfo, "\nFinishWrite: "); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) - printf(" %11d", this->incumbent[instance].finish_write_incumbent); - printf("\n"); + highsLogUser(log_options, HighsLogType::kInfo, " %11d", this->incumbent[instance].finish_write_incumbent); + highsLogUser(log_options, HighsLogType::kInfo, "\n"); } void MipRace::clear() { @@ -2956,7 +2956,7 @@ bool MipRace::newSolution(double objective, void MipRace::terminate() { assert(this->record); - this->record->terminate.assign(this->concurrency(), true); + this->record->terminate[this->my_instance] = true; } bool MipRace::terminated() const { @@ -2968,10 +2968,10 @@ bool MipRace::terminated() const { void MipRace::report() const { assert(this->record); - this->record->report(); - printf("LastIncumbentRead: "); + this->record->report(this->log_options); + highsLogUser(this->log_options, HighsLogType::kInfo, "LastIncumbentRead: "); for (HighsInt instance = 0; instance < this->concurrency(); instance++) - printf(" %11d", this->last_incumbent_read[instance]); - printf("\n\n"); + highsLogUser(this->log_options, HighsLogType::kInfo, " %11d", this->last_incumbent_read[instance]); + highsLogUser(this->log_options, HighsLogType::kInfo, "\n\n"); } From a3b0846837082caddc518161b4aa98686655edf2 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 18 Jul 2025 10:30:44 +0100 Subject: [PATCH 15/58] Now only terminate the MIP race if it's not already terminated --- highs/mip/HighsMipSolver.cpp | 6 ++++-- highs/mip/HighsMipSolver.h | 2 +- highs/mip/HighsMipSolverData.cpp | 14 +++++++------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index 8c601ab8c8e..40293e53b21 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -688,9 +688,11 @@ void HighsMipSolver::run() { } void HighsMipSolver::cleanupSolve() { - // Terminate any MIP race + if (!submip) { - mipdata_->mipRaceTerminate(); + // If another instance has not terminated the MIP race, then + // terminate it + if (!mipdata_->mipRaceTerminated()) mipdata_->mipRaceTerminate(); mipdata_->mipRaceReport(); } diff --git a/highs/mip/HighsMipSolver.h b/highs/mip/HighsMipSolver.h index 88de740e485..2b551109ff7 100644 --- a/highs/mip/HighsMipSolver.h +++ b/highs/mip/HighsMipSolver.h @@ -33,7 +33,7 @@ struct MipRaceIncumbent { }; struct MipRaceRecord { - std::vector terminate; + std::vector terminated; std::vector incumbent; void clear(); void initialise(const HighsInt mip_race_concurrency, diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index 5c52e155663..c409621ed9a 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -2409,7 +2409,7 @@ void HighsMipSolverData::evaluateRootNode() { bool HighsMipSolverData::checkLimits(int64_t nodeOffset) const { const HighsOptions& options = *mipsolver.options_mip_; - // Possible termination of MIP race + // MIP race may have terminated if (!mipsolver.submip && this->mipRaceTerminated()) return true; // Possible user interrupt @@ -2873,14 +2873,14 @@ bool MipRaceIncumbent::readOk(double& objective_, } void MipRaceRecord::clear() { - this->terminate.clear(); + this->terminated.clear(); this->incumbent.clear(); } void MipRaceRecord::initialise(const HighsInt mip_race_concurrency, const HighsInt num_col) { this->clear(); - this->terminate.assign(mip_race_concurrency, false); + this->terminated.assign(mip_race_concurrency, false); MipRaceIncumbent incumbent_; incumbent_.initialise(num_col); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) @@ -2902,9 +2902,9 @@ void MipRaceRecord::report(const HighsLogOptions log_options) const { highsLogUser(log_options, HighsLogType::kInfo, "\nMipRaceRecord: "); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) highsLogUser(log_options, HighsLogType::kInfo, " %11d", int(instance)); - highsLogUser(log_options, HighsLogType::kInfo, "\nTerminate: "); + highsLogUser(log_options, HighsLogType::kInfo, "\nTerminated: "); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) - highsLogUser(log_options, HighsLogType::kInfo, " %11s", this->terminate[instance] ? "T" : "F"); + highsLogUser(log_options, HighsLogType::kInfo, " %11s", this->terminated[instance] ? "T" : "F"); highsLogUser(log_options, HighsLogType::kInfo, "\nStartWrite: "); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) highsLogUser(log_options, HighsLogType::kInfo, " %11d", this->incumbent[instance].start_write_incumbent); @@ -2956,13 +2956,13 @@ bool MipRace::newSolution(double objective, void MipRace::terminate() { assert(this->record); - this->record->terminate[this->my_instance] = true; + this->record->terminated[this->my_instance] = true; } bool MipRace::terminated() const { assert(this->record); for (HighsInt instance = 0; instance < this->concurrency(); instance++) - if (this->record->terminate[instance]) return true; + if (this->record->terminated[instance]) return true; return false; } From 2b1a1312284aab2fe53bcbf5834cdde06397069c Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 18 Jul 2025 11:58:02 +0100 Subject: [PATCH 16/58] Introduced HighsModelStatus::kHighsInterrupt for MIP race interrupt --- check/TestMipSolver.cpp | 6 +-- highs/highs_bindings.cpp | 3 +- highs/interfaces/highs_csharp_api.cs | 3 +- highs/lp_data/HConst.h | 3 +- highs/lp_data/Highs.cpp | 49 ++++++++--------- highs/lp_data/HighsInfoDebug.cpp | 2 + highs/lp_data/HighsModelUtils.cpp | 5 ++ highs/lp_data/HighsOptions.h | 8 +-- highs/mip/HighsMipSolver.cpp | 32 +++++------ highs/mip/HighsMipSolver.h | 26 ++++----- highs/mip/HighsMipSolverData.cpp | 80 ++++++++++++++-------------- highs/mip/HighsMipSolverData.h | 3 +- 12 files changed, 107 insertions(+), 113 deletions(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index 6c6703e27d6..a3787a599ad 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -1005,14 +1005,10 @@ TEST_CASE("issue-2432", "[highs_test_mip_solver]") { TEST_CASE("mip-race", "[highs_test_mip_solver]") { const std::string model = "flugpl"; const std::string model_file = - std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; + std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; Highs h; // h.setOptionValue("output_flag", dev_run); h.setOptionValue("mip_race_concurrency", 2); REQUIRE(h.readModel(model_file) == HighsStatus::kOk); REQUIRE(h.run() == HighsStatus::kOk); - - - } - diff --git a/highs/highs_bindings.cpp b/highs/highs_bindings.cpp index 84990c130d0..35ae51d3f97 100644 --- a/highs/highs_bindings.cpp +++ b/highs/highs_bindings.cpp @@ -975,7 +975,8 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { .value("kUnknown", HighsModelStatus::kUnknown) .value("kSolutionLimit", HighsModelStatus::kSolutionLimit) .value("kInterrupt", HighsModelStatus::kInterrupt) - .value("kMemoryLimit", HighsModelStatus::kMemoryLimit); + .value("kMemoryLimit", HighsModelStatus::kMemoryLimit) + .value("kHighsInterrupt", HighsModelStatus::kHighsInterrupt); py::enum_(m, "HighsPresolveStatus", py::module_local()) .value("kNotPresolved", HighsPresolveStatus::kNotPresolved) .value("kNotReduced", HighsPresolveStatus::kNotReduced) diff --git a/highs/interfaces/highs_csharp_api.cs b/highs/interfaces/highs_csharp_api.cs index e06f6027507..4c8c31b1641 100644 --- a/highs/interfaces/highs_csharp_api.cs +++ b/highs/interfaces/highs_csharp_api.cs @@ -61,7 +61,8 @@ public enum HighsModelStatus kUnknown, kSolutionLimit, kInterrupt, - kMemoryLimit + kMemoryLimit, + kHighsInterrupt } public enum HighsIntegrality diff --git a/highs/lp_data/HConst.h b/highs/lp_data/HConst.h index 1a71d94b374..90f52814531 100644 --- a/highs/lp_data/HConst.h +++ b/highs/lp_data/HConst.h @@ -211,8 +211,9 @@ enum class HighsModelStatus { kSolutionLimit, kInterrupt, kMemoryLimit, + kHighsInterrupt, kMin = kNotset, - kMax = kMemoryLimit + kMax = kHighsInterrupt }; enum HighsCallbackType : int { diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index 0063362957d..185161834d7 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -4046,30 +4046,29 @@ HighsStatus Highs::callSolveMip() { worker_options.log_to_console = false; worker_options.setLogOptions(); // Race the MIP solver! - highs::parallel::for_each(0, mip_race_concurrency, [&](HighsInt start, HighsInt end) { - for (HighsInt instance = start; instance < end; instance++) { - printf("MIP race thread %d\n", int(instance)); - if (instance == 0) { - solver.mip_race_.initialise(mip_race_concurrency, - instance, - &mip_race_record, - options_.log_options - ); - solver.run(); - } else { - std::string worker_log_file = "mip_worker" + std::to_string(instance) + ".log"; - highsOpenLogFile(worker_options, worker_log_file); - printf("Setting log_file to %s\n", worker_options.log_file.c_str()); - HighsMipSolver worker(worker_callback, worker_options, lp, solution_); - worker.mip_race_.initialise(mip_race_concurrency, - instance, - &mip_race_record, - worker_options.log_options - ); - worker.run(); - } - } - }); + highs::parallel::for_each( + 0, mip_race_concurrency, [&](HighsInt start, HighsInt end) { + for (HighsInt instance = start; instance < end; instance++) { + if (instance == 0) { + solver.mip_race_.initialise(mip_race_concurrency, instance, + &mip_race_record, + options_.log_options); + solver.run(); + } else { + // Use the instance ID as an offset to the random seed + worker_options.random_seed = options_.random_seed + instance; + std::string worker_log_file = + "mip_worker" + std::to_string(instance) + ".log"; + highsOpenLogFile(worker_options, worker_log_file); + HighsMipSolver worker(worker_callback, worker_options, lp, + solution_); + worker.mip_race_.initialise(mip_race_concurrency, instance, + &mip_race_record, + worker_options.log_options); + worker.run(); + } + } + }); } else { // Run a single MIP solver solver.run(); @@ -4544,6 +4543,7 @@ HighsStatus Highs::returnFromOptimizeModel(const HighsStatus run_return_status, case HighsModelStatus::kIterationLimit: case HighsModelStatus::kSolutionLimit: case HighsModelStatus::kInterrupt: + case HighsModelStatus::kHighsInterrupt: case HighsModelStatus::kUnknown: assert(return_status == HighsStatus::kWarning); break; @@ -4584,6 +4584,7 @@ HighsStatus Highs::returnFromOptimizeModel(const HighsStatus run_return_status, case HighsModelStatus::kIterationLimit: case HighsModelStatus::kSolutionLimit: case HighsModelStatus::kInterrupt: + case HighsModelStatus::kHighsInterrupt: case HighsModelStatus::kUnknown: // Have info and primal solution (unless infeasible). No primal solution // in some other case, too! diff --git a/highs/lp_data/HighsInfoDebug.cpp b/highs/lp_data/HighsInfoDebug.cpp index 56513de1df1..9578a242c96 100644 --- a/highs/lp_data/HighsInfoDebug.cpp +++ b/highs/lp_data/HighsInfoDebug.cpp @@ -48,6 +48,8 @@ HighsDebugStatus debugInfo(const HighsOptions& options, const HighsLp& lp, case HighsModelStatus::kTimeLimit: case HighsModelStatus::kIterationLimit: case HighsModelStatus::kSolutionLimit: + case HighsModelStatus::kInterrupt: + case HighsModelStatus::kHighsInterrupt: case HighsModelStatus::kUnknown: // Should have info assert(have_info == true); diff --git a/highs/lp_data/HighsModelUtils.cpp b/highs/lp_data/HighsModelUtils.cpp index d7aeccb153a..d8bc7fdb112 100644 --- a/highs/lp_data/HighsModelUtils.cpp +++ b/highs/lp_data/HighsModelUtils.cpp @@ -1387,6 +1387,9 @@ std::string utilModelStatusToString(const HighsModelStatus model_status) { case HighsModelStatus::kInterrupt: return "Interrupted by user"; break; + case HighsModelStatus::kHighsInterrupt: + return "Interrupted by HiGHS"; + break; case HighsModelStatus::kUnknown: return "Unknown"; break; @@ -1471,6 +1474,8 @@ HighsStatus highsStatusFromHighsModelStatus(HighsModelStatus model_status) { return HighsStatus::kWarning; case HighsModelStatus::kInterrupt: return HighsStatus::kWarning; + case HighsModelStatus::kHighsInterrupt: + return HighsStatus::kWarning; case HighsModelStatus::kUnknown: return HighsStatus::kWarning; default: diff --git a/highs/lp_data/HighsOptions.h b/highs/lp_data/HighsOptions.h index a23eea41443..731deadd117 100644 --- a/highs/lp_data/HighsOptions.h +++ b/highs/lp_data/HighsOptions.h @@ -576,7 +576,7 @@ struct HighsOptionsStruct { icrash_breakpoints(false), mip_detect_symmetry(false), mip_allow_restart(false), - mip_race_concurrency(0), + mip_race_concurrency(0), mip_max_nodes(0), mip_max_stall_nodes(0), mip_max_start_nodes(0), @@ -1019,9 +1019,9 @@ class HighsOptions : public HighsOptionsStruct { advanced, &mip_allow_restart, true); records.push_back(record_bool); - record_int = new OptionRecordInt("mip_race_concurrency", - "Concurrency for non-deterministic MIP race", advanced, - &mip_race_concurrency, 0, 0, kHighsIInf); + record_int = new OptionRecordInt( + "mip_race_concurrency", "Concurrency for non-deterministic MIP race", + advanced, &mip_race_concurrency, 0, 0, kHighsIInf); records.push_back(record_int); record_int = new OptionRecordInt("mip_max_nodes", diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index 40293e53b21..4771ca40945 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -133,8 +133,8 @@ void HighsMipSolver::run() { } // Possibly query existence of an external solution if (!submip) - mipdata_->queryExternalSolution(solution_objective_, - kExternalMipSolutionQueryOriginAfterSetup); + mipdata_->queryExternalSolution( + solution_objective_, kExternalMipSolutionQueryOriginAfterSetup); if (options_mip_->mip_heuristic_run_feasibility_jump) { // Apply the feasibility jump before evaluating the root node @@ -148,16 +148,6 @@ void HighsMipSolver::run() { cleanupSolve(); return; } - const bool bailout_after_feasibility_jump = false; - if (bailout_after_feasibility_jump) { - highsLogUser(options_mip_->log_options, HighsLogType::kInfo, - "HighsMipSolver: Bailing out after Feasibility Jump with " - "model status = %s\n", - utilModelStatusToString(returned_model_status).c_str()); - modelstatus_ = HighsModelStatus::kInterrupt; - cleanupSolve(); - return; - } } // Apply the trivial heuristics analysis_.mipTimerStart(kMipClockTrivialHeuristics); @@ -235,8 +225,8 @@ void HighsMipSolver::run() { while (search.hasNode()) { // Possibly query existence of an external solution if (!submip) - mipdata_->queryExternalSolution(solution_objective_, - kExternalMipSolutionQueryOriginBeforeDive); + mipdata_->queryExternalSolution( + solution_objective_, kExternalMipSolutionQueryOriginBeforeDive); analysis_.mipTimerStart(kMipClockPerformAging1); mipdata_->conflictPool.performAging(); @@ -688,14 +678,18 @@ void HighsMipSolver::run() { } void HighsMipSolver::cleanupSolve() { - if (!submip) { - // If another instance has not terminated the MIP race, then - // terminate it - if (!mipdata_->mipRaceTerminated()) mipdata_->mipRaceTerminate(); + if (!mipdata_->mipRaceTerminated()) { + // No other instance has terminated the MIP race, so terminate + // it + mipdata_->mipRaceTerminate(); + } else { + // Indicate that this MIP race instance has been interrupted + modelstatus_ = HighsModelStatus::kHighsInterrupt; + } mipdata_->mipRaceReport(); } - + // Force a final logging line mipdata_->printDisplayLine(kSolutionSourceCleanup); // Stop the solve clock - which won't be running if presolve diff --git a/highs/mip/HighsMipSolver.h b/highs/mip/HighsMipSolver.h index 2b551109ff7..cde6c23a5cf 100644 --- a/highs/mip/HighsMipSolver.h +++ b/highs/mip/HighsMipSolver.h @@ -26,22 +26,18 @@ struct MipRaceIncumbent { std::vector solution; void clear(); void initialise(const HighsInt num_col); - void update(const double objective, - const std::vector& solution); - bool readOk(double& objective_, - std::vector& solution_) const; + void update(const double objective, const std::vector& solution); + bool readOk(double& objective_, std::vector& solution_) const; }; struct MipRaceRecord { std::vector terminated; std::vector incumbent; void clear(); - void initialise(const HighsInt mip_race_concurrency, - const HighsInt num_col); + void initialise(const HighsInt mip_race_concurrency, const HighsInt num_col); HighsInt concurrency() const; - void update(const HighsInt instance, - const double objective, - const std::vector& solution); + void update(const HighsInt instance, const double objective, + const std::vector& solution); void report(const HighsLogOptions log_options) const; }; @@ -52,15 +48,11 @@ struct MipRace { std::vector last_incumbent_read; void clear(); void initialise(const HighsInt mip_race_concurrency, - const HighsInt my_instance_, - MipRaceRecord* record_, - const HighsLogOptions log_options_ - ); + const HighsInt my_instance_, MipRaceRecord* record_, + const HighsLogOptions log_options_); HighsInt concurrency() const; - void update(const double objective, - const std::vector& solution); - bool newSolution(double objective, - std::vector& solution) const; + void update(const double objective, const std::vector& solution); + bool newSolution(double objective, std::vector& solution) const; void terminate(); bool terminated() const; void report() const; diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index c409621ed9a..a2c6d0e4e1d 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -2410,7 +2410,7 @@ bool HighsMipSolverData::checkLimits(int64_t nodeOffset) const { const HighsOptions& options = *mipsolver.options_mip_; // MIP race may have terminated - if (!mipsolver.submip && this->mipRaceTerminated()) return true; + if (!mipsolver.submip && this->mipRaceTerminated()) return true; // Possible user interrupt if (!mipsolver.submip && mipsolver.callback_->user_callback) { @@ -2618,19 +2618,17 @@ bool HighsMipSolverData::interruptFromCallbackWithData( void HighsMipSolverData::queryExternalSolution( const double mipsolver_objective_value, const ExternalMipSolutionQueryOrigin external_solution_query_origin) { - HighsCallback* callback = mipsolver.callback_; const bool use_callback = - callback->user_callback && - callback->active[kCallbackMipUserSolution]; + callback->user_callback && callback->active[kCallbackMipUserSolution]; if (use_callback) { setCallbackDataOut(mipsolver_objective_value); callback->data_out.external_solution_query_origin = - external_solution_query_origin; + external_solution_query_origin; callback->clearHighsCallbackInput(); const bool interrupt = - callback->callbackAction(kCallbackMipUserSolution, "MIP User solution"); + callback->callbackAction(kCallbackMipUserSolution, "MIP User solution"); assert(!interrupt); if (callback->data_in.user_has_solution) { const auto& user_solution = callback->data_in.user_solution; @@ -2638,30 +2636,29 @@ void HighsMipSolverData::queryExternalSolution( double row_violation_ = 0; double integrality_violation_ = 0; HighsCDouble user_solution_quad_objective_value = 0; - const bool feasible = - mipsolver.solutionFeasible(mipsolver.orig_model_, user_solution, - nullptr, bound_violation_, - row_violation_, integrality_violation_, - user_solution_quad_objective_value); + const bool feasible = mipsolver.solutionFeasible( + mipsolver.orig_model_, user_solution, nullptr, bound_violation_, + row_violation_, integrality_violation_, + user_solution_quad_objective_value); double user_solution_objective_value = - double(user_solution_quad_objective_value); + double(user_solution_quad_objective_value); if (!feasible) { - highsLogUser( - mipsolver.options_mip_->log_options, HighsLogType::kWarning, - "User-supplied solution has with objective %g has violations: " - "bound = %.4g; integrality = %.4g; row = %.4g\n", - user_solution_objective_value, bound_violation_, - integrality_violation_, row_violation_); - return; + highsLogUser( + mipsolver.options_mip_->log_options, HighsLogType::kWarning, + "User-supplied solution has with objective %g has violations: " + "bound = %.4g; integrality = %.4g; row = %.4g\n", + user_solution_objective_value, bound_violation_, + integrality_violation_, row_violation_); + return; } std::vector reduced_user_solution; reduced_user_solution = - postSolveStack.getReducedPrimalSolution(user_solution); + postSolveStack.getReducedPrimalSolution(user_solution); const bool print_display_line = true; const bool is_user_solution = true; addIncumbent(reduced_user_solution, user_solution_objective_value, - kSolutionSourceUserSolution, print_display_line, - is_user_solution); + kSolutionSourceUserSolution, print_display_line, + is_user_solution); } } } @@ -2675,10 +2672,12 @@ HighsInt HighsMipSolverData::mipRaceConcurrency() const { void HighsMipSolverData::mipRaceUpdate() { if (!mipsolver.mip_race_.record) return; assert(!mipsolver.submip); - mipsolver.mip_race_.update(mipsolver.solution_objective_, mipsolver.solution_); + mipsolver.mip_race_.update(mipsolver.solution_objective_, + mipsolver.solution_); } -bool HighsMipSolverData::mipRaceNewSolution(double& objective_value, std::vector& solution) { +bool HighsMipSolverData::mipRaceNewSolution(double& objective_value, + std::vector& solution) { if (!mipsolver.mip_race_.record) return false; assert(!mipsolver.submip); return false; @@ -2850,7 +2849,7 @@ void MipRaceIncumbent::initialise(const HighsInt num_col) { } void MipRaceIncumbent::update(const double objective_, - const std::vector& solution_) { + const std::vector& solution_) { assert(this->solution.size() == solution_.size()); this->start_write_incumbent++; this->objective = objective_; @@ -2891,9 +2890,8 @@ HighsInt MipRaceRecord::concurrency() const { return static_cast(this->incumbent.size()); } -void MipRaceRecord::update(const HighsInt instance, - const double objective, - const std::vector& solution) { +void MipRaceRecord::update(const HighsInt instance, const double objective, + const std::vector& solution) { this->incumbent[instance].update(objective, solution); } @@ -2904,16 +2902,20 @@ void MipRaceRecord::report(const HighsLogOptions log_options) const { highsLogUser(log_options, HighsLogType::kInfo, " %11d", int(instance)); highsLogUser(log_options, HighsLogType::kInfo, "\nTerminated: "); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) - highsLogUser(log_options, HighsLogType::kInfo, " %11s", this->terminated[instance] ? "T" : "F"); + highsLogUser(log_options, HighsLogType::kInfo, " %11s", + this->terminated[instance] ? "T" : "F"); highsLogUser(log_options, HighsLogType::kInfo, "\nStartWrite: "); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) - highsLogUser(log_options, HighsLogType::kInfo, " %11d", this->incumbent[instance].start_write_incumbent); + highsLogUser(log_options, HighsLogType::kInfo, " %11d", + this->incumbent[instance].start_write_incumbent); highsLogUser(log_options, HighsLogType::kInfo, "\nObjective: "); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) - highsLogUser(log_options, HighsLogType::kInfo, " %11.4g", this->incumbent[instance].objective); + highsLogUser(log_options, HighsLogType::kInfo, " %11.4g", + this->incumbent[instance].objective); highsLogUser(log_options, HighsLogType::kInfo, "\nFinishWrite: "); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) - highsLogUser(log_options, HighsLogType::kInfo, " %11d", this->incumbent[instance].finish_write_incumbent); + highsLogUser(log_options, HighsLogType::kInfo, " %11d", + this->incumbent[instance].finish_write_incumbent); highsLogUser(log_options, HighsLogType::kInfo, "\n"); } @@ -2924,10 +2926,8 @@ void MipRace::clear() { } void MipRace::initialise(const HighsInt mip_race_concurrency, - const HighsInt my_instance_, - MipRaceRecord* record_, - const HighsLogOptions log_options_ - ) { + const HighsInt my_instance_, MipRaceRecord* record_, + const HighsLogOptions log_options_) { this->clear(); assert(mip_race_concurrency > 0); this->my_instance = my_instance_; @@ -2942,14 +2942,14 @@ HighsInt MipRace::concurrency() const { } void MipRace::update(const double objective, - const std::vector& solution) { + const std::vector& solution) { assert(this->record); this->record->update(this->my_instance, objective, solution); this->report(); } bool MipRace::newSolution(double objective, - std::vector& solution) const { + std::vector& solution) const { assert(this->record); return false; } @@ -2971,7 +2971,7 @@ void MipRace::report() const { this->record->report(this->log_options); highsLogUser(this->log_options, HighsLogType::kInfo, "LastIncumbentRead: "); for (HighsInt instance = 0; instance < this->concurrency(); instance++) - highsLogUser(this->log_options, HighsLogType::kInfo, " %11d", this->last_incumbent_read[instance]); + highsLogUser(this->log_options, HighsLogType::kInfo, " %11d", + this->last_incumbent_read[instance]); highsLogUser(this->log_options, HighsLogType::kInfo, "\n\n"); } - diff --git a/highs/mip/HighsMipSolverData.h b/highs/mip/HighsMipSolverData.h index 98e9078c305..f0be4316e29 100644 --- a/highs/mip/HighsMipSolverData.h +++ b/highs/mip/HighsMipSolverData.h @@ -302,7 +302,8 @@ struct HighsMipSolverData { HighsInt mipRaceConcurrency() const; void mipRaceUpdate(); - bool mipRaceNewSolution(double& objective_value, std::vector& solution); + bool mipRaceNewSolution(double& objective_value, + std::vector& solution); void mipRaceTerminate(); bool mipRaceTerminated() const; void mipRaceReport() const; From d0af6ecf0aaecd2a1291733b381104eea385e435 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 18 Jul 2025 13:13:01 +0100 Subject: [PATCH 17/58] Now extracting HighsMipSolverInfo --- highs/Highs.h | 1 + highs/lp_data/HStruct.h | 17 ++++++++++++++ highs/lp_data/Highs.cpp | 26 +++++++++++++++++++-- highs/lp_data/HighsInterface.cpp | 16 +++++++++++++ highs/mip/HighsMipSolverData.cpp | 33 +++++++++++++++------------ highs/mip/HighsMipSolverData.h | 39 ++++++++++++++++---------------- 6 files changed, 96 insertions(+), 36 deletions(-) diff --git a/highs/Highs.h b/highs/Highs.h index 1e6a6aa9894..471eeb6ac1f 100644 --- a/highs/Highs.h +++ b/highs/Highs.h @@ -1714,6 +1714,7 @@ class Highs { bool optionsHasHighsFiles() const; void saveHighsFiles(); void getHighsFiles(); + }; // Start of deprecated methods not in the Highs class diff --git a/highs/lp_data/HStruct.h b/highs/lp_data/HStruct.h index 3b54629832c..a9531e16780 100644 --- a/highs/lp_data/HStruct.h +++ b/highs/lp_data/HStruct.h @@ -182,4 +182,21 @@ struct HighsSimplexStats { void initialise(const HighsInt iteration_count_ = 0); }; +struct HighsMipSolverInfo { + // Data pulled from the MIP solver that are required after instance + // is deleted + HighsModelStatus modelstatus; + std::vector solution; + double solution_objective; + double bound_violation; + double integrality_violation; + double row_violation; + double dual_bound; + double primal_bound; + double gap; + int64_t node_count; + int64_t total_lp_iterations; + double primal_dual_integral; + void clear(); +}; #endif /* LP_DATA_HSTRUCT_H_ */ diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index 185161834d7..4866b397308 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -917,6 +917,8 @@ HighsStatus Highs::presolve() { return returnFromHighs(return_status); } +HighsMipSolverInfo getMipSolverInfo(const HighsMipSolver& solver); + HighsStatus Highs::run() { const bool options_had_highs_files = this->optionsHasHighsFiles(); if (options_had_highs_files) { @@ -4031,7 +4033,7 @@ HighsStatus Highs::callSolveMip() { // Create the master MIP solver instance that will exist beyond any // race HighsMipSolver solver(callback_, options_, lp, solution_); - + HighsMipSolverInfo mip_solver_info; const HighsInt mip_race_concurrency = this->options_.mip_race_concurrency; if (mip_race_concurrency > 1) { // Set up the shared memory for the MIP solver race @@ -4073,6 +4075,7 @@ HighsStatus Highs::callSolveMip() { // Run a single MIP solver solver.run(); } + mip_solver_info = getMipSolverInfo(solver); options_.log_dev_level = log_dev_level; // Set the return_status, model status and, for completeness, scaled // model status @@ -4089,7 +4092,7 @@ HighsStatus Highs::callSolveMip() { // solution from the MIP solver solution_.col_value.resize(model_.lp_.num_col_); solution_.col_value = solver.solution_; - saved_objective_and_solution_ = solver.saved_objective_and_solution_; + this->saved_objective_and_solution_ = solver.saved_objective_and_solution_; model_.lp_.a_matrix_.productQuad(solution_.row_value, solution_.col_value); solution_.value_valid = true; } else { @@ -4832,3 +4835,22 @@ void Highs::getHighsFiles() { this->options_.write_basis_file = this->files_.write_basis_file; this->files_.clear(); } + + +HighsMipSolverInfo getMipSolverInfo(const HighsMipSolver& mip_solver) { + HighsMipSolverInfo mip_solver_info; + mip_solver_info.clear(); + mip_solver_info.modelstatus = mip_solver.modelstatus_; + mip_solver_info.solution = mip_solver.solution_; + mip_solver_info.solution_objective = mip_solver.solution_objective_; + mip_solver_info.bound_violation = mip_solver.bound_violation_; + mip_solver_info.integrality_violation = mip_solver.integrality_violation_; + mip_solver_info.row_violation = mip_solver.row_violation_; + mip_solver_info.dual_bound = mip_solver.dual_bound_; + mip_solver_info.primal_bound = mip_solver.primal_bound_; + mip_solver_info.gap = mip_solver.gap_; + mip_solver_info.node_count = mip_solver.node_count_; + mip_solver_info.total_lp_iterations = mip_solver.total_lp_iterations_; + mip_solver_info.primal_dual_integral = mip_solver.primal_dual_integral_; + return mip_solver_info; +} diff --git a/highs/lp_data/HighsInterface.cpp b/highs/lp_data/HighsInterface.cpp index c962c81874e..f88539881aa 100644 --- a/highs/lp_data/HighsInterface.cpp +++ b/highs/lp_data/HighsInterface.cpp @@ -4243,3 +4243,19 @@ void HighsLinearObjective::clear() { this->rel_tolerance = 0.0; this->priority = 0; } + + +void HighsMipSolverInfo::clear() { + this->modelstatus = HighsModelStatus::kNotset; + this->solution.clear(); + this->solution_objective = -kHighsInf; + this->bound_violation = -kHighsInf; + this->integrality_violation = -kHighsInf; + this->row_violation = -kHighsInf; + this->dual_bound = -kHighsInf; + this->primal_bound = -kHighsInf; + this->gap = -kHighsInf; + this->node_count = -kHighsSize_tInf; + this->total_lp_iterations = -kHighsSize_tInf; + this->primal_dual_integral = -kHighsInf; +} diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index a2c6d0e4e1d..be2f49c2f54 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -24,6 +24,9 @@ std::string HighsMipSolverData::solutionSourceToString( if (solution_source == kSolutionSourceNone) { if (code) return " "; return "None"; + // } else if (solution_source == kSolutionSourceInitial) { + // if (code) return "0"; + // return "Initial"; } else if (solution_source == kSolutionSourceBranching) { if (code) return "B"; return "Branching"; @@ -33,15 +36,15 @@ std::string HighsMipSolverData::solutionSourceToString( } else if (solution_source == kSolutionSourceFeasibilityPump) { if (code) return "F"; return "Feasibility pump"; - } else if (solution_source == kSolutionSourceFeasibilityJump) { - if (code) return "J"; - return "Feasibility jump"; } else if (solution_source == kSolutionSourceHeuristic) { if (code) return "H"; return "Heuristic"; - // } else if (solution_source == kSolutionSourceInitial) { - // if (code) return "I"; - // return "Initial"; + } else if (solution_source == kSolutionSourceShifting) { + if (code) return "I"; + return "Shifting"; + } else if (solution_source == kSolutionSourceFeasibilityJump) { + if (code) return "J"; + return "Feasibility jump"; } else if (solution_source == kSolutionSourceSubMip) { if (code) return "L"; return "Sub-MIP"; @@ -51,12 +54,6 @@ std::string HighsMipSolverData::solutionSourceToString( } else if (solution_source == kSolutionSourceRandomizedRounding) { if (code) return "R"; return "Randomized rounding"; - } else if (solution_source == kSolutionSourceZiRound) { - if (code) return "Z"; - return "ZI Round"; - } else if (solution_source == kSolutionSourceShifting) { - if (code) return "I"; - return "Shifting"; } else if (solution_source == kSolutionSourceSolveLp) { if (code) return "S"; return "Solve LP"; @@ -66,6 +63,15 @@ std::string HighsMipSolverData::solutionSourceToString( } else if (solution_source == kSolutionSourceUnbounded) { if (code) return "U"; return "Unbounded"; + } else if (solution_source == kSolutionSourceUserSolution) { + if (code) return "X"; + return "User solution"; + } else if (solution_source == kSolutionSourceHighsSolution) { + if (code) return "Y"; + return "HiGHS solution"; + } else if (solution_source == kSolutionSourceZiRound) { + if (code) return "Z"; + return "ZI Round"; } else if (solution_source == kSolutionSourceTrivialZ) { if (code) return "z"; return "Trivial zero"; @@ -78,9 +84,6 @@ std::string HighsMipSolverData::solutionSourceToString( } else if (solution_source == kSolutionSourceTrivialP) { if (code) return "p"; return "Trivial point"; - } else if (solution_source == kSolutionSourceUserSolution) { - if (code) return "X"; - return "User solution"; } else if (solution_source == kSolutionSourceCleanup) { if (code) return " "; return ""; diff --git a/highs/mip/HighsMipSolverData.h b/highs/mip/HighsMipSolverData.h index f0be4316e29..db4c4a7832a 100644 --- a/highs/mip/HighsMipSolverData.h +++ b/highs/mip/HighsMipSolverData.h @@ -42,25 +42,26 @@ struct HighsPrimaDualIntegral { enum MipSolutionSource : int { kSolutionSourceNone = -1, kSolutionSourceMin = kSolutionSourceNone, - kSolutionSourceBranching, - kSolutionSourceCentralRounding, - kSolutionSourceFeasibilityPump, - kSolutionSourceFeasibilityJump, - kSolutionSourceHeuristic, - // kSolutionSourceInitial, - kSolutionSourceSubMip, - kSolutionSourceEmptyMip, - kSolutionSourceRandomizedRounding, - kSolutionSourceZiRound, - kSolutionSourceShifting, - kSolutionSourceSolveLp, - kSolutionSourceEvaluateNode, - kSolutionSourceUnbounded, - kSolutionSourceUserSolution, - kSolutionSourceTrivialZ, - kSolutionSourceTrivialL, - kSolutionSourceTrivialU, - kSolutionSourceTrivialP, + // kSolutionSourceInitial, // 0 + kSolutionSourceBranching, // B + kSolutionSourceCentralRounding, // C + kSolutionSourceFeasibilityPump, // F + kSolutionSourceHeuristic, // H + kSolutionSourceShifting, // I + kSolutionSourceFeasibilityJump, // J + kSolutionSourceSubMip, // L + kSolutionSourceEmptyMip, // P + kSolutionSourceRandomizedRounding, // R + kSolutionSourceSolveLp, // S + kSolutionSourceEvaluateNode, // T + kSolutionSourceUnbounded, // U + kSolutionSourceUserSolution, // X + kSolutionSourceHighsSolution, // Y + kSolutionSourceZiRound, // Z + kSolutionSourceTrivialL, // l + kSolutionSourceTrivialP, // p + kSolutionSourceTrivialU, // u + kSolutionSourceTrivialZ, // z kSolutionSourceCleanup, kSolutionSourceCount }; From 39456e879f084354ad3685eb35cd7d84332d8a3e Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 18 Jul 2025 15:42:08 +0100 Subject: [PATCH 18/58] WIP --- check/TestMipSolver.cpp | 3 ++- highs/lp_data/Highs.cpp | 45 +++++++++++++++++++++++++++-------------- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index a3787a599ad..3c0141b90d8 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -1008,7 +1008,8 @@ TEST_CASE("mip-race", "[highs_test_mip_solver]") { std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; Highs h; // h.setOptionValue("output_flag", dev_run); - h.setOptionValue("mip_race_concurrency", 2); + h.setOptionValue("mip_race_concurrency", 4); REQUIRE(h.readModel(model_file) == HighsStatus::kOk); REQUIRE(h.run() == HighsStatus::kOk); } + diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index 4866b397308..ee1f83dd19c 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -4048,6 +4048,7 @@ HighsStatus Highs::callSolveMip() { worker_options.log_to_console = false; worker_options.setLogOptions(); // Race the MIP solver! + std::vector worker_info(mip_race_concurrency); highs::parallel::for_each( 0, mip_race_concurrency, [&](HighsInt start, HighsInt end) { for (HighsInt instance = start; instance < end; instance++) { @@ -4056,6 +4057,7 @@ HighsStatus Highs::callSolveMip() { &mip_race_record, options_.log_options); solver.run(); + mip_solver_info = getMipSolverInfo(solver); } else { // Use the instance ID as an offset to the random seed worker_options.random_seed = options_.random_seed + instance; @@ -4068,30 +4070,43 @@ HighsStatus Highs::callSolveMip() { &mip_race_record, worker_options.log_options); worker.run(); + worker_info[instance] = getMipSolverInfo(worker); } } }); + // Report on the solver and workers, and identify which has won! + HighsInt winning_instance = -1; + highsLogUser(options_.log_options, HighsLogType::kInfo, + "MIP race results:\n"); + for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) { + const HighsMipSolverInfo& solver_info = instance == 0 ? mip_solver_info : worker_info[instance]; + HighsModelStatus instance_model_status = solver_info.modelstatus; + highsLogUser(options_.log_options, HighsLogType::kInfo, + " Solver %d has best objective %.12g, gap %6.2f, and status %s\n", + int(instance), solver_info.solution_objective, 1e2 * solver_info.gap, + modelStatusToString(instance_model_status).c_str()); + } } else { // Run a single MIP solver solver.run(); + mip_solver_info = getMipSolverInfo(solver); } - mip_solver_info = getMipSolverInfo(solver); 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_; + highsStatusFromHighsModelStatus(mip_solver_info.modelstatus); + model_status_ = mip_solver_info.modelstatus; // Extract the solution - if (solver.solution_objective_ != kHighsInf) { + if (mip_solver_info.solution_objective != kHighsInf) { // There is a primal solution - HighsInt solver_solution_size = solver.solution_.size(); + HighsInt solver_solution_size = mip_solver_info.solution.size(); assert(solver_solution_size >= lp.num_col_); // If the original model has semi-variables, its solution is // (still) given by the first model_.lp_.num_col_ entries of the // solution from the MIP solver solution_.col_value.resize(model_.lp_.num_col_); - solution_.col_value = solver.solution_; + solution_.col_value = mip_solver_info.solution; this->saved_objective_and_solution_ = solver.saved_objective_and_solution_; model_.lp_.a_matrix_.productQuad(solution_.row_value, solution_.col_value); solution_.value_valid = true; @@ -4111,7 +4126,7 @@ HighsStatus Highs::callSolveMip() { // There is no basis: should be so by default assert(!basis_.valid); // Get the objective and any KKT failures - info_.objective_function_value = solver.solution_objective_; + info_.objective_function_value = mip_solver_info.solution_objective; // Remember to judge primal feasibility according to // mip_feasibility_tolerance, so take a copy of the original // value... @@ -4120,13 +4135,13 @@ HighsStatus Highs::callSolveMip() { // NB getKktFailures sets the primal and dual solution status getKktFailures(options_, model_, solution_, basis_, info_); // Set the MIP-specific values of info_ - info_.mip_node_count = solver.node_count_; - info_.mip_dual_bound = solver.dual_bound_; - info_.mip_gap = solver.gap_; - info_.primal_dual_integral = solver.primal_dual_integral_; + info_.mip_node_count = mip_solver_info.node_count; + info_.mip_dual_bound = mip_solver_info.dual_bound; + info_.mip_gap = mip_solver_info.gap; + info_.primal_dual_integral = mip_solver_info.primal_dual_integral; // Get the number of LP iterations, avoiding overflow if the int64_t // value is too large - int64_t mip_total_lp_iterations = solver.total_lp_iterations_; + int64_t mip_total_lp_iterations = mip_solver_info.total_lp_iterations; info_.simplex_iteration_count = mip_total_lp_iterations > kHighsIInf ? -1 : HighsInt(mip_total_lp_iterations); @@ -4134,9 +4149,9 @@ HighsStatus Highs::callSolveMip() { if (model_status_ == HighsModelStatus::kOptimal) return_status = checkOptimality("MIP"); // Overwrite max infeasibility to include integrality if there is a solution - if (solver.solution_objective_ != kHighsInf) { + if (mip_solver_info.solution_objective != kHighsInf) { const double mip_max_bound_violation = - std::max(solver.row_violation_, solver.bound_violation_); + std::max(mip_solver_info.row_violation, mip_solver_info.bound_violation); const double delta_max_bound_violation = std::abs(mip_max_bound_violation - info_.max_primal_infeasibility); // Possibly report a mis-match between the max bound violation @@ -4148,7 +4163,7 @@ HighsStatus Highs::callSolveMip() { "(%10.4g); Difference of %10.4g\n", mip_max_bound_violation, info_.max_primal_infeasibility, delta_max_bound_violation); - info_.max_integrality_violation = solver.integrality_violation_; + info_.max_integrality_violation = mip_solver_info.integrality_violation; if (info_.max_integrality_violation > options_.mip_feasibility_tolerance) { info_.primal_solution_status = kSolutionStatusInfeasible; assert(model_status_ == HighsModelStatus::kInfeasible); From 5a5b30ab02738a89bd0116903113109a74df63b7 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 18 Jul 2025 22:29:06 +0100 Subject: [PATCH 19/58] Now to start reading incumbents from other MIP solver instances --- highs/lp_data/Highs.cpp | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index ee1f83dd19c..6a2fce32ad3 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -4076,16 +4076,34 @@ HighsStatus Highs::callSolveMip() { }); // Report on the solver and workers, and identify which has won! HighsInt winning_instance = -1; + HighsModelStatus winning_model_status = HighsModelStatus::kNotset; highsLogUser(options_.log_options, HighsLogType::kInfo, "MIP race results:\n"); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) { const HighsMipSolverInfo& solver_info = instance == 0 ? mip_solver_info : worker_info[instance]; HighsModelStatus instance_model_status = solver_info.modelstatus; highsLogUser(options_.log_options, HighsLogType::kInfo, - " Solver %d has best objective %.12g, gap %6.2f, and status %s\n", + " Solver %d has best objective %15.8g, gap %6.2f\%, and status %s\n", int(instance), solver_info.solution_objective, 1e2 * solver_info.gap, modelStatusToString(instance_model_status).c_str()); + if (instance_model_status != HighsModelStatus::kHighsInterrupt) { + // Definitive status for this instance, so check compatibility + // with any current winning model status + if (winning_model_status != HighsModelStatus::kNotset) { + if (winning_model_status != instance_model_status) { + highsLogUser(options_.log_options, HighsLogType::kError, + "MIP race: conflict between status \"%s\" for instance %d and status \"%s\" for instance %d\n", + modelStatusToString(winning_model_status).c_str(), int(winning_instance), + modelStatusToString(instance_model_status).c_str(), int(instance)); + } + } else { + winning_model_status = instance_model_status; + winning_instance = instance; + } + } } + if (winning_instance > 0) + mip_solver_info = worker_info[winning_instance]; } else { // Run a single MIP solver solver.run(); @@ -4101,6 +4119,10 @@ HighsStatus Highs::callSolveMip() { if (mip_solver_info.solution_objective != kHighsInf) { // There is a primal solution HighsInt solver_solution_size = mip_solver_info.solution.size(); + const bool solver_solution_size_ok = solver_solution_size >= lp.num_col_; + if (!solver_solution_size) + highsLogUser(options_.log_options, HighsLogType::kError, + "After MIP race, size of solution is %d < %d = lp.num_col_\n", int(solver_solution_size), int(lp.num_col_)); assert(solver_solution_size >= lp.num_col_); // If the original model has semi-variables, its solution is // (still) given by the first model_.lp_.num_col_ entries of the From 14e2a975ce96b1b5a39316b0364239c170364ab6 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sat, 19 Jul 2025 17:09:31 +0100 Subject: [PATCH 20/58] HiGHS solution has been read --- check/TestMipSolver.cpp | 5 ++- highs/lp_data/HighsOptions.h | 7 ++++ highs/mip/HighsMipSolver.h | 10 +++-- highs/mip/HighsMipSolverData.cpp | 64 +++++++++++++++++++++----------- highs/mip/HighsMipSolverData.h | 5 ++- 5 files changed, 61 insertions(+), 30 deletions(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index 3c0141b90d8..16acaac400a 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -1003,12 +1003,13 @@ TEST_CASE("issue-2432", "[highs_test_mip_solver]") { } TEST_CASE("mip-race", "[highs_test_mip_solver]") { - const std::string model = "flugpl"; + const std::string model = "bell5";//"flugpl"; const std::string model_file = std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; Highs h; // h.setOptionValue("output_flag", dev_run); - h.setOptionValue("mip_race_concurrency", 4); + h.setOptionValue("mip_race_concurrency", 2); + // h.setOptionValue("mip_race_read_solutions", false); REQUIRE(h.readModel(model_file) == HighsStatus::kOk); REQUIRE(h.run() == HighsStatus::kOk); } diff --git a/highs/lp_data/HighsOptions.h b/highs/lp_data/HighsOptions.h index 731deadd117..5b9a31481bf 100644 --- a/highs/lp_data/HighsOptions.h +++ b/highs/lp_data/HighsOptions.h @@ -425,6 +425,7 @@ struct HighsOptionsStruct { bool mip_detect_symmetry; bool mip_allow_restart; HighsInt mip_race_concurrency; + bool mip_race_read_solutions; HighsInt mip_max_nodes; HighsInt mip_max_stall_nodes; HighsInt mip_max_start_nodes; @@ -577,6 +578,7 @@ struct HighsOptionsStruct { mip_detect_symmetry(false), mip_allow_restart(false), mip_race_concurrency(0), + mip_race_read_solutions(false), mip_max_nodes(0), mip_max_stall_nodes(0), mip_max_start_nodes(0), @@ -1024,6 +1026,11 @@ class HighsOptions : public HighsOptionsStruct { advanced, &mip_race_concurrency, 0, 0, kHighsIInf); records.push_back(record_int); + record_bool = new OptionRecordBool("mip_race_read_solutions", + "Whether the MIP races should read other racers' solutions", + advanced, &mip_race_read_solutions, true); + records.push_back(record_bool); + record_int = new OptionRecordInt("mip_max_nodes", "MIP solver max number of nodes", advanced, &mip_max_nodes, 0, kHighsIInf, kHighsIInf); diff --git a/highs/mip/HighsMipSolver.h b/highs/mip/HighsMipSolver.h index cde6c23a5cf..6668bc50ec3 100644 --- a/highs/mip/HighsMipSolver.h +++ b/highs/mip/HighsMipSolver.h @@ -19,15 +19,17 @@ struct HighsPseudocostInitialization; class HighsCliqueTable; class HighsImplications; +const HighsInt kMipRaceNoSolution = -1; + struct MipRaceIncumbent { - HighsInt start_write_incumbent = -1; - HighsInt finish_write_incumbent = -1; + HighsInt start_write_incumbent = kMipRaceNoSolution; + HighsInt finish_write_incumbent = kMipRaceNoSolution; double objective = -kHighsInf; std::vector solution; void clear(); void initialise(const HighsInt num_col); void update(const double objective, const std::vector& solution); - bool readOk(double& objective_, std::vector& solution_) const; + HighsInt read(double& objective_, std::vector& solution_) const; }; struct MipRaceRecord { @@ -52,7 +54,7 @@ struct MipRace { const HighsLogOptions log_options_); HighsInt concurrency() const; void update(const double objective, const std::vector& solution); - bool newSolution(double objective, std::vector& solution) const; + HighsInt newSolution(const HighsInt instance, double objective, std::vector& solution) const; void terminate(); bool terminated() const; void report() const; diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index be2f49c2f54..1556a959fb9 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -2621,6 +2621,7 @@ bool HighsMipSolverData::interruptFromCallbackWithData( void HighsMipSolverData::queryExternalSolution( const double mipsolver_objective_value, const ExternalMipSolutionQueryOrigin external_solution_query_origin) { + assert(!mipsolver.submip); HighsCallback* callback = mipsolver.callback_; const bool use_callback = callback->user_callback && callback->active[kCallbackMipUserSolution]; @@ -2664,11 +2665,29 @@ void HighsMipSolverData::queryExternalSolution( is_user_solution); } } + if (!mipsolver.options_mip_->mip_race_read_solutions) return; + MipRace& mip_race = mipsolver.mip_race_; + if (!mip_race.record) return; + double instance_solution_objective_value; + std::vector instance_solution; + for (HighsInt instance = 0; instance < mip_race.concurrency(); instance++) { + if (instance == mip_race.my_instance) continue; + HighsInt read_incumbent = mip_race.newSolution(instance, instance_solution_objective_value, instance_solution); + if (read_incumbent < 0) continue; + if (read_incumbent <= mip_race.last_incumbent_read[instance]) continue; + // Have read a new incumbent + std::vector reduced_instance_solution; + reduced_instance_solution = + postSolveStack.getReducedPrimalSolution(instance_solution); + addIncumbent(reduced_instance_solution, instance_solution_objective_value, + kSolutionSourceHighsSolution); + + } } HighsInt HighsMipSolverData::mipRaceConcurrency() const { - if (!mipsolver.mip_race_.record) return; assert(!mipsolver.submip); + if (!mipsolver.mip_race_.record) return; return mipsolver.mip_race_.concurrency(); } @@ -2679,22 +2698,23 @@ void HighsMipSolverData::mipRaceUpdate() { mipsolver.solution_); } -bool HighsMipSolverData::mipRaceNewSolution(double& objective_value, - std::vector& solution) { - if (!mipsolver.mip_race_.record) return false; +HighsInt HighsMipSolverData::mipRaceNewSolution(const HighsInt instance, + double& objective_value, + std::vector& solution) { assert(!mipsolver.submip); - return false; + if (!mipsolver.mip_race_.record) return kMipRaceNoSolution; + return mipsolver.mip_race_.newSolution(instance, objective_value, solution); } void HighsMipSolverData::mipRaceTerminate() { - if (!mipsolver.mip_race_.record) return; assert(!mipsolver.submip); + if (!mipsolver.mip_race_.record) return; mipsolver.mip_race_.terminate(); } bool HighsMipSolverData::mipRaceTerminated() const { - if (!mipsolver.mip_race_.record) return false; assert(!mipsolver.submip); + if (!mipsolver.mip_race_.record) return false; return mipsolver.mip_race_.terminated(); } @@ -2840,8 +2860,8 @@ void HighsMipSolverData::updatePrimalDualIntegral(const double from_lower_bound, void HighsPrimaDualIntegral::initialise() { this->value = -kHighsInf; } void MipRaceIncumbent::clear() { - this->start_write_incumbent = -1; - this->finish_write_incumbent = -1; + this->start_write_incumbent = kMipRaceNoSolution; + this->finish_write_incumbent = kMipRaceNoSolution; this->objective = -kHighsInf; this->solution.clear(); } @@ -2861,17 +2881,17 @@ void MipRaceIncumbent::update(const double objective_, assert(this->start_write_incumbent == this->finish_write_incumbent); } -bool MipRaceIncumbent::readOk(double& objective_, - std::vector& solution_) const { +HighsInt MipRaceIncumbent::read(double& objective_, + std::vector& solution_) const { const HighsInt start_write_incumbent = this->start_write_incumbent; assert(this->finish_write_incumbent <= start_write_incumbent); // If a write call has not completed, return failure - if (this->finish_write_incumbent < start_write_incumbent) return false; + if (this->finish_write_incumbent < start_write_incumbent) return kMipRaceNoSolution; // finish_write_incumbent = start_write_incumbent so start reading objective_ = this->objective; solution_ = this->solution; // Read is OK if no new write has started - return this->start_write_incumbent == start_write_incumbent; + return this->start_write_incumbent == start_write_incumbent ? start_write_incumbent : kMipRaceNoSolution; } void MipRaceRecord::clear() { @@ -2902,22 +2922,22 @@ void MipRaceRecord::report(const HighsLogOptions log_options) const { HighsInt mip_race_concurrency = this->concurrency(); highsLogUser(log_options, HighsLogType::kInfo, "\nMipRaceRecord: "); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) - highsLogUser(log_options, HighsLogType::kInfo, " %11d", int(instance)); + highsLogUser(log_options, HighsLogType::kInfo, " %16d", int(instance)); highsLogUser(log_options, HighsLogType::kInfo, "\nTerminated: "); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) - highsLogUser(log_options, HighsLogType::kInfo, " %11s", + highsLogUser(log_options, HighsLogType::kInfo, " %16s", this->terminated[instance] ? "T" : "F"); highsLogUser(log_options, HighsLogType::kInfo, "\nStartWrite: "); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) - highsLogUser(log_options, HighsLogType::kInfo, " %11d", + highsLogUser(log_options, HighsLogType::kInfo, " %16d", this->incumbent[instance].start_write_incumbent); highsLogUser(log_options, HighsLogType::kInfo, "\nObjective: "); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) - highsLogUser(log_options, HighsLogType::kInfo, " %11.4g", + highsLogUser(log_options, HighsLogType::kInfo, " %16.8g", this->incumbent[instance].objective); highsLogUser(log_options, HighsLogType::kInfo, "\nFinishWrite: "); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) - highsLogUser(log_options, HighsLogType::kInfo, " %11d", + highsLogUser(log_options, HighsLogType::kInfo, " %16d", this->incumbent[instance].finish_write_incumbent); highsLogUser(log_options, HighsLogType::kInfo, "\n"); } @@ -2936,7 +2956,7 @@ void MipRace::initialise(const HighsInt mip_race_concurrency, this->my_instance = my_instance_; this->record = record_; this->log_options = log_options_; - this->last_incumbent_read.assign(mip_race_concurrency, -1); + this->last_incumbent_read.assign(mip_race_concurrency, kMipRaceNoSolution); } HighsInt MipRace::concurrency() const { @@ -2951,10 +2971,10 @@ void MipRace::update(const double objective, this->report(); } -bool MipRace::newSolution(double objective, +HighsInt MipRace::newSolution(const HighsInt instance, double objective, std::vector& solution) const { assert(this->record); - return false; + return this->record->incumbent[instance].read(objective, solution); } void MipRace::terminate() { @@ -2974,7 +2994,7 @@ void MipRace::report() const { this->record->report(this->log_options); highsLogUser(this->log_options, HighsLogType::kInfo, "LastIncumbentRead: "); for (HighsInt instance = 0; instance < this->concurrency(); instance++) - highsLogUser(this->log_options, HighsLogType::kInfo, " %11d", + highsLogUser(this->log_options, HighsLogType::kInfo, " %16d", this->last_incumbent_read[instance]); highsLogUser(this->log_options, HighsLogType::kInfo, "\n\n"); } diff --git a/highs/mip/HighsMipSolverData.h b/highs/mip/HighsMipSolverData.h index db4c4a7832a..44800ff4c25 100644 --- a/highs/mip/HighsMipSolverData.h +++ b/highs/mip/HighsMipSolverData.h @@ -303,8 +303,9 @@ struct HighsMipSolverData { HighsInt mipRaceConcurrency() const; void mipRaceUpdate(); - bool mipRaceNewSolution(double& objective_value, - std::vector& solution); + HighsInt mipRaceNewSolution(const HighsInt instance, + double& objective_value, + std::vector& solution); void mipRaceTerminate(); bool mipRaceTerminated() const; void mipRaceReport() const; From 6e65dbea6dc03f240c35600760cc2dcefa0c3777 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sat, 19 Jul 2025 17:58:27 +0100 Subject: [PATCH 21/58] Reading HiGHS solution; different random_seed; all logging still to file for last worker; LastIncumbentRead not being set --- highs/lp_data/Highs.cpp | 36 ++++++++++++++++++++++---------- highs/mip/HighsMipSolver.cpp | 1 + highs/mip/HighsMipSolverData.cpp | 12 +++++------ 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index 6a2fce32ad3..4434b9bcd36 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -4043,12 +4043,31 @@ HighsStatus Highs::callSolveMip() { // Don't allow callbacks for workers HighsCallback worker_callback = callback_; worker_callback.clear(); - HighsOptions worker_options = options_; - // No workers log to console - worker_options.log_to_console = false; - worker_options.setLogOptions(); // Race the MIP solver! + highsLogUser(options_.log_options, HighsLogType::kInfo, + "Starting MIP race with %d instances: performance is non-deterministic!\n", int(mip_race_concurrency)); + // Define the HighsMipSolverInfo record for each worker std::vector worker_info(mip_race_concurrency); + // Set up the vector of options settings for workers + std::vector worker_options; + // std::vector worker; + for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) { + HighsOptions instance_options = options_; + // No workers log to console + instance_options.log_to_console = false; + instance_options.setLogOptions(); + // Use the instance ID as an offset to the random seed + instance_options.random_seed = options_.random_seed + instance; + std::string worker_log_file = + "mip_worker" + std::to_string(instance) + ".log"; + highsOpenLogFile(instance_options, worker_log_file); + worker_options.push_back(instance_options); + /* + HighsMipSolver worker_instance(worker_callback, worker_options[instance], lp, + solution_); + worker.push_back(&worker_instance); + */ + } highs::parallel::for_each( 0, mip_race_concurrency, [&](HighsInt start, HighsInt end) { for (HighsInt instance = start; instance < end; instance++) { @@ -4059,16 +4078,11 @@ HighsStatus Highs::callSolveMip() { solver.run(); mip_solver_info = getMipSolverInfo(solver); } else { - // Use the instance ID as an offset to the random seed - worker_options.random_seed = options_.random_seed + instance; - std::string worker_log_file = - "mip_worker" + std::to_string(instance) + ".log"; - highsOpenLogFile(worker_options, worker_log_file); - HighsMipSolver worker(worker_callback, worker_options, lp, + HighsMipSolver worker(worker_callback, worker_options[instance], lp, solution_); worker.mip_race_.initialise(mip_race_concurrency, instance, &mip_race_record, - worker_options.log_options); + worker_options[instance].log_options); worker.run(); worker_info[instance] = getMipSolverInfo(worker); } diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index 4771ca40945..f98d7cfbca3 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -67,6 +67,7 @@ HighsMipSolver::HighsMipSolver(HighsCallback& callback, HighsMipSolver::~HighsMipSolver() = default; void HighsMipSolver::run() { + if (!submip) printf("HighsMipSolver::run() with random_seed = %d\n", int(options_mip_->random_seed)); modelstatus_ = HighsModelStatus::kNotset; if (submip) { diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index 1556a959fb9..65fb0c1c4ff 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -2922,22 +2922,22 @@ void MipRaceRecord::report(const HighsLogOptions log_options) const { HighsInt mip_race_concurrency = this->concurrency(); highsLogUser(log_options, HighsLogType::kInfo, "\nMipRaceRecord: "); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) - highsLogUser(log_options, HighsLogType::kInfo, " %16d", int(instance)); + highsLogUser(log_options, HighsLogType::kInfo, " %20d", int(instance)); highsLogUser(log_options, HighsLogType::kInfo, "\nTerminated: "); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) - highsLogUser(log_options, HighsLogType::kInfo, " %16s", + highsLogUser(log_options, HighsLogType::kInfo, " %20s", this->terminated[instance] ? "T" : "F"); highsLogUser(log_options, HighsLogType::kInfo, "\nStartWrite: "); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) - highsLogUser(log_options, HighsLogType::kInfo, " %16d", + highsLogUser(log_options, HighsLogType::kInfo, " %20d", this->incumbent[instance].start_write_incumbent); highsLogUser(log_options, HighsLogType::kInfo, "\nObjective: "); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) - highsLogUser(log_options, HighsLogType::kInfo, " %16.8g", + highsLogUser(log_options, HighsLogType::kInfo, " %20.12g", this->incumbent[instance].objective); highsLogUser(log_options, HighsLogType::kInfo, "\nFinishWrite: "); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) - highsLogUser(log_options, HighsLogType::kInfo, " %16d", + highsLogUser(log_options, HighsLogType::kInfo, " %20d", this->incumbent[instance].finish_write_incumbent); highsLogUser(log_options, HighsLogType::kInfo, "\n"); } @@ -2994,7 +2994,7 @@ void MipRace::report() const { this->record->report(this->log_options); highsLogUser(this->log_options, HighsLogType::kInfo, "LastIncumbentRead: "); for (HighsInt instance = 0; instance < this->concurrency(); instance++) - highsLogUser(this->log_options, HighsLogType::kInfo, " %16d", + highsLogUser(this->log_options, HighsLogType::kInfo, " %20d", this->last_incumbent_read[instance]); highsLogUser(this->log_options, HighsLogType::kInfo, "\n\n"); } From 54603c112fc779effb048d16824f78cb018ff527 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sun, 20 Jul 2025 09:41:00 +0100 Subject: [PATCH 22/58] Now only reading if incumbent is newer than last read --- highs/mip/HighsMipSolver.h | 5 +++-- highs/mip/HighsMipSolverData.cpp | 34 +++++++++++++++++++++----------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/highs/mip/HighsMipSolver.h b/highs/mip/HighsMipSolver.h index 6668bc50ec3..ff727f343ac 100644 --- a/highs/mip/HighsMipSolver.h +++ b/highs/mip/HighsMipSolver.h @@ -29,7 +29,8 @@ struct MipRaceIncumbent { void clear(); void initialise(const HighsInt num_col); void update(const double objective, const std::vector& solution); - HighsInt read(double& objective_, std::vector& solution_) const; + HighsInt read(const HighsInt last_incumbent_read, + double& objective_, std::vector& solution_) const; }; struct MipRaceRecord { @@ -54,7 +55,7 @@ struct MipRace { const HighsLogOptions log_options_); HighsInt concurrency() const; void update(const double objective, const std::vector& solution); - HighsInt newSolution(const HighsInt instance, double objective, std::vector& solution) const; + bool newSolution(const HighsInt instance, double objective, std::vector& solution); void terminate(); bool terminated() const; void report() const; diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index 65fb0c1c4ff..07aa6bd4656 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -2672,9 +2672,7 @@ void HighsMipSolverData::queryExternalSolution( std::vector instance_solution; for (HighsInt instance = 0; instance < mip_race.concurrency(); instance++) { if (instance == mip_race.my_instance) continue; - HighsInt read_incumbent = mip_race.newSolution(instance, instance_solution_objective_value, instance_solution); - if (read_incumbent < 0) continue; - if (read_incumbent <= mip_race.last_incumbent_read[instance]) continue; + if (!mip_race.newSolution(instance, instance_solution_objective_value, instance_solution)) continue; // Have read a new incumbent std::vector reduced_instance_solution; reduced_instance_solution = @@ -2881,10 +2879,12 @@ void MipRaceIncumbent::update(const double objective_, assert(this->start_write_incumbent == this->finish_write_incumbent); } -HighsInt MipRaceIncumbent::read(double& objective_, - std::vector& solution_) const { +HighsInt MipRaceIncumbent::read(const HighsInt last_incumbent_read, + double& objective_, + std::vector& solution_) const { const HighsInt start_write_incumbent = this->start_write_incumbent; assert(this->finish_write_incumbent <= start_write_incumbent); + if (start_write_incumbent < last_incumbent_read) return kMipRaceNoSolution; // If a write call has not completed, return failure if (this->finish_write_incumbent < start_write_incumbent) return kMipRaceNoSolution; // finish_write_incumbent = start_write_incumbent so start reading @@ -2971,10 +2971,17 @@ void MipRace::update(const double objective, this->report(); } -HighsInt MipRace::newSolution(const HighsInt instance, double objective, - std::vector& solution) const { +bool MipRace::newSolution(const HighsInt instance, double objective, + std::vector& solution) { assert(this->record); - return this->record->incumbent[instance].read(objective, solution); + HighsInt new_incumbent_read = + this->record->incumbent[instance].read(this->last_incumbent_read[instance], + objective, solution); + if (new_incumbent_read != kMipRaceNoSolution) { + this->last_incumbent_read[instance] = new_incumbent_read; + return true; + } + return false; } void MipRace::terminate() { @@ -2993,8 +3000,13 @@ void MipRace::report() const { assert(this->record); this->record->report(this->log_options); highsLogUser(this->log_options, HighsLogType::kInfo, "LastIncumbentRead: "); - for (HighsInt instance = 0; instance < this->concurrency(); instance++) - highsLogUser(this->log_options, HighsLogType::kInfo, " %20d", - this->last_incumbent_read[instance]); + for (HighsInt instance = 0; instance < this->concurrency(); instance++) { + if (instance == this->my_instance) { + highsLogUser(this->log_options, HighsLogType::kInfo, " %20s", ""); + } else { + highsLogUser(this->log_options, HighsLogType::kInfo, " %20d", + this->last_incumbent_read[instance]); + } + } highsLogUser(this->log_options, HighsLogType::kInfo, "\n\n"); } From ffe29d98427a7539f8fa71e05d08aba19b74c884 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sun, 20 Jul 2025 17:12:39 +0100 Subject: [PATCH 23/58] Prototype concurrent MIP solver --- check/TestMipSolver.cpp | 9 ++-- highs/Highs.h | 1 - highs/lp_data/Highs.cpp | 80 ++++++++++++++++++-------------- highs/lp_data/HighsInterface.cpp | 1 - highs/lp_data/HighsOptions.h | 9 ++-- highs/mip/HighsMipSolver.cpp | 4 +- highs/mip/HighsMipSolver.h | 7 +-- highs/mip/HighsMipSolverData.cpp | 35 +++++++------- highs/mip/HighsMipSolverData.h | 43 +++++++++-------- 9 files changed, 103 insertions(+), 86 deletions(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index 16acaac400a..763064dc65e 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -1003,14 +1003,15 @@ TEST_CASE("issue-2432", "[highs_test_mip_solver]") { } TEST_CASE("mip-race", "[highs_test_mip_solver]") { - const std::string model = "bell5";//"flugpl"; + const std::string model = + "fiball"; //"neos-3381206-awhea"; //bell5";//"flugpl"; const std::string model_file = - std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; + // std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; + "/srv/miplib2017/" + model + ".mps.gz"; Highs h; // h.setOptionValue("output_flag", dev_run); - h.setOptionValue("mip_race_concurrency", 2); + h.setOptionValue("mip_race_concurrency", 4); // h.setOptionValue("mip_race_read_solutions", false); REQUIRE(h.readModel(model_file) == HighsStatus::kOk); REQUIRE(h.run() == HighsStatus::kOk); } - diff --git a/highs/Highs.h b/highs/Highs.h index 471eeb6ac1f..1e6a6aa9894 100644 --- a/highs/Highs.h +++ b/highs/Highs.h @@ -1714,7 +1714,6 @@ class Highs { bool optionsHasHighsFiles() const; void saveHighsFiles(); void getHighsFiles(); - }; // Start of deprecated methods not in the Highs class diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index 4434b9bcd36..d9a134e7edf 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -4045,12 +4045,15 @@ HighsStatus Highs::callSolveMip() { worker_callback.clear(); // Race the MIP solver! highsLogUser(options_.log_options, HighsLogType::kInfo, - "Starting MIP race with %d instances: performance is non-deterministic!\n", int(mip_race_concurrency)); + "Starting MIP race with %d instances: performance is " + "non-deterministic!\n", + int(mip_race_concurrency)); // Define the HighsMipSolverInfo record for each worker std::vector worker_info(mip_race_concurrency); // Set up the vector of options settings for workers std::vector worker_options; // std::vector worker; + std::vector mip_time(mip_race_concurrency); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) { HighsOptions instance_options = options_; // No workers log to console @@ -4059,13 +4062,12 @@ HighsStatus Highs::callSolveMip() { // Use the instance ID as an offset to the random seed instance_options.random_seed = options_.random_seed + instance; std::string worker_log_file = - "mip_worker" + std::to_string(instance) + ".log"; + "mip_worker" + std::to_string(instance) + ".log"; highsOpenLogFile(instance_options, worker_log_file); worker_options.push_back(instance_options); /* - HighsMipSolver worker_instance(worker_callback, worker_options[instance], lp, - solution_); - worker.push_back(&worker_instance); + HighsMipSolver worker_instance(worker_callback, worker_options[instance], + lp, solution_); worker.push_back(&worker_instance); */ } highs::parallel::for_each( @@ -4075,16 +4077,20 @@ HighsStatus Highs::callSolveMip() { solver.mip_race_.initialise(mip_race_concurrency, instance, &mip_race_record, options_.log_options); + mip_time[instance] = -timer_.read(); solver.run(); - mip_solver_info = getMipSolverInfo(solver); + mip_time[instance] += timer_.read(); + mip_solver_info = getMipSolverInfo(solver); } else { - HighsMipSolver worker(worker_callback, worker_options[instance], lp, - solution_); + HighsMipSolver worker(worker_callback, worker_options[instance], + lp, solution_); worker.mip_race_.initialise(mip_race_concurrency, instance, &mip_race_record, worker_options[instance].log_options); + mip_time[instance] = -timer_.read(); worker.run(); - worker_info[instance] = getMipSolverInfo(worker); + mip_time[instance] += timer_.read(); + worker_info[instance] = getMipSolverInfo(worker); } } }); @@ -4092,32 +4098,37 @@ HighsStatus Highs::callSolveMip() { HighsInt winning_instance = -1; HighsModelStatus winning_model_status = HighsModelStatus::kNotset; highsLogUser(options_.log_options, HighsLogType::kInfo, - "MIP race results:\n"); + "MIP race results:\n"); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) { - const HighsMipSolverInfo& solver_info = instance == 0 ? mip_solver_info : worker_info[instance]; + const HighsMipSolverInfo& solver_info = + instance == 0 ? mip_solver_info : worker_info[instance]; HighsModelStatus instance_model_status = solver_info.modelstatus; highsLogUser(options_.log_options, HighsLogType::kInfo, - " Solver %d has best objective %15.8g, gap %6.2f\%, and status %s\n", - int(instance), solver_info.solution_objective, 1e2 * solver_info.gap, - modelStatusToString(instance_model_status).c_str()); + " Solver %d has best objective %15.8g, gap %6.2f\% (time " + "= %6.2f), and status %s\n", + int(instance), solver_info.solution_objective, + 1e2 * solver_info.gap, mip_time[instance], + modelStatusToString(instance_model_status).c_str()); if (instance_model_status != HighsModelStatus::kHighsInterrupt) { - // Definitive status for this instance, so check compatibility - // with any current winning model status - if (winning_model_status != HighsModelStatus::kNotset) { - if (winning_model_status != instance_model_status) { - highsLogUser(options_.log_options, HighsLogType::kError, - "MIP race: conflict between status \"%s\" for instance %d and status \"%s\" for instance %d\n", - modelStatusToString(winning_model_status).c_str(), int(winning_instance), - modelStatusToString(instance_model_status).c_str(), int(instance)); - } - } else { - winning_model_status = instance_model_status; - winning_instance = instance; - } + // Definitive status for this instance, so check compatibility + // with any current winning model status + if (winning_model_status != HighsModelStatus::kNotset) { + if (winning_model_status != instance_model_status) { + highsLogUser(options_.log_options, HighsLogType::kError, + "MIP race: conflict between status \"%s\" for " + "instance %d and status \"%s\" for instance %d\n", + modelStatusToString(winning_model_status).c_str(), + int(winning_instance), + modelStatusToString(instance_model_status).c_str(), + int(instance)); + } + } else { + winning_model_status = instance_model_status; + winning_instance = instance; + } } } - if (winning_instance > 0) - mip_solver_info = worker_info[winning_instance]; + if (winning_instance > 0) mip_solver_info = worker_info[winning_instance]; } else { // Run a single MIP solver solver.run(); @@ -4135,8 +4146,10 @@ HighsStatus Highs::callSolveMip() { HighsInt solver_solution_size = mip_solver_info.solution.size(); const bool solver_solution_size_ok = solver_solution_size >= lp.num_col_; if (!solver_solution_size) - highsLogUser(options_.log_options, HighsLogType::kError, - "After MIP race, size of solution is %d < %d = lp.num_col_\n", int(solver_solution_size), int(lp.num_col_)); + highsLogUser( + options_.log_options, HighsLogType::kError, + "After MIP race, size of solution is %d < %d = lp.num_col_\n", + int(solver_solution_size), int(lp.num_col_)); assert(solver_solution_size >= lp.num_col_); // If the original model has semi-variables, its solution is // (still) given by the first model_.lp_.num_col_ entries of the @@ -4186,8 +4199,8 @@ HighsStatus Highs::callSolveMip() { return_status = checkOptimality("MIP"); // Overwrite max infeasibility to include integrality if there is a solution if (mip_solver_info.solution_objective != kHighsInf) { - const double mip_max_bound_violation = - std::max(mip_solver_info.row_violation, mip_solver_info.bound_violation); + const double mip_max_bound_violation = std::max( + mip_solver_info.row_violation, mip_solver_info.bound_violation); const double delta_max_bound_violation = std::abs(mip_max_bound_violation - info_.max_primal_infeasibility); // Possibly report a mis-match between the max bound violation @@ -4887,7 +4900,6 @@ void Highs::getHighsFiles() { this->files_.clear(); } - HighsMipSolverInfo getMipSolverInfo(const HighsMipSolver& mip_solver) { HighsMipSolverInfo mip_solver_info; mip_solver_info.clear(); diff --git a/highs/lp_data/HighsInterface.cpp b/highs/lp_data/HighsInterface.cpp index f88539881aa..220a2c71042 100644 --- a/highs/lp_data/HighsInterface.cpp +++ b/highs/lp_data/HighsInterface.cpp @@ -4244,7 +4244,6 @@ void HighsLinearObjective::clear() { this->priority = 0; } - void HighsMipSolverInfo::clear() { this->modelstatus = HighsModelStatus::kNotset; this->solution.clear(); diff --git a/highs/lp_data/HighsOptions.h b/highs/lp_data/HighsOptions.h index 5b9a31481bf..33e9670a77e 100644 --- a/highs/lp_data/HighsOptions.h +++ b/highs/lp_data/HighsOptions.h @@ -578,7 +578,7 @@ struct HighsOptionsStruct { mip_detect_symmetry(false), mip_allow_restart(false), mip_race_concurrency(0), - mip_race_read_solutions(false), + mip_race_read_solutions(false), mip_max_nodes(0), mip_max_stall_nodes(0), mip_max_start_nodes(0), @@ -1026,9 +1026,10 @@ class HighsOptions : public HighsOptionsStruct { advanced, &mip_race_concurrency, 0, 0, kHighsIInf); records.push_back(record_int); - record_bool = new OptionRecordBool("mip_race_read_solutions", - "Whether the MIP races should read other racers' solutions", - advanced, &mip_race_read_solutions, true); + record_bool = new OptionRecordBool( + "mip_race_read_solutions", + "Whether the MIP races should read other racers' solutions", advanced, + &mip_race_read_solutions, true); records.push_back(record_bool); record_int = new OptionRecordInt("mip_max_nodes", diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index f98d7cfbca3..53f0067f93d 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -67,7 +67,9 @@ HighsMipSolver::HighsMipSolver(HighsCallback& callback, HighsMipSolver::~HighsMipSolver() = default; void HighsMipSolver::run() { - if (!submip) printf("HighsMipSolver::run() with random_seed = %d\n", int(options_mip_->random_seed)); + if (!submip) + printf("HighsMipSolver::run() with random_seed = %d\n", + int(options_mip_->random_seed)); modelstatus_ = HighsModelStatus::kNotset; if (submip) { diff --git a/highs/mip/HighsMipSolver.h b/highs/mip/HighsMipSolver.h index ff727f343ac..49047df284a 100644 --- a/highs/mip/HighsMipSolver.h +++ b/highs/mip/HighsMipSolver.h @@ -29,8 +29,8 @@ struct MipRaceIncumbent { void clear(); void initialise(const HighsInt num_col); void update(const double objective, const std::vector& solution); - HighsInt read(const HighsInt last_incumbent_read, - double& objective_, std::vector& solution_) const; + HighsInt read(const HighsInt last_incumbent_read, double& objective_, + std::vector& solution_) const; }; struct MipRaceRecord { @@ -55,7 +55,8 @@ struct MipRace { const HighsLogOptions log_options_); HighsInt concurrency() const; void update(const double objective, const std::vector& solution); - bool newSolution(const HighsInt instance, double objective, std::vector& solution); + bool newSolution(const HighsInt instance, double objective, + std::vector& solution); void terminate(); bool terminated() const; void report() const; diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index 07aa6bd4656..99c8ce64d53 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -2672,14 +2672,15 @@ void HighsMipSolverData::queryExternalSolution( std::vector instance_solution; for (HighsInt instance = 0; instance < mip_race.concurrency(); instance++) { if (instance == mip_race.my_instance) continue; - if (!mip_race.newSolution(instance, instance_solution_objective_value, instance_solution)) continue; + if (!mip_race.newSolution(instance, instance_solution_objective_value, + instance_solution)) + continue; // Have read a new incumbent std::vector reduced_instance_solution; reduced_instance_solution = - postSolveStack.getReducedPrimalSolution(instance_solution); + postSolveStack.getReducedPrimalSolution(instance_solution); addIncumbent(reduced_instance_solution, instance_solution_objective_value, - kSolutionSourceHighsSolution); - + kSolutionSourceHighsSolution); } } @@ -2697,8 +2698,8 @@ void HighsMipSolverData::mipRaceUpdate() { } HighsInt HighsMipSolverData::mipRaceNewSolution(const HighsInt instance, - double& objective_value, - std::vector& solution) { + double& objective_value, + std::vector& solution) { assert(!mipsolver.submip); if (!mipsolver.mip_race_.record) return kMipRaceNoSolution; return mipsolver.mip_race_.newSolution(instance, objective_value, solution); @@ -2880,18 +2881,21 @@ void MipRaceIncumbent::update(const double objective_, } HighsInt MipRaceIncumbent::read(const HighsInt last_incumbent_read, - double& objective_, - std::vector& solution_) const { + double& objective_, + std::vector& solution_) const { const HighsInt start_write_incumbent = this->start_write_incumbent; assert(this->finish_write_incumbent <= start_write_incumbent); if (start_write_incumbent < last_incumbent_read) return kMipRaceNoSolution; // If a write call has not completed, return failure - if (this->finish_write_incumbent < start_write_incumbent) return kMipRaceNoSolution; + if (this->finish_write_incumbent < start_write_incumbent) + return kMipRaceNoSolution; // finish_write_incumbent = start_write_incumbent so start reading objective_ = this->objective; solution_ = this->solution; // Read is OK if no new write has started - return this->start_write_incumbent == start_write_incumbent ? start_write_incumbent : kMipRaceNoSolution; + return this->start_write_incumbent == start_write_incumbent + ? start_write_incumbent + : kMipRaceNoSolution; } void MipRaceRecord::clear() { @@ -2974,14 +2978,13 @@ void MipRace::update(const double objective, bool MipRace::newSolution(const HighsInt instance, double objective, std::vector& solution) { assert(this->record); - HighsInt new_incumbent_read = - this->record->incumbent[instance].read(this->last_incumbent_read[instance], - objective, solution); + HighsInt new_incumbent_read = this->record->incumbent[instance].read( + this->last_incumbent_read[instance], objective, solution); if (new_incumbent_read != kMipRaceNoSolution) { this->last_incumbent_read[instance] = new_incumbent_read; return true; } - return false; + return false; } void MipRace::terminate() { @@ -3001,11 +3004,11 @@ void MipRace::report() const { this->record->report(this->log_options); highsLogUser(this->log_options, HighsLogType::kInfo, "LastIncumbentRead: "); for (HighsInt instance = 0; instance < this->concurrency(); instance++) { - if (instance == this->my_instance) { + if (instance == this->my_instance) { highsLogUser(this->log_options, HighsLogType::kInfo, " %20s", ""); } else { highsLogUser(this->log_options, HighsLogType::kInfo, " %20d", - this->last_incumbent_read[instance]); + this->last_incumbent_read[instance]); } } highsLogUser(this->log_options, HighsLogType::kInfo, "\n\n"); diff --git a/highs/mip/HighsMipSolverData.h b/highs/mip/HighsMipSolverData.h index 44800ff4c25..75b09378e39 100644 --- a/highs/mip/HighsMipSolverData.h +++ b/highs/mip/HighsMipSolverData.h @@ -43,25 +43,25 @@ enum MipSolutionSource : int { kSolutionSourceNone = -1, kSolutionSourceMin = kSolutionSourceNone, // kSolutionSourceInitial, // 0 - kSolutionSourceBranching, // B - kSolutionSourceCentralRounding, // C - kSolutionSourceFeasibilityPump, // F - kSolutionSourceHeuristic, // H - kSolutionSourceShifting, // I - kSolutionSourceFeasibilityJump, // J - kSolutionSourceSubMip, // L - kSolutionSourceEmptyMip, // P - kSolutionSourceRandomizedRounding, // R - kSolutionSourceSolveLp, // S - kSolutionSourceEvaluateNode, // T - kSolutionSourceUnbounded, // U - kSolutionSourceUserSolution, // X - kSolutionSourceHighsSolution, // Y - kSolutionSourceZiRound, // Z - kSolutionSourceTrivialL, // l - kSolutionSourceTrivialP, // p - kSolutionSourceTrivialU, // u - kSolutionSourceTrivialZ, // z + kSolutionSourceBranching, // B + kSolutionSourceCentralRounding, // C + kSolutionSourceFeasibilityPump, // F + kSolutionSourceHeuristic, // H + kSolutionSourceShifting, // I + kSolutionSourceFeasibilityJump, // J + kSolutionSourceSubMip, // L + kSolutionSourceEmptyMip, // P + kSolutionSourceRandomizedRounding, // R + kSolutionSourceSolveLp, // S + kSolutionSourceEvaluateNode, // T + kSolutionSourceUnbounded, // U + kSolutionSourceUserSolution, // X + kSolutionSourceHighsSolution, // Y + kSolutionSourceZiRound, // Z + kSolutionSourceTrivialL, // l + kSolutionSourceTrivialP, // p + kSolutionSourceTrivialU, // u + kSolutionSourceTrivialZ, // z kSolutionSourceCleanup, kSolutionSourceCount }; @@ -303,9 +303,8 @@ struct HighsMipSolverData { HighsInt mipRaceConcurrency() const; void mipRaceUpdate(); - HighsInt mipRaceNewSolution(const HighsInt instance, - double& objective_value, - std::vector& solution); + HighsInt mipRaceNewSolution(const HighsInt instance, double& objective_value, + std::vector& solution); void mipRaceTerminate(); bool mipRaceTerminated() const; void mipRaceReport() const; From f01c30c98acceda3db0866a5fd96df5dbeaef719 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sun, 20 Jul 2025 17:28:57 +0100 Subject: [PATCH 24/58] Fixed two issues leading to CI compiler failures --- highs/mip/HighsMipSolverData.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index 99c8ce64d53..a71e4909d1e 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -2668,9 +2668,9 @@ void HighsMipSolverData::queryExternalSolution( if (!mipsolver.options_mip_->mip_race_read_solutions) return; MipRace& mip_race = mipsolver.mip_race_; if (!mip_race.record) return; - double instance_solution_objective_value; + double instance_solution_objective_value = kHighsInf; std::vector instance_solution; - for (HighsInt instance = 0; instance < mip_race.concurrency(); instance++) { + for (HighsInt instance = 0; instance < mipRaceConcurrency(); instance++) { if (instance == mip_race.my_instance) continue; if (!mip_race.newSolution(instance, instance_solution_objective_value, instance_solution)) @@ -2686,7 +2686,7 @@ void HighsMipSolverData::queryExternalSolution( HighsInt HighsMipSolverData::mipRaceConcurrency() const { assert(!mipsolver.submip); - if (!mipsolver.mip_race_.record) return; + if (!mipsolver.mip_race_.record) return 0; return mipsolver.mip_race_.concurrency(); } @@ -2965,7 +2965,7 @@ void MipRace::initialise(const HighsInt mip_race_concurrency, HighsInt MipRace::concurrency() const { assert(this->record); - return static_cast(this->last_incumbent_read.size()); + return this->record->concurrency(); } void MipRace::update(const double objective, From b785ecabbf45cc4805c63144aca0b122a43df5df Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sun, 20 Jul 2025 17:48:42 +0100 Subject: [PATCH 25/58] Need to use check/instances model in unit_tests mip-race --- check/TestMipSolver.cpp | 13 +++++++------ highs/mip/HighsMipSolver.cpp | 3 --- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index 763064dc65e..111e1e6abf7 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -1003,15 +1003,16 @@ TEST_CASE("issue-2432", "[highs_test_mip_solver]") { } TEST_CASE("mip-race", "[highs_test_mip_solver]") { - const std::string model = - "fiball"; //"neos-3381206-awhea"; //bell5";//"flugpl"; + const std::string model = "flugpl"; + // "fiball"; + // "neos-3381206-awhea"; const std::string model_file = - // std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; - "/srv/miplib2017/" + model + ".mps.gz"; + std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; + //"/srv/miplib2017/" + model + ".mps.gz"; Highs h; - // h.setOptionValue("output_flag", dev_run); + h.setOptionValue("output_flag", dev_run); h.setOptionValue("mip_race_concurrency", 4); - // h.setOptionValue("mip_race_read_solutions", false); + h.setOptionValue("mip_race_read_solutions", false); REQUIRE(h.readModel(model_file) == HighsStatus::kOk); REQUIRE(h.run() == HighsStatus::kOk); } diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index 3a8ee4c0e3f..73156f6b9c5 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -67,9 +67,6 @@ HighsMipSolver::HighsMipSolver(HighsCallback& callback, HighsMipSolver::~HighsMipSolver() = default; void HighsMipSolver::run() { - if (!submip) - printf("HighsMipSolver::run() with random_seed = %d\n", - int(options_mip_->random_seed)); modelstatus_ = HighsModelStatus::kNotset; if (submip) { From 0d284c98a5c9c28180e04f9bf0b11858967eae8a Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 21 Jul 2025 09:13:24 +0100 Subject: [PATCH 26/58] Introduce termination_status flag --- check/TestMipSolver.cpp | 12 ++++++------ highs/lp_data/Highs.cpp | 8 ++++++-- highs/mip/HighsMipSolver.cpp | 7 +++++++ highs/mip/HighsMipSolver.h | 1 + highs/mip/HighsMipSolverData.cpp | 16 +++++++++++++++- highs/mip/HighsMipSolverData.h | 1 + 6 files changed, 36 insertions(+), 9 deletions(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index 111e1e6abf7..fa5095a8139 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -1003,14 +1003,14 @@ TEST_CASE("issue-2432", "[highs_test_mip_solver]") { } TEST_CASE("mip-race", "[highs_test_mip_solver]") { - const std::string model = "flugpl"; - // "fiball"; + const bool ci_test = false; + const std::string model = ci_test ? "flugpl" : "fiball"; // "neos-3381206-awhea"; - const std::string model_file = - std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; - //"/srv/miplib2017/" + model + ".mps.gz"; + const std::string model_file = ci_test ? + std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps" : + "/srv/miplib2017/" + model + ".mps.gz"; Highs h; - h.setOptionValue("output_flag", dev_run); + if (ci_test) h.setOptionValue("output_flag", dev_run); h.setOptionValue("mip_race_concurrency", 4); h.setOptionValue("mip_race_read_solutions", false); REQUIRE(h.readModel(model_file) == HighsStatus::kOk); diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index f009cfd5c38..a6a2affb196 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -4079,7 +4079,9 @@ HighsStatus Highs::callSolveMip() { solver.mip_race_.initialise(mip_race_concurrency, instance, &mip_race_record, options_.log_options); - mip_time[instance] = -timer_.read(); + double this_time = timer_.read(); + printf("instance0: call run() %f6.4\n", this_time); + mip_time[instance] = -this_time; solver.run(); mip_time[instance] += timer_.read(); mip_solver_info = getMipSolverInfo(solver); @@ -4089,7 +4091,9 @@ HighsStatus Highs::callSolveMip() { worker.mip_race_.initialise(mip_race_concurrency, instance, &mip_race_record, worker_options[instance].log_options); - mip_time[instance] = -timer_.read(); + double this_time = timer_.read(); + printf("instance%d: call run() %f6.4\n", int(instance), this_time); + mip_time[instance] = -this_time; worker.run(); mip_time[instance] += timer_.read(); worker_info[instance] = getMipSolverInfo(worker); diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index 73156f6b9c5..643d56f165a 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -69,6 +69,9 @@ HighsMipSolver::~HighsMipSolver() = default; void HighsMipSolver::run() { modelstatus_ = HighsModelStatus::kNotset; + if (!submip) highsLogUser(options_mip_->log_options, HighsLogType::kInfo, + "instance%d: top run() %6.4f (MIP)\n", int(this->mip_race_.my_instance), this->timer_.read()); + if (submip) { analysis_.analyse_mip_time = false; } else { @@ -682,9 +685,13 @@ void HighsMipSolver::cleanupSolve() { if (!mipdata_->mipRaceTerminated()) { // No other instance has terminated the MIP race, so terminate // it + highsLogUser(options_mip_->log_options, HighsLogType::kInfo, + "instance%d: terminate %6.4f (MIP)\n", int(this->mipdata_->mipRaceMyInstance()), this->timer_.read()); mipdata_->mipRaceTerminate(); } else { // Indicate that this MIP race instance has been interrupted + highsLogUser(options_mip_->log_options, HighsLogType::kInfo, + "instance%d: terminated %6.4f (MIP)\n", int(this->mipdata_->mipRaceMyInstance()), this->timer_.read()); modelstatus_ = HighsModelStatus::kHighsInterrupt; } mipdata_->mipRaceReport(); diff --git a/highs/mip/HighsMipSolver.h b/highs/mip/HighsMipSolver.h index 49047df284a..80185451c7b 100644 --- a/highs/mip/HighsMipSolver.h +++ b/highs/mip/HighsMipSolver.h @@ -20,6 +20,7 @@ class HighsCliqueTable; class HighsImplications; const HighsInt kMipRaceNoSolution = -1; +const HighsInt kMipRaceNoInstance = -1; struct MipRaceIncumbent { HighsInt start_write_incumbent = kMipRaceNoSolution; diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index a71e4909d1e..34af97a4c64 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -2413,7 +2413,15 @@ bool HighsMipSolverData::checkLimits(int64_t nodeOffset) const { const HighsOptions& options = *mipsolver.options_mip_; // MIP race may have terminated - if (!mipsolver.submip && this->mipRaceTerminated()) return true; + if (!mipsolver.submip) { + highsLogUser(options.log_options, HighsLogType::kInfo, + "instance%d: terminated? %6.4f (MIP)\n", int(this->mipRaceMyInstance()), this->mipsolver.timer_.read()); + if (this->mipRaceTerminated()) { + highsLogUser(options.log_options, HighsLogType::kInfo, + "instance%d: terminated %6.4f (MIP)\n", int(this->mipRaceMyInstance()), this->mipsolver.timer_.read()); + return true; + } + } // Possible user interrupt if (!mipsolver.submip && mipsolver.callback_->user_callback) { @@ -2684,6 +2692,12 @@ void HighsMipSolverData::queryExternalSolution( } } +HighsInt HighsMipSolverData::mipRaceMyInstance() const { + assert(!mipsolver.submip); + if (!mipsolver.mip_race_.record) return kMipRaceNoInstance; + return mipsolver.mip_race_.my_instance; +} + HighsInt HighsMipSolverData::mipRaceConcurrency() const { assert(!mipsolver.submip); if (!mipsolver.mip_race_.record) return 0; diff --git a/highs/mip/HighsMipSolverData.h b/highs/mip/HighsMipSolverData.h index 75b09378e39..b1f45f548af 100644 --- a/highs/mip/HighsMipSolverData.h +++ b/highs/mip/HighsMipSolverData.h @@ -302,6 +302,7 @@ struct HighsMipSolverData { const ExternalMipSolutionQueryOrigin external_solution_query_origin); HighsInt mipRaceConcurrency() const; + HighsInt mipRaceMyInstance() const; void mipRaceUpdate(); HighsInt mipRaceNewSolution(const HighsInt instance, double& objective_value, std::vector& solution); From ad8bca5a3942fea3e18cc0d1f74d92ad6573f215 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 21 Jul 2025 10:20:46 +0100 Subject: [PATCH 27/58] Introduced independent terminator status and HighsTerminator struct --- check/TestMipSolver.cpp | 7 +++-- highs/lp_data/HConst.h | 1 + highs/lp_data/Highs.cpp | 6 ++-- highs/mip/HighsMipSolver.cpp | 9 ++++++ highs/mip/HighsMipSolver.h | 19 ++++++++++++- highs/mip/HighsMipSolverData.cpp | 47 ++++++++++++++++++++++++++++++-- highs/mip/HighsMipSolverData.h | 3 ++ 7 files changed, 84 insertions(+), 8 deletions(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index fa5095a8139..fb79dee8bce 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -1003,15 +1003,16 @@ TEST_CASE("issue-2432", "[highs_test_mip_solver]") { } TEST_CASE("mip-race", "[highs_test_mip_solver]") { - const bool ci_test = false; + const bool ci_test = true; const std::string model = ci_test ? "flugpl" : "fiball"; // "neos-3381206-awhea"; const std::string model_file = ci_test ? std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps" : "/srv/miplib2017/" + model + ".mps.gz"; Highs h; - if (ci_test) h.setOptionValue("output_flag", dev_run); - h.setOptionValue("mip_race_concurrency", 4); + // if (ci_test) h.setOptionValue("output_flag", dev_run); + const HighsInt mip_race_concurrency = ci_test ? 2 : 4; + h.setOptionValue("mip_race_concurrency", mip_race_concurrency); h.setOptionValue("mip_race_read_solutions", false); REQUIRE(h.readModel(model_file) == HighsStatus::kOk); REQUIRE(h.run() == HighsStatus::kOk); diff --git a/highs/lp_data/HConst.h b/highs/lp_data/HConst.h index 554e593a6ee..3e9afe12abc 100644 --- a/highs/lp_data/HConst.h +++ b/highs/lp_data/HConst.h @@ -39,6 +39,7 @@ const double kExcessivelyLargeCostValue = 1e10; const double kExcessivelySmallBoundValue = 1e-4; const double kExcessivelySmallCostValue = 1e-4; +const HighsInt kNoThreadInstance = -1; const bool kAllowDeveloperAssert = false; const bool kExtendInvertWhenAddingRows = false; diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index a6a2affb196..ffd5b45d596 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -4080,7 +4080,8 @@ HighsStatus Highs::callSolveMip() { &mip_race_record, options_.log_options); double this_time = timer_.read(); - printf("instance0: call run() %f6.4\n", this_time); + highsLogUser(options_.log_options, HighsLogType::kInfo, + "instance0: call run() %f6.4\n", this_time); mip_time[instance] = -this_time; solver.run(); mip_time[instance] += timer_.read(); @@ -4092,7 +4093,8 @@ HighsStatus Highs::callSolveMip() { &mip_race_record, worker_options[instance].log_options); double this_time = timer_.read(); - printf("instance%d: call run() %f6.4\n", int(instance), this_time); + highsLogUser(options_.log_options, HighsLogType::kInfo, + "instance%d: call run() %f6.4\n", int(instance), this_time); mip_time[instance] = -this_time; worker.run(); mip_time[instance] += timer_.read(); diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index 643d56f165a..692f405f8bf 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -44,6 +44,7 @@ HighsMipSolver::HighsMipSolver(HighsCallback& callback, implicinit(nullptr) { assert(!submip || submip_level > 0); max_submip_level = 0; + initialiseTerminator(); if (solution.value_valid) { #ifndef NDEBUG // MIP solver doesn't check row residuals, but they should be OK @@ -979,3 +980,11 @@ bool HighsMipSolver::solutionFeasible(const HighsLp* lp, row_violation <= mip_feasibility_tolerance; return feasible; } + +void HighsMipSolver::initialiseTerminator(HighsInt num_instance_, + HighsInt my_instance_, + HighsModelStatus* record_) { + this->termination_status_ = HighsModelStatus::kNotset; + this->terminator_.initialise(num_instance_, my_instance_, record_); +} + diff --git a/highs/mip/HighsMipSolver.h b/highs/mip/HighsMipSolver.h index 80185451c7b..a4be874f318 100644 --- a/highs/mip/HighsMipSolver.h +++ b/highs/mip/HighsMipSolver.h @@ -20,7 +20,6 @@ class HighsCliqueTable; class HighsImplications; const HighsInt kMipRaceNoSolution = -1; -const HighsInt kMipRaceNoInstance = -1; struct MipRaceIncumbent { HighsInt start_write_incumbent = kMipRaceNoSolution; @@ -63,6 +62,18 @@ struct MipRace { void report() const; }; +struct HighsTerminator { + HighsInt num_instance; + HighsInt my_instance; + HighsModelStatus* record; + void clear(); + void initialise(HighsInt num_instance_, + HighsInt my_instance_, + HighsModelStatus*record_); + void terminateNw(); + HighsModelStatus terminatedNw() const; +}; + class HighsMipSolver { public: HighsCallback* callback_; @@ -101,6 +112,9 @@ class HighsMipSolver { MipRace mip_race_; + HighsModelStatus termination_status_; + HighsTerminator terminator_; + void run(); HighsInt numCol() const { return model_->num_col_; } @@ -153,6 +167,9 @@ class HighsMipSolver { const std::vector* pass_row_value, double& bound_violation, double& row_violation, double& integrality_violation, HighsCDouble& obj) const; + void initialiseTerminator(HighsInt num_instance_ = 0, + HighsInt my_instance_ = kNoThreadInstance, + HighsModelStatus* record_ = nullptr); }; #endif diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index 34af97a4c64..b15483bed62 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -2423,6 +2423,8 @@ bool HighsMipSolverData::checkLimits(int64_t nodeOffset) const { } } + if (this->terminatedNw()) return true; + // Possible user interrupt if (!mipsolver.submip && mipsolver.callback_->user_callback) { mipsolver.callback_->clearHighsCallbackOutput(); @@ -2694,7 +2696,7 @@ void HighsMipSolverData::queryExternalSolution( HighsInt HighsMipSolverData::mipRaceMyInstance() const { assert(!mipsolver.submip); - if (!mipsolver.mip_race_.record) return kMipRaceNoInstance; + if (!mipsolver.mip_race_.record) return kNoThreadInstance; return mipsolver.mip_race_.my_instance; } @@ -2732,10 +2734,22 @@ bool HighsMipSolverData::mipRaceTerminated() const { } void HighsMipSolverData::mipRaceReport() const { - if (!mipsolver.mip_race_.record) return; assert(!mipsolver.submip); + if (!mipsolver.mip_race_.record) return; mipsolver.mip_race_.report(); } + +void HighsMipSolverData::terminateNw() { + if (mipsolver.terminator_.num_instance <= 0) return; + mipsolver.terminator_.terminateNw(); +} + +bool HighsMipSolverData::terminatedNw() const { + if (mipsolver.terminator_.num_instance > 0) + mipsolver.termination_status_ = mipsolver.terminator_.terminatedNw(); + return mipsolver.termination_status_ != HighsModelStatus::kNotset; +} + static double possInfRelDiff(const double v0, const double v1, const double den) { double rel_diff; @@ -3027,3 +3041,32 @@ void MipRace::report() const { } highsLogUser(this->log_options, HighsLogType::kInfo, "\n\n"); } + +void HighsTerminator::clear() { + this->num_instance = 0; + this->my_instance = kNoThreadInstance; + this->record = nullptr; +} + +void HighsTerminator::initialise(HighsInt num_instance_, + HighsInt my_instance_, + HighsModelStatus* record_) { + this->num_instance = num_instance_; + this->my_instance = my_instance_; + this->record = record_; +} + +void HighsTerminator::terminateNw() { + assert(this->record); + assert(this->my_instance < this->num_instance); + this->record[this->my_instance] = HighsModelStatus::kHighsInterrupt; +} + +HighsModelStatus HighsTerminator::terminatedNw() const { + assert(this->record); + for (HighsInt instance = 0; instance < this->num_instance; instance++) { + if (this->record[instance] != HighsModelStatus::kNotset) + return this->record[instance]; + } + return HighsModelStatus::kNotset; +} diff --git a/highs/mip/HighsMipSolverData.h b/highs/mip/HighsMipSolverData.h index b1f45f548af..a35c00b439e 100644 --- a/highs/mip/HighsMipSolverData.h +++ b/highs/mip/HighsMipSolverData.h @@ -309,6 +309,9 @@ struct HighsMipSolverData { void mipRaceTerminate(); bool mipRaceTerminated() const; void mipRaceReport() const; + + void terminateNw(); + bool terminatedNw() const; }; #endif From 105d5609b553ed4a3e1687bb8ada7600ce078d83 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 21 Jul 2025 10:32:57 +0100 Subject: [PATCH 28/58] Now unit tests pass, start using termination_status_ and HighsTerminator --- highs/mip/HighsMipSolver.h | 1 + highs/mip/HighsMipSolverData.cpp | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/highs/mip/HighsMipSolver.h b/highs/mip/HighsMipSolver.h index a4be874f318..a4bd732b08f 100644 --- a/highs/mip/HighsMipSolver.h +++ b/highs/mip/HighsMipSolver.h @@ -72,6 +72,7 @@ struct HighsTerminator { HighsModelStatus*record_); void terminateNw(); HighsModelStatus terminatedNw() const; + void report(const HighsLogOptions log_options) const; }; class HighsMipSolver { diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index b15483bed62..ea3174e2d6b 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -3070,3 +3070,12 @@ HighsModelStatus HighsTerminator::terminatedNw() const { } return HighsModelStatus::kNotset; } + +void HighsTerminator::report(const HighsLogOptions log_options) const { + highsLogUser(log_options, HighsLogType::kInfo, "\nTerminator: "); + for (HighsInt instance = 0; instance < this->num_instance; instance++) + highsLogUser(log_options, HighsLogType::kInfo, " %20d", + int(this->record[instance])); + highsLogUser(log_options, HighsLogType::kInfo, "\n"); +} + From e8a917571510f0bc17a49cd30a2fafea06fa81c2 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 21 Jul 2025 13:52:37 +0100 Subject: [PATCH 29/58] Now to initialise HighsTerminator --- highs/mip/HighsMipSolver.cpp | 2 ++ highs/mip/HighsMipSolverData.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index 692f405f8bf..03d091b44bc 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -683,12 +683,14 @@ void HighsMipSolver::run() { void HighsMipSolver::cleanupSolve() { if (!submip) { + assert(mipdata_->mipRaceTerminated() == (terminator_.terminatedNw() != HighsModelStatus::kNotset)); if (!mipdata_->mipRaceTerminated()) { // No other instance has terminated the MIP race, so terminate // it highsLogUser(options_mip_->log_options, HighsLogType::kInfo, "instance%d: terminate %6.4f (MIP)\n", int(this->mipdata_->mipRaceMyInstance()), this->timer_.read()); mipdata_->mipRaceTerminate(); + terminator_.terminateNw(); } else { // Indicate that this MIP race instance has been interrupted highsLogUser(options_mip_->log_options, HighsLogType::kInfo, diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index ea3174e2d6b..f854a87c7e4 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -2416,6 +2416,7 @@ bool HighsMipSolverData::checkLimits(int64_t nodeOffset) const { if (!mipsolver.submip) { highsLogUser(options.log_options, HighsLogType::kInfo, "instance%d: terminated? %6.4f (MIP)\n", int(this->mipRaceMyInstance()), this->mipsolver.timer_.read()); + assert(this->mipRaceTerminated() == this->terminatedNw()); if (this->mipRaceTerminated()) { highsLogUser(options.log_options, HighsLogType::kInfo, "instance%d: terminated %6.4f (MIP)\n", int(this->mipRaceMyInstance()), this->mipsolver.timer_.read()); @@ -2423,6 +2424,7 @@ bool HighsMipSolverData::checkLimits(int64_t nodeOffset) const { } } + assert(this->mipRaceTerminated() == this->terminatedNw()); if (this->terminatedNw()) return true; // Possible user interrupt From e91a215c3281343b38a90324d077fa38096a1d1c Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 21 Jul 2025 15:20:22 +0100 Subject: [PATCH 30/58] Eliminated infinite loop when HighsTermination occurs in performRestart --- check/TestMipSolver.cpp | 8 ++++---- highs/lp_data/Highs.cpp | 5 ++++- highs/mip/HighsMipSolver.cpp | 10 ++++++++++ highs/mip/HighsMipSolver.h | 3 +++ highs/mip/HighsMipSolverData.cpp | 15 ++++++++++----- 5 files changed, 31 insertions(+), 10 deletions(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index fb79dee8bce..00a5d620d55 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -1003,17 +1003,17 @@ TEST_CASE("issue-2432", "[highs_test_mip_solver]") { } TEST_CASE("mip-race", "[highs_test_mip_solver]") { - const bool ci_test = true; - const std::string model = ci_test ? "flugpl" : "fiball"; + const bool ci_test = false;//true; + const std::string model = ci_test ? "rgn" : "fiball"; // "neos-3381206-awhea"; const std::string model_file = ci_test ? std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps" : "/srv/miplib2017/" + model + ".mps.gz"; Highs h; // if (ci_test) h.setOptionValue("output_flag", dev_run); - const HighsInt mip_race_concurrency = ci_test ? 2 : 4; + const HighsInt mip_race_concurrency = ci_test ? 4 : 4; h.setOptionValue("mip_race_concurrency", mip_race_concurrency); - h.setOptionValue("mip_race_read_solutions", false); + // h.setOptionValue("mip_race_read_solutions", false); REQUIRE(h.readModel(model_file) == HighsStatus::kOk); REQUIRE(h.run() == HighsStatus::kOk); } diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index ffd5b45d596..e7a04bfb480 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -4041,7 +4041,8 @@ HighsStatus Highs::callSolveMip() { // Set up the shared memory for the MIP solver race MipRaceRecord mip_race_record; mip_race_record.initialise(mip_race_concurrency, lp.num_col_); - + // Set up the shared memory for the concurrent MIP terminator + auto terminator_record = solver.initialiseRecord(mip_race_concurrency); // Don't allow callbacks for workers HighsCallback worker_callback = callback_; worker_callback.clear(); @@ -4079,6 +4080,7 @@ HighsStatus Highs::callSolveMip() { solver.mip_race_.initialise(mip_race_concurrency, instance, &mip_race_record, options_.log_options); + solver.initialiseTerminator(mip_race_concurrency, instance, terminator_record.data()); double this_time = timer_.read(); highsLogUser(options_.log_options, HighsLogType::kInfo, "instance0: call run() %f6.4\n", this_time); @@ -4092,6 +4094,7 @@ HighsStatus Highs::callSolveMip() { worker.mip_race_.initialise(mip_race_concurrency, instance, &mip_race_record, worker_options[instance].log_options); + worker.initialiseTerminator(mip_race_concurrency, instance, terminator_record.data()); double this_time = timer_.read(); highsLogUser(options_.log_options, HighsLogType::kInfo, "instance%d: call run() %f6.4\n", int(instance), this_time); diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index 03d091b44bc..9ee6df50d12 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -172,6 +172,11 @@ void HighsMipSolver::run() { analysis_.mipTimerStart(kMipClockEvaluateRootNode); mipdata_->evaluateRootNode(); analysis_.mipTimerStop(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 && @@ -990,3 +995,8 @@ void HighsMipSolver::initialiseTerminator(HighsInt num_instance_, this->terminator_.initialise(num_instance_, my_instance_, record_); } +std::vector HighsMipSolver::initialiseRecord(HighsInt num_instance) const { + std::vector record(num_instance, HighsModelStatus::kNotset); + return record; +} + diff --git a/highs/mip/HighsMipSolver.h b/highs/mip/HighsMipSolver.h index a4bd732b08f..6408c2f5d87 100644 --- a/highs/mip/HighsMipSolver.h +++ b/highs/mip/HighsMipSolver.h @@ -168,9 +168,12 @@ class HighsMipSolver { const std::vector* pass_row_value, double& bound_violation, double& row_violation, double& integrality_violation, HighsCDouble& obj) const; + std::vector initialiseRecord(HighsInt num_instance) const; void initialiseTerminator(HighsInt num_instance_ = 0, HighsInt my_instance_ = kNoThreadInstance, HighsModelStatus* record_ = nullptr); + bool terminate() const { return this->termination_status_ != HighsModelStatus::kNotset; } + HighsModelStatus terminationStatus() const { return this->termination_status_; } }; #endif diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index f854a87c7e4..f9046010431 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -1330,6 +1330,11 @@ void HighsMipSolverData::performRestart() { // Bounds are currently in the original space since presolve will have // changed offset_ runSetup(); + if (mipsolver.terminate()) { + printf("HighsMipSolverData::performRestart() mipsolver.termination_status_ = %d\n", + int(mipsolver.termination_status_)); + return; + } postSolveStack.removeCutsFromModel(numCuts); @@ -2382,6 +2387,7 @@ void HighsMipSolverData::evaluateRootNode() { analysis.mipTimerStart(kMipClockPerformRestart); performRestart(); analysis.mipTimerStop(kMipClockPerformRestart); + if (mipsolver.terminate()) return; ++numRestartsRoot; if (mipsolver.modelstatus_ == HighsModelStatus::kNotset) { clockOff(analysis); @@ -2422,11 +2428,10 @@ bool HighsMipSolverData::checkLimits(int64_t nodeOffset) const { "instance%d: terminated %6.4f (MIP)\n", int(this->mipRaceMyInstance()), this->mipsolver.timer_.read()); return true; } + assert(this->mipRaceTerminated() == this->terminatedNw()); + if (this->terminatedNw()) return true; } - assert(this->mipRaceTerminated() == this->terminatedNw()); - if (this->terminatedNw()) return true; - // Possible user interrupt if (!mipsolver.submip && mipsolver.callback_->user_callback) { mipsolver.callback_->clearHighsCallbackOutput(); @@ -2737,8 +2742,8 @@ bool HighsMipSolverData::mipRaceTerminated() const { void HighsMipSolverData::mipRaceReport() const { assert(!mipsolver.submip); - if (!mipsolver.mip_race_.record) return; - mipsolver.mip_race_.report(); + if (mipsolver.terminator_.record) mipsolver.terminator_.report(mipsolver.options_mip_->log_options); + if (mipsolver.mip_race_.record) mipsolver.mip_race_.report(); } void HighsMipSolverData::terminateNw() { From 46c5bde1db4053069a5308b74aae7025a84aa019 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 21 Jul 2025 15:33:39 +0100 Subject: [PATCH 31/58] Stripped termination record from MipRace --- check/TestMipSolver.cpp | 2 +- highs/mip/HighsMipSolver.cpp | 4 +-- highs/mip/HighsMipSolver.h | 4 +-- highs/mip/HighsMipSolverData.cpp | 45 +++++++++----------------------- highs/mip/HighsMipSolverData.h | 2 -- 5 files changed, 15 insertions(+), 42 deletions(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index 00a5d620d55..364247d6e58 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -1003,7 +1003,7 @@ TEST_CASE("issue-2432", "[highs_test_mip_solver]") { } TEST_CASE("mip-race", "[highs_test_mip_solver]") { - const bool ci_test = false;//true; + const bool ci_test = true;//false;// const std::string model = ci_test ? "rgn" : "fiball"; // "neos-3381206-awhea"; const std::string model_file = ci_test ? diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index 9ee6df50d12..149257e11c6 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -688,13 +688,11 @@ void HighsMipSolver::run() { void HighsMipSolver::cleanupSolve() { if (!submip) { - assert(mipdata_->mipRaceTerminated() == (terminator_.terminatedNw() != HighsModelStatus::kNotset)); - if (!mipdata_->mipRaceTerminated()) { + if (terminator_.notTerminatedNw()) { // No other instance has terminated the MIP race, so terminate // it highsLogUser(options_mip_->log_options, HighsLogType::kInfo, "instance%d: terminate %6.4f (MIP)\n", int(this->mipdata_->mipRaceMyInstance()), this->timer_.read()); - mipdata_->mipRaceTerminate(); terminator_.terminateNw(); } else { // Indicate that this MIP race instance has been interrupted diff --git a/highs/mip/HighsMipSolver.h b/highs/mip/HighsMipSolver.h index 6408c2f5d87..342ec227df9 100644 --- a/highs/mip/HighsMipSolver.h +++ b/highs/mip/HighsMipSolver.h @@ -34,7 +34,6 @@ struct MipRaceIncumbent { }; struct MipRaceRecord { - std::vector terminated; std::vector incumbent; void clear(); void initialise(const HighsInt mip_race_concurrency, const HighsInt num_col); @@ -57,8 +56,6 @@ struct MipRace { void update(const double objective, const std::vector& solution); bool newSolution(const HighsInt instance, double objective, std::vector& solution); - void terminate(); - bool terminated() const; void report() const; }; @@ -72,6 +69,7 @@ struct HighsTerminator { HighsModelStatus*record_); void terminateNw(); HighsModelStatus terminatedNw() const; + bool notTerminatedNw() const; void report(const HighsLogOptions log_options) const; }; diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index f9046010431..a9283c861ce 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -2422,13 +2422,13 @@ bool HighsMipSolverData::checkLimits(int64_t nodeOffset) const { if (!mipsolver.submip) { highsLogUser(options.log_options, HighsLogType::kInfo, "instance%d: terminated? %6.4f (MIP)\n", int(this->mipRaceMyInstance()), this->mipsolver.timer_.read()); - assert(this->mipRaceTerminated() == this->terminatedNw()); - if (this->mipRaceTerminated()) { + assert(this->terminatedNw() == this->terminatedNw()); + if (this->terminatedNw()) { highsLogUser(options.log_options, HighsLogType::kInfo, "instance%d: terminated %6.4f (MIP)\n", int(this->mipRaceMyInstance()), this->mipsolver.timer_.read()); return true; } - assert(this->mipRaceTerminated() == this->terminatedNw()); + assert(this->terminatedNw() == this->terminatedNw()); if (this->terminatedNw()) return true; } @@ -2728,18 +2728,6 @@ HighsInt HighsMipSolverData::mipRaceNewSolution(const HighsInt instance, return mipsolver.mip_race_.newSolution(instance, objective_value, solution); } -void HighsMipSolverData::mipRaceTerminate() { - assert(!mipsolver.submip); - if (!mipsolver.mip_race_.record) return; - mipsolver.mip_race_.terminate(); -} - -bool HighsMipSolverData::mipRaceTerminated() const { - assert(!mipsolver.submip); - if (!mipsolver.mip_race_.record) return false; - return mipsolver.mip_race_.terminated(); -} - void HighsMipSolverData::mipRaceReport() const { assert(!mipsolver.submip); if (mipsolver.terminator_.record) mipsolver.terminator_.report(mipsolver.options_mip_->log_options); @@ -2934,14 +2922,12 @@ HighsInt MipRaceIncumbent::read(const HighsInt last_incumbent_read, } void MipRaceRecord::clear() { - this->terminated.clear(); this->incumbent.clear(); } void MipRaceRecord::initialise(const HighsInt mip_race_concurrency, const HighsInt num_col) { this->clear(); - this->terminated.assign(mip_race_concurrency, false); MipRaceIncumbent incumbent_; incumbent_.initialise(num_col); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) @@ -2962,10 +2948,6 @@ void MipRaceRecord::report(const HighsLogOptions log_options) const { highsLogUser(log_options, HighsLogType::kInfo, "\nMipRaceRecord: "); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) highsLogUser(log_options, HighsLogType::kInfo, " %20d", int(instance)); - highsLogUser(log_options, HighsLogType::kInfo, "\nTerminated: "); - for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) - highsLogUser(log_options, HighsLogType::kInfo, " %20s", - this->terminated[instance] ? "T" : "F"); highsLogUser(log_options, HighsLogType::kInfo, "\nStartWrite: "); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) highsLogUser(log_options, HighsLogType::kInfo, " %20d", @@ -3022,18 +3004,6 @@ bool MipRace::newSolution(const HighsInt instance, double objective, return false; } -void MipRace::terminate() { - assert(this->record); - this->record->terminated[this->my_instance] = true; -} - -bool MipRace::terminated() const { - assert(this->record); - for (HighsInt instance = 0; instance < this->concurrency(); instance++) - if (this->record->terminated[instance]) return true; - return false; -} - void MipRace::report() const { assert(this->record); this->record->report(this->log_options); @@ -3078,6 +3048,15 @@ HighsModelStatus HighsTerminator::terminatedNw() const { return HighsModelStatus::kNotset; } +bool HighsTerminator::notTerminatedNw() const { + assert(this->record); + for (HighsInt instance = 0; instance < this->num_instance; instance++) { + if (this->record[instance] != HighsModelStatus::kNotset) + return false; + } + return true; +} + void HighsTerminator::report(const HighsLogOptions log_options) const { highsLogUser(log_options, HighsLogType::kInfo, "\nTerminator: "); for (HighsInt instance = 0; instance < this->num_instance; instance++) diff --git a/highs/mip/HighsMipSolverData.h b/highs/mip/HighsMipSolverData.h index a35c00b439e..a4504706385 100644 --- a/highs/mip/HighsMipSolverData.h +++ b/highs/mip/HighsMipSolverData.h @@ -306,8 +306,6 @@ struct HighsMipSolverData { void mipRaceUpdate(); HighsInt mipRaceNewSolution(const HighsInt instance, double& objective_value, std::vector& solution); - void mipRaceTerminate(); - bool mipRaceTerminated() const; void mipRaceReport() const; void terminateNw(); From 26e930b912df2af3f836bad72c43beb54d1fbe1b Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 21 Jul 2025 16:06:44 +0100 Subject: [PATCH 32/58] Now to avoid HighsTerminator call for standard MIP solve --- check/TestMipSolver.cpp | 55 ++++++++++++++++++++++++++++---- highs/lp_data/Highs.cpp | 19 ++++++----- highs/mip/HighsMipSolver.cpp | 26 +++++++++------ highs/mip/HighsMipSolver.h | 25 ++++++++------- highs/mip/HighsMipSolverData.cpp | 22 ++++++------- highs/mip/HighsMipSolverData.h | 4 +-- 6 files changed, 102 insertions(+), 49 deletions(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index 364247d6e58..65e956ad4f6 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -40,12 +40,16 @@ TEST_CASE("MIP-rowless-1", "[highs_test_mip_solver]") { Highs highs; if (!dev_run) highs.setOptionValue("output_flag", false); rowlessMIP1(highs); + + highs.resetGlobalScheduler(true); } TEST_CASE("MIP-rowless-2", "[highs_test_mip_solver]") { Highs highs; if (!dev_run) highs.setOptionValue("output_flag", false); rowlessMIP2(highs); + + highs.resetGlobalScheduler(true); } TEST_CASE("MIP-solution-limit", "[highs_test_mip_solver]") { @@ -79,6 +83,8 @@ TEST_CASE("MIP-solution-limit", "[highs_test_mip_solver]") { REQUIRE(highs.getModelStatus() == HighsModelStatus::kSolutionLimit); highs.setOptionValue("mip_max_improving_sols", kHighsIInf); highs.clearSolver(); + + highs.resetGlobalScheduler(true); } TEST_CASE("MIP-integrality", "[highs_test_mip_solver]") { @@ -173,6 +179,8 @@ TEST_CASE("MIP-integrality", "[highs_test_mip_solver]") { REQUIRE(info.mip_node_count == 1); REQUIRE(fabs(info.mip_dual_bound + 6) < double_equal_tolerance); REQUIRE(std::fabs(info.mip_gap) < 1e-12); + + highs.resetGlobalScheduler(true); } TEST_CASE("MIP-clear-integrality", "[highs_test_mip_solver]") { @@ -215,6 +223,8 @@ TEST_CASE("MIP-nmck", "[highs_test_mip_solver]") { REQUIRE(info.num_primal_infeasibilities == 0); REQUIRE(info.max_primal_infeasibility == 0); REQUIRE(info.sum_primal_infeasibilities == 0); + + highs.resetGlobalScheduler(true); } TEST_CASE("MIP-maximize", "[highs_test_mip_solver]") { @@ -295,6 +305,8 @@ TEST_CASE("MIP-maximize", "[highs_test_mip_solver]") { REQUIRE(std::abs(info.objective_function_value - info.mip_dual_bound) <= options.mip_abs_gap); REQUIRE(std::abs(info.mip_gap) <= options.mip_rel_gap); + + highs.resetGlobalScheduler(true); } TEST_CASE("MIP-unbounded", "[highs_test_mip_solver]") { @@ -403,6 +415,8 @@ TEST_CASE("MIP-unbounded", "[highs_test_mip_solver]") { model_status = highs.getModelStatus(); REQUIRE(model_status == HighsModelStatus::kInfeasible); + + highs.resetGlobalScheduler(true); } TEST_CASE("MIP-od", "[highs_test_mip_solver]") { @@ -470,6 +484,8 @@ TEST_CASE("MIP-od", "[highs_test_mip_solver]") { double_equal_tolerance); REQUIRE(fabs(solution.col_value[0] - required_x0_value) < double_equal_tolerance); + + highs.resetGlobalScheduler(true); } TEST_CASE("MIP-infeasible-start", "[highs_test_mip_solver]") { @@ -513,6 +529,8 @@ TEST_CASE("MIP-infeasible-start", "[highs_test_mip_solver]") { HighsStatus::kOk); highs.run(); REQUIRE(model_status == HighsModelStatus::kInfeasible); + + highs.resetGlobalScheduler(true); } TEST_CASE("get-integrality", "[highs_test_mip_solver]") {} @@ -556,6 +574,8 @@ TEST_CASE("MIP-bounds", "[highs_test_mip_solver]") { obj1); REQUIRE(obj0 == obj1); std::remove(test_mps.c_str()); + + highs.resetGlobalScheduler(true); } TEST_CASE("MIP-get-saved-solutions", "[highs_test_mip_solver]") { @@ -583,6 +603,8 @@ TEST_CASE("MIP-get-saved-solutions", "[highs_test_mip_solver]") { REQUIRE(saved_objective_and_solution[last_saved_solution].col_value[iCol] == highs.getSolution().col_value[iCol]); std::remove(solution_file.c_str()); + + highs.resetGlobalScheduler(true); } TEST_CASE("MIP-objective-target", "[highs_test_mip_solver]") { @@ -597,6 +619,8 @@ TEST_CASE("MIP-objective-target", "[highs_test_mip_solver]") { highs.run(); REQUIRE(highs.getModelStatus() == HighsModelStatus::kObjectiveTarget); REQUIRE(highs.getInfo().objective_function_value > egout_optimal_objective); + + highs.resetGlobalScheduler(true); } TEST_CASE("MIP-max-offset-test", "[highs_test_mip_solver]") { @@ -625,6 +649,8 @@ TEST_CASE("MIP-max-offset-test", "[highs_test_mip_solver]") { highs.getInfo().objective_function_value; REQUIRE(objectiveOk(max_offset_optimal_objective, -offset_optimal_objective, dev_run)); + + highs.resetGlobalScheduler(true); } TEST_CASE("MIP-get-saved-solutions-presolve", "[highs_test_mip_solver]") { @@ -663,6 +689,8 @@ TEST_CASE("MIP-get-saved-solutions-presolve", "[highs_test_mip_solver]") { REQUIRE(saved_objective_and_solution[last_saved_solution].col_value[iCol] == highs.getSolution().col_value[iCol]); std::remove(solution_file.c_str()); + + highs.resetGlobalScheduler(true); } TEST_CASE("IP-infeasible-unbounded", "[highs_test_mip_solver]") { @@ -720,6 +748,8 @@ TEST_CASE("IP-infeasible-unbounded", "[highs_test_mip_solver]") { } highs.setOptionValue("presolve", kHighsOnString); } + + highs.resetGlobalScheduler(true); } TEST_CASE("IP-with-fract-bounds-no-presolve", "[highs_test_mip_solver]") { @@ -755,6 +785,8 @@ TEST_CASE("IP-with-fract-bounds-no-presolve", "[highs_test_mip_solver]") { // Infeasible REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); + + highs.resetGlobalScheduler(true); } bool objectiveOk(const double optimal_objective, @@ -787,6 +819,8 @@ void solve(Highs& highs, std::string presolve, require_optimal_objective, dev_run)); } REQUIRE(highs.resetOptions() == HighsStatus::kOk); + + highs.resetGlobalScheduler(true); } void distillationMIP(Highs& highs) { @@ -1003,17 +1037,24 @@ TEST_CASE("issue-2432", "[highs_test_mip_solver]") { } TEST_CASE("mip-race", "[highs_test_mip_solver]") { - const bool ci_test = true;//false;// - const std::string model = ci_test ? "rgn" : "fiball"; + const bool ci_test = true; // false;// + const std::string model = ci_test ? "flugpl" : "fiball"; // "neos-3381206-awhea"; - const std::string model_file = ci_test ? - std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps" : - "/srv/miplib2017/" + model + ".mps.gz"; + const std::string model_file = + ci_test ? std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps" + : "/srv/miplib2017/" + model + ".mps.gz"; Highs h; - // if (ci_test) h.setOptionValue("output_flag", dev_run); - const HighsInt mip_race_concurrency = ci_test ? 4 : 4; + if (ci_test) h.setOptionValue("output_flag", dev_run); + const HighsInt mip_race_concurrency = ci_test ? 2 : 4; h.setOptionValue("mip_race_concurrency", mip_race_concurrency); // h.setOptionValue("mip_race_read_solutions", false); REQUIRE(h.readModel(model_file) == HighsStatus::kOk); REQUIRE(h.run() == HighsStatus::kOk); + + if (ci_test) { + h.clearSolver(); + h.setOptionValue("mip_race_read_solutions", false); + REQUIRE(h.run() == HighsStatus::kOk); + } + h.resetGlobalScheduler(true); } diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index e7a04bfb480..bcdb1560ba9 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -4080,10 +4080,11 @@ HighsStatus Highs::callSolveMip() { solver.mip_race_.initialise(mip_race_concurrency, instance, &mip_race_record, options_.log_options); - solver.initialiseTerminator(mip_race_concurrency, instance, terminator_record.data()); - double this_time = timer_.read(); - highsLogUser(options_.log_options, HighsLogType::kInfo, - "instance0: call run() %f6.4\n", this_time); + solver.initialiseTerminator(mip_race_concurrency, instance, + terminator_record.data()); + double this_time = timer_.read(); + highsLogUser(options_.log_options, HighsLogType::kInfo, + "instance0: call run() %f6.4\n", this_time); mip_time[instance] = -this_time; solver.run(); mip_time[instance] += timer_.read(); @@ -4094,10 +4095,12 @@ HighsStatus Highs::callSolveMip() { worker.mip_race_.initialise(mip_race_concurrency, instance, &mip_race_record, worker_options[instance].log_options); - worker.initialiseTerminator(mip_race_concurrency, instance, terminator_record.data()); - double this_time = timer_.read(); - highsLogUser(options_.log_options, HighsLogType::kInfo, - "instance%d: call run() %f6.4\n", int(instance), this_time); + worker.initialiseTerminator(mip_race_concurrency, instance, + terminator_record.data()); + double this_time = timer_.read(); + highsLogUser(options_.log_options, HighsLogType::kInfo, + "instance%d: call run() %f6.4\n", int(instance), + this_time); mip_time[instance] = -this_time; worker.run(); mip_time[instance] += timer_.read(); diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index 149257e11c6..e071ac8eef0 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -70,8 +70,10 @@ HighsMipSolver::~HighsMipSolver() = default; void HighsMipSolver::run() { modelstatus_ = HighsModelStatus::kNotset; - if (!submip) highsLogUser(options_mip_->log_options, HighsLogType::kInfo, - "instance%d: top run() %6.4f (MIP)\n", int(this->mip_race_.my_instance), this->timer_.read()); + if (!submip) + highsLogUser(options_mip_->log_options, HighsLogType::kInfo, + "instance%d: top run() %6.4f (MIP)\n", + int(this->mip_race_.my_instance), this->timer_.read()); if (submip) { analysis_.analyse_mip_time = false; @@ -688,16 +690,20 @@ void HighsMipSolver::run() { void HighsMipSolver::cleanupSolve() { if (!submip) { - if (terminator_.notTerminatedNw()) { + if (terminator_.notTerminated()) { // No other instance has terminated the MIP race, so terminate // it highsLogUser(options_mip_->log_options, HighsLogType::kInfo, - "instance%d: terminate %6.4f (MIP)\n", int(this->mipdata_->mipRaceMyInstance()), this->timer_.read()); - terminator_.terminateNw(); + "instance%d: terminate %6.4f (MIP)\n", + int(this->mipdata_->mipRaceMyInstance()), + this->timer_.read()); + terminator_.terminate(); } else { // Indicate that this MIP race instance has been interrupted highsLogUser(options_mip_->log_options, HighsLogType::kInfo, - "instance%d: terminated %6.4f (MIP)\n", int(this->mipdata_->mipRaceMyInstance()), this->timer_.read()); + "instance%d: terminated %6.4f (MIP)\n", + int(this->mipdata_->mipRaceMyInstance()), + this->timer_.read()); modelstatus_ = HighsModelStatus::kHighsInterrupt; } mipdata_->mipRaceReport(); @@ -987,14 +993,14 @@ bool HighsMipSolver::solutionFeasible(const HighsLp* lp, } void HighsMipSolver::initialiseTerminator(HighsInt num_instance_, - HighsInt my_instance_, - HighsModelStatus* record_) { + HighsInt my_instance_, + HighsModelStatus* record_) { this->termination_status_ = HighsModelStatus::kNotset; this->terminator_.initialise(num_instance_, my_instance_, record_); } -std::vector HighsMipSolver::initialiseRecord(HighsInt num_instance) const { +std::vector HighsMipSolver::initialiseRecord( + HighsInt num_instance) const { std::vector record(num_instance, HighsModelStatus::kNotset); return record; } - diff --git a/highs/mip/HighsMipSolver.h b/highs/mip/HighsMipSolver.h index 342ec227df9..e920676d514 100644 --- a/highs/mip/HighsMipSolver.h +++ b/highs/mip/HighsMipSolver.h @@ -64,15 +64,14 @@ struct HighsTerminator { HighsInt my_instance; HighsModelStatus* record; void clear(); - void initialise(HighsInt num_instance_, - HighsInt my_instance_, - HighsModelStatus*record_); - void terminateNw(); - HighsModelStatus terminatedNw() const; - bool notTerminatedNw() const; + void initialise(HighsInt num_instance_, HighsInt my_instance_, + HighsModelStatus* record_); + void terminate(); + HighsModelStatus terminated() const; + bool notTerminated() const; void report(const HighsLogOptions log_options) const; }; - + class HighsMipSolver { public: HighsCallback* callback_; @@ -168,10 +167,14 @@ class HighsMipSolver { double& integrality_violation, HighsCDouble& obj) const; std::vector initialiseRecord(HighsInt num_instance) const; void initialiseTerminator(HighsInt num_instance_ = 0, - HighsInt my_instance_ = kNoThreadInstance, - HighsModelStatus* record_ = nullptr); - bool terminate() const { return this->termination_status_ != HighsModelStatus::kNotset; } - HighsModelStatus terminationStatus() const { return this->termination_status_; } + HighsInt my_instance_ = kNoThreadInstance, + HighsModelStatus* record_ = nullptr); + bool terminate() const { + return this->termination_status_ != HighsModelStatus::kNotset; + } + HighsModelStatus terminationStatus() const { + return this->termination_status_; + } }; #endif diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index a9283c861ce..7fff6ca64a7 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -2422,14 +2422,14 @@ bool HighsMipSolverData::checkLimits(int64_t nodeOffset) const { if (!mipsolver.submip) { highsLogUser(options.log_options, HighsLogType::kInfo, "instance%d: terminated? %6.4f (MIP)\n", int(this->mipRaceMyInstance()), this->mipsolver.timer_.read()); - assert(this->terminatedNw() == this->terminatedNw()); - if (this->terminatedNw()) { + assert(this->terminated() == this->terminated()); + if (this->terminated()) { highsLogUser(options.log_options, HighsLogType::kInfo, "instance%d: terminated %6.4f (MIP)\n", int(this->mipRaceMyInstance()), this->mipsolver.timer_.read()); return true; } - assert(this->terminatedNw() == this->terminatedNw()); - if (this->terminatedNw()) return true; + assert(this->terminated() == this->terminated()); + if (this->terminated()) return true; } // Possible user interrupt @@ -2734,14 +2734,14 @@ void HighsMipSolverData::mipRaceReport() const { if (mipsolver.mip_race_.record) mipsolver.mip_race_.report(); } -void HighsMipSolverData::terminateNw() { +void HighsMipSolverData::terminate() { if (mipsolver.terminator_.num_instance <= 0) return; - mipsolver.terminator_.terminateNw(); + mipsolver.terminator_.terminate(); } -bool HighsMipSolverData::terminatedNw() const { +bool HighsMipSolverData::terminated() const { if (mipsolver.terminator_.num_instance > 0) - mipsolver.termination_status_ = mipsolver.terminator_.terminatedNw(); + mipsolver.termination_status_ = mipsolver.terminator_.terminated(); return mipsolver.termination_status_ != HighsModelStatus::kNotset; } @@ -3033,13 +3033,13 @@ void HighsTerminator::initialise(HighsInt num_instance_, this->record = record_; } -void HighsTerminator::terminateNw() { +void HighsTerminator::terminate() { assert(this->record); assert(this->my_instance < this->num_instance); this->record[this->my_instance] = HighsModelStatus::kHighsInterrupt; } -HighsModelStatus HighsTerminator::terminatedNw() const { +HighsModelStatus HighsTerminator::terminated() const { assert(this->record); for (HighsInt instance = 0; instance < this->num_instance; instance++) { if (this->record[instance] != HighsModelStatus::kNotset) @@ -3048,7 +3048,7 @@ HighsModelStatus HighsTerminator::terminatedNw() const { return HighsModelStatus::kNotset; } -bool HighsTerminator::notTerminatedNw() const { +bool HighsTerminator::notTerminated() const { assert(this->record); for (HighsInt instance = 0; instance < this->num_instance; instance++) { if (this->record[instance] != HighsModelStatus::kNotset) diff --git a/highs/mip/HighsMipSolverData.h b/highs/mip/HighsMipSolverData.h index a4504706385..06549b416fd 100644 --- a/highs/mip/HighsMipSolverData.h +++ b/highs/mip/HighsMipSolverData.h @@ -308,8 +308,8 @@ struct HighsMipSolverData { std::vector& solution); void mipRaceReport() const; - void terminateNw(); - bool terminatedNw() const; + void terminate(); + bool terminated() const; }; #endif From 9948f3cb8c34f937c4edd71a97112a0c7e05378e Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 21 Jul 2025 16:33:17 +0100 Subject: [PATCH 33/58] Not so ambitious... --- highs/lp_data/Highs.cpp | 2 +- highs/mip/HighsMipSolver.cpp | 2 +- highs/mip/HighsMipSolver.h | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index bcdb1560ba9..4fbdefbe6e7 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -4042,7 +4042,7 @@ HighsStatus Highs::callSolveMip() { MipRaceRecord mip_race_record; mip_race_record.initialise(mip_race_concurrency, lp.num_col_); // Set up the shared memory for the concurrent MIP terminator - auto terminator_record = solver.initialiseRecord(mip_race_concurrency); + auto terminator_record = solver.initialiseTerminatorRecord(mip_race_concurrency); // Don't allow callbacks for workers HighsCallback worker_callback = callback_; worker_callback.clear(); diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index e071ac8eef0..d7344f9f194 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -999,7 +999,7 @@ void HighsMipSolver::initialiseTerminator(HighsInt num_instance_, this->terminator_.initialise(num_instance_, my_instance_, record_); } -std::vector HighsMipSolver::initialiseRecord( +std::vector HighsMipSolver::initialiseTerminatorRecord( HighsInt num_instance) const { std::vector record(num_instance, HighsModelStatus::kNotset); return record; diff --git a/highs/mip/HighsMipSolver.h b/highs/mip/HighsMipSolver.h index e920676d514..32050ef2d08 100644 --- a/highs/mip/HighsMipSolver.h +++ b/highs/mip/HighsMipSolver.h @@ -165,7 +165,8 @@ class HighsMipSolver { const std::vector* pass_row_value, double& bound_violation, double& row_violation, double& integrality_violation, HighsCDouble& obj) const; - std::vector initialiseRecord(HighsInt num_instance) const; + + std::vector initialiseTerminatorRecord(HighsInt num_instance) const; void initialiseTerminator(HighsInt num_instance_ = 0, HighsInt my_instance_ = kNoThreadInstance, HighsModelStatus* record_ = nullptr); From 4ad6e3b72e2efa3f4ef8486c0e1037f4b339b9db Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 21 Jul 2025 18:26:29 +0100 Subject: [PATCH 34/58] Use HighsTerminator::my_instance --- highs/mip/HighsMipSolver.cpp | 34 +++++++++++++++++--------------- highs/mip/HighsMipSolver.h | 4 ++-- highs/mip/HighsMipSolverData.cpp | 26 +++++++----------------- highs/mip/HighsMipSolverData.h | 6 ++++-- 4 files changed, 31 insertions(+), 39 deletions(-) diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index d7344f9f194..c49f7069a0a 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -690,23 +690,25 @@ void HighsMipSolver::run() { void HighsMipSolver::cleanupSolve() { if (!submip) { - if (terminator_.notTerminated()) { - // No other instance has terminated the MIP race, so terminate - // it - highsLogUser(options_mip_->log_options, HighsLogType::kInfo, - "instance%d: terminate %6.4f (MIP)\n", - int(this->mipdata_->mipRaceMyInstance()), - this->timer_.read()); - terminator_.terminate(); - } else { - // Indicate that this MIP race instance has been interrupted - highsLogUser(options_mip_->log_options, HighsLogType::kInfo, - "instance%d: terminated %6.4f (MIP)\n", - int(this->mipdata_->mipRaceMyInstance()), - this->timer_.read()); - modelstatus_ = HighsModelStatus::kHighsInterrupt; + if (mipdata_->terminatorActive()) { + if (!mipdata_->terminatorTerminated()) { + // No other instance has terminated the MIP race, so terminate + // it + highsLogUser(options_mip_->log_options, HighsLogType::kInfo, + "instance%d: terminate %6.4f (MIP)\n", + int(this->mipdata_->mipRaceMyInstance()), + this->timer_.read()); + mipdata_->terminatorTerminate(); + } else { + // Indicate that this instance has been interrupted + highsLogUser(options_mip_->log_options, HighsLogType::kInfo, + "instance%d: terminated %6.4f (MIP)\n", + int(this->mipdata_->mipRaceMyInstance()), + this->timer_.read()); + modelstatus_ = HighsModelStatus::kHighsInterrupt; + } + if (mipdata_->mipRaceActive()) mipdata_->mipRaceReport(); } - mipdata_->mipRaceReport(); } // Force a final logging line diff --git a/highs/mip/HighsMipSolver.h b/highs/mip/HighsMipSolver.h index 32050ef2d08..2e0082daa13 100644 --- a/highs/mip/HighsMipSolver.h +++ b/highs/mip/HighsMipSolver.h @@ -67,8 +67,8 @@ struct HighsTerminator { void initialise(HighsInt num_instance_, HighsInt my_instance_, HighsModelStatus* record_); void terminate(); - HighsModelStatus terminated() const; - bool notTerminated() const; + bool terminated() const; + HighsModelStatus terminationStatus() const; void report(const HighsLogOptions log_options) const; }; diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index 7fff6ca64a7..fab2997e351 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -2422,14 +2422,11 @@ bool HighsMipSolverData::checkLimits(int64_t nodeOffset) const { if (!mipsolver.submip) { highsLogUser(options.log_options, HighsLogType::kInfo, "instance%d: terminated? %6.4f (MIP)\n", int(this->mipRaceMyInstance()), this->mipsolver.timer_.read()); - assert(this->terminated() == this->terminated()); - if (this->terminated()) { + if (this->terminatorTerminated()) { highsLogUser(options.log_options, HighsLogType::kInfo, "instance%d: terminated %6.4f (MIP)\n", int(this->mipRaceMyInstance()), this->mipsolver.timer_.read()); return true; } - assert(this->terminated() == this->terminated()); - if (this->terminated()) return true; } // Possible user interrupt @@ -2734,14 +2731,14 @@ void HighsMipSolverData::mipRaceReport() const { if (mipsolver.mip_race_.record) mipsolver.mip_race_.report(); } -void HighsMipSolverData::terminate() { - if (mipsolver.terminator_.num_instance <= 0) return; +void HighsMipSolverData::terminatorTerminate() { + assert(mipsolver.terminator_.num_instance > 0); mipsolver.terminator_.terminate(); } -bool HighsMipSolverData::terminated() const { - if (mipsolver.terminator_.num_instance > 0) - mipsolver.termination_status_ = mipsolver.terminator_.terminated(); +bool HighsMipSolverData::terminatorTerminated() const { + if (this->terminatorActive()) + mipsolver.termination_status_ = mipsolver.terminator_.terminationStatus(); return mipsolver.termination_status_ != HighsModelStatus::kNotset; } @@ -3039,7 +3036,7 @@ void HighsTerminator::terminate() { this->record[this->my_instance] = HighsModelStatus::kHighsInterrupt; } -HighsModelStatus HighsTerminator::terminated() const { +HighsModelStatus HighsTerminator::terminationStatus() const { assert(this->record); for (HighsInt instance = 0; instance < this->num_instance; instance++) { if (this->record[instance] != HighsModelStatus::kNotset) @@ -3048,15 +3045,6 @@ HighsModelStatus HighsTerminator::terminated() const { return HighsModelStatus::kNotset; } -bool HighsTerminator::notTerminated() const { - assert(this->record); - for (HighsInt instance = 0; instance < this->num_instance; instance++) { - if (this->record[instance] != HighsModelStatus::kNotset) - return false; - } - return true; -} - void HighsTerminator::report(const HighsLogOptions log_options) const { highsLogUser(log_options, HighsLogType::kInfo, "\nTerminator: "); for (HighsInt instance = 0; instance < this->num_instance; instance++) diff --git a/highs/mip/HighsMipSolverData.h b/highs/mip/HighsMipSolverData.h index 06549b416fd..6387c3a6cc8 100644 --- a/highs/mip/HighsMipSolverData.h +++ b/highs/mip/HighsMipSolverData.h @@ -302,14 +302,16 @@ struct HighsMipSolverData { const ExternalMipSolutionQueryOrigin external_solution_query_origin); HighsInt mipRaceConcurrency() const; + bool mipRaceActive() const { return mipRaceConcurrency() > 0; } HighsInt mipRaceMyInstance() const; void mipRaceUpdate(); HighsInt mipRaceNewSolution(const HighsInt instance, double& objective_value, std::vector& solution); void mipRaceReport() const; - void terminate(); - bool terminated() const; + void terminatorTerminate(); + bool terminatorTerminated() const; + bool terminatorActive() const { return mipsolver.terminator_.num_instance > 0; } }; #endif From e895296e3e022208711f75eedca948bebee020a0 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 21 Jul 2025 19:03:09 +0100 Subject: [PATCH 35/58] Cleaned up; formatted --- check/TestMipSolver.cpp | 1 - highs/lp_data/Highs.cpp | 6 ++-- highs/mip/HighsMipSolver.cpp | 38 ++++++++++---------- highs/mip/HighsMipSolver.h | 4 ++- highs/mip/HighsMipSolverData.cpp | 60 +++++++++++++++++++------------- highs/mip/HighsMipSolverData.h | 7 ++-- 6 files changed, 65 insertions(+), 51 deletions(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index 65e956ad4f6..638f5b1434f 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -1047,7 +1047,6 @@ TEST_CASE("mip-race", "[highs_test_mip_solver]") { if (ci_test) h.setOptionValue("output_flag", dev_run); const HighsInt mip_race_concurrency = ci_test ? 2 : 4; h.setOptionValue("mip_race_concurrency", mip_race_concurrency); - // h.setOptionValue("mip_race_read_solutions", false); REQUIRE(h.readModel(model_file) == HighsStatus::kOk); REQUIRE(h.run() == HighsStatus::kOk); diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index 4fbdefbe6e7..86bcdfee334 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -4042,7 +4042,8 @@ HighsStatus Highs::callSolveMip() { MipRaceRecord mip_race_record; mip_race_record.initialise(mip_race_concurrency, lp.num_col_); // Set up the shared memory for the concurrent MIP terminator - auto terminator_record = solver.initialiseTerminatorRecord(mip_race_concurrency); + auto terminator_record = + solver.initialiseTerminatorRecord(mip_race_concurrency); // Don't allow callbacks for workers HighsCallback worker_callback = callback_; worker_callback.clear(); @@ -4066,7 +4067,8 @@ HighsStatus Highs::callSolveMip() { instance_options.random_seed = options_.random_seed + instance; std::string worker_log_file = "mip_worker" + std::to_string(instance) + ".log"; - highsOpenLogFile(instance_options, worker_log_file); + if (options_.output_flag) + highsOpenLogFile(instance_options, worker_log_file); worker_options.push_back(instance_options); /* HighsMipSolver worker_instance(worker_callback, worker_options[instance], diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index c49f7069a0a..4a4dc61ef39 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -689,26 +689,26 @@ void HighsMipSolver::run() { } void HighsMipSolver::cleanupSolve() { - if (!submip) { - if (mipdata_->terminatorActive()) { - if (!mipdata_->terminatorTerminated()) { - // No other instance has terminated the MIP race, so terminate - // it - highsLogUser(options_mip_->log_options, HighsLogType::kInfo, - "instance%d: terminate %6.4f (MIP)\n", - int(this->mipdata_->mipRaceMyInstance()), - this->timer_.read()); - mipdata_->terminatorTerminate(); - } else { - // Indicate that this instance has been interrupted - highsLogUser(options_mip_->log_options, HighsLogType::kInfo, - "instance%d: terminated %6.4f (MIP)\n", - int(this->mipdata_->mipRaceMyInstance()), - this->timer_.read()); - modelstatus_ = HighsModelStatus::kHighsInterrupt; - } - if (mipdata_->mipRaceActive()) mipdata_->mipRaceReport(); + if (!submip && mipdata_->terminatorActive()) { + if (!mipdata_->terminatorTerminated()) { + // No other instance has terminated the MIP race, so terminate + // it + highsLogUser(options_mip_->log_options, HighsLogType::kInfo, + "instance%d: terminate %6.4f (MIP)\n", + int(this->mipdata_->terminatorMyInstance()), + this->timer_.read()); + mipdata_->terminatorTerminate(); + } else { + // Indicate that this instance has been interrupted + highsLogUser(options_mip_->log_options, HighsLogType::kInfo, + "instance%d: terminated %6.4f (MIP)\n", + int(this->mipdata_->terminatorMyInstance()), + this->timer_.read()); + modelstatus_ = HighsModelStatus::kHighsInterrupt; } + mipdata_->terminatorReport(); + // Report on any active MIP race + mipdata_->mipRaceReport(); } // Force a final logging line diff --git a/highs/mip/HighsMipSolver.h b/highs/mip/HighsMipSolver.h index 2e0082daa13..86838eddeeb 100644 --- a/highs/mip/HighsMipSolver.h +++ b/highs/mip/HighsMipSolver.h @@ -66,6 +66,7 @@ struct HighsTerminator { void clear(); void initialise(HighsInt num_instance_, HighsInt my_instance_, HighsModelStatus* record_); + HighsInt concurrency() const; void terminate(); bool terminated() const; HighsModelStatus terminationStatus() const; @@ -166,7 +167,8 @@ class HighsMipSolver { double& bound_violation, double& row_violation, double& integrality_violation, HighsCDouble& obj) const; - std::vector initialiseTerminatorRecord(HighsInt num_instance) const; + std::vector initialiseTerminatorRecord( + HighsInt num_instance) const; void initialiseTerminator(HighsInt num_instance_ = 0, HighsInt my_instance_ = kNoThreadInstance, HighsModelStatus* record_ = nullptr); diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index fab2997e351..824bfde6689 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -1331,8 +1331,10 @@ void HighsMipSolverData::performRestart() { // changed offset_ runSetup(); if (mipsolver.terminate()) { - printf("HighsMipSolverData::performRestart() mipsolver.termination_status_ = %d\n", - int(mipsolver.termination_status_)); + printf( + "HighsMipSolverData::performRestart() mipsolver.termination_status_ = " + "%d\n", + int(mipsolver.termination_status_)); return; } @@ -2387,7 +2389,7 @@ void HighsMipSolverData::evaluateRootNode() { analysis.mipTimerStart(kMipClockPerformRestart); performRestart(); analysis.mipTimerStop(kMipClockPerformRestart); - if (mipsolver.terminate()) return; + if (mipsolver.terminate()) return; ++numRestartsRoot; if (mipsolver.modelstatus_ == HighsModelStatus::kNotset) { clockOff(analysis); @@ -2419,12 +2421,16 @@ bool HighsMipSolverData::checkLimits(int64_t nodeOffset) const { const HighsOptions& options = *mipsolver.options_mip_; // MIP race may have terminated - if (!mipsolver.submip) { + if (!mipsolver.submip && terminatorActive()) { highsLogUser(options.log_options, HighsLogType::kInfo, - "instance%d: terminated? %6.4f (MIP)\n", int(this->mipRaceMyInstance()), this->mipsolver.timer_.read()); + "instance%d: terminated? %6.4f (MIP)\n", + int(this->terminatorMyInstance()), + this->mipsolver.timer_.read()); if (this->terminatorTerminated()) { highsLogUser(options.log_options, HighsLogType::kInfo, - "instance%d: terminated %6.4f (MIP)\n", int(this->mipRaceMyInstance()), this->mipsolver.timer_.read()); + "instance%d: terminated %6.4f (MIP)\n", + int(this->terminatorMyInstance()), + this->mipsolver.timer_.read()); return true; } } @@ -2698,12 +2704,6 @@ void HighsMipSolverData::queryExternalSolution( } } -HighsInt HighsMipSolverData::mipRaceMyInstance() const { - assert(!mipsolver.submip); - if (!mipsolver.mip_race_.record) return kNoThreadInstance; - return mipsolver.mip_race_.my_instance; -} - HighsInt HighsMipSolverData::mipRaceConcurrency() const { assert(!mipsolver.submip); if (!mipsolver.mip_race_.record) return 0; @@ -2727,21 +2727,33 @@ HighsInt HighsMipSolverData::mipRaceNewSolution(const HighsInt instance, void HighsMipSolverData::mipRaceReport() const { assert(!mipsolver.submip); - if (mipsolver.terminator_.record) mipsolver.terminator_.report(mipsolver.options_mip_->log_options); - if (mipsolver.mip_race_.record) mipsolver.mip_race_.report(); + if (mipsolver.mip_race_.record) mipsolver.mip_race_.report(); +} + +HighsInt HighsMipSolverData::terminatorConcurrency() const { + return mipsolver.terminator_.num_instance; +} + +HighsInt HighsMipSolverData::terminatorMyInstance() const { + return mipsolver.terminator_.my_instance; } void HighsMipSolverData::terminatorTerminate() { - assert(mipsolver.terminator_.num_instance > 0); + assert(terminatorActive()); mipsolver.terminator_.terminate(); } bool HighsMipSolverData::terminatorTerminated() const { - if (this->terminatorActive()) + if (this->terminatorActive()) mipsolver.termination_status_ = mipsolver.terminator_.terminationStatus(); return mipsolver.termination_status_ != HighsModelStatus::kNotset; } +void HighsMipSolverData::terminatorReport() const { + if (this->terminatorActive()) + mipsolver.terminator_.report(mipsolver.options_mip_->log_options); +} + static double possInfRelDiff(const double v0, const double v1, const double den) { double rel_diff; @@ -2918,9 +2930,7 @@ HighsInt MipRaceIncumbent::read(const HighsInt last_incumbent_read, : kMipRaceNoSolution; } -void MipRaceRecord::clear() { - this->incumbent.clear(); -} +void MipRaceRecord::clear() { this->incumbent.clear(); } void MipRaceRecord::initialise(const HighsInt mip_race_concurrency, const HighsInt num_col) { @@ -3022,14 +3032,15 @@ void HighsTerminator::clear() { this->record = nullptr; } -void HighsTerminator::initialise(HighsInt num_instance_, - HighsInt my_instance_, - HighsModelStatus* record_) { +void HighsTerminator::initialise(HighsInt num_instance_, HighsInt my_instance_, + HighsModelStatus* record_) { this->num_instance = num_instance_; this->my_instance = my_instance_; this->record = record_; } +HighsInt HighsTerminator::concurrency() const { return this->num_instance; } + void HighsTerminator::terminate() { assert(this->record); assert(this->my_instance < this->num_instance); @@ -3047,9 +3058,8 @@ HighsModelStatus HighsTerminator::terminationStatus() const { void HighsTerminator::report(const HighsLogOptions log_options) const { highsLogUser(log_options, HighsLogType::kInfo, "\nTerminator: "); - for (HighsInt instance = 0; instance < this->num_instance; instance++) + for (HighsInt instance = 0; instance < this->num_instance; instance++) highsLogUser(log_options, HighsLogType::kInfo, " %20d", - int(this->record[instance])); + int(this->record[instance])); highsLogUser(log_options, HighsLogType::kInfo, "\n"); } - diff --git a/highs/mip/HighsMipSolverData.h b/highs/mip/HighsMipSolverData.h index 6387c3a6cc8..6f16c00186a 100644 --- a/highs/mip/HighsMipSolverData.h +++ b/highs/mip/HighsMipSolverData.h @@ -302,16 +302,17 @@ struct HighsMipSolverData { const ExternalMipSolutionQueryOrigin external_solution_query_origin); HighsInt mipRaceConcurrency() const; - bool mipRaceActive() const { return mipRaceConcurrency() > 0; } - HighsInt mipRaceMyInstance() const; void mipRaceUpdate(); HighsInt mipRaceNewSolution(const HighsInt instance, double& objective_value, std::vector& solution); void mipRaceReport() const; + HighsInt terminatorConcurrency() const; + bool terminatorActive() const { return terminatorConcurrency() > 0; } + HighsInt terminatorMyInstance() const; void terminatorTerminate(); bool terminatorTerminated() const; - bool terminatorActive() const { return mipsolver.terminator_.num_instance > 0; } + void terminatorReport() const; }; #endif From 7058fc4847f552d523cbd2c8014c5fc932d8c7f3 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 21 Jul 2025 23:56:41 +0100 Subject: [PATCH 36/58] Introduced HighsMipSolver::initialiseMipRace --- check/TestMipSolver.cpp | 2 +- highs/mip/HighsMipSolver.cpp | 22 ++++++++++++++++++---- highs/mip/HighsMipSolver.h | 4 ++++ highs/mip/HighsMipSolverData.cpp | 10 +++++++--- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index 638f5b1434f..84eefa5d03f 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -1037,7 +1037,7 @@ TEST_CASE("issue-2432", "[highs_test_mip_solver]") { } TEST_CASE("mip-race", "[highs_test_mip_solver]") { - const bool ci_test = true; // false;// + const bool ci_test = false;//true; // const std::string model = ci_test ? "flugpl" : "fiball"; // "neos-3381206-awhea"; const std::string model_file = diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index 4a4dc61ef39..741aa9d971c 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -44,7 +44,12 @@ HighsMipSolver::HighsMipSolver(HighsCallback& callback, implicinit(nullptr) { assert(!submip || submip_level > 0); max_submip_level = 0; + // Initialise empty terminator, since this sets termination_status_ + // to HighsModelStatus::kNotset... initialiseTerminator(); + // ... and empty MIP race + initialiseMipRace(); + assert(termination_status_ == HighsModelStatus::kNotset); if (solution.value_valid) { #ifndef NDEBUG // MIP solver doesn't check row residuals, but they should be OK @@ -994,15 +999,24 @@ bool HighsMipSolver::solutionFeasible(const HighsLp* lp, return feasible; } +std::vector HighsMipSolver::initialiseTerminatorRecord( + HighsInt num_instance) const { + std::vector record(num_instance, HighsModelStatus::kNotset); + return record; +} + void HighsMipSolver::initialiseTerminator(HighsInt num_instance_, HighsInt my_instance_, HighsModelStatus* record_) { this->termination_status_ = HighsModelStatus::kNotset; + this->terminator_.clear(); this->terminator_.initialise(num_instance_, my_instance_, record_); } -std::vector HighsMipSolver::initialiseTerminatorRecord( - HighsInt num_instance) const { - std::vector record(num_instance, HighsModelStatus::kNotset); - return record; +void HighsMipSolver::initialiseMipRace(const HighsInt mip_race_concurrency, + const HighsInt my_instance, + MipRaceRecord* record) { + this->mip_race_.clear(); + this->mip_race_.initialise(mip_race_concurrency, my_instance, record, + this->options_mip_->log_options); } diff --git a/highs/mip/HighsMipSolver.h b/highs/mip/HighsMipSolver.h index 86838eddeeb..dab26acfffd 100644 --- a/highs/mip/HighsMipSolver.h +++ b/highs/mip/HighsMipSolver.h @@ -167,6 +167,10 @@ class HighsMipSolver { double& bound_violation, double& row_violation, double& integrality_violation, HighsCDouble& obj) const; + void initialiseMipRace(const HighsInt mip_race_concurrency = 0, + const HighsInt my_instance_ = kNoThreadInstance, + MipRaceRecord* record_ = nullptr); + std::vector initialiseTerminatorRecord( HighsInt num_instance) const; void initialiseTerminator(HighsInt num_instance_ = 0, diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index 824bfde6689..27c3c2f8966 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -2937,8 +2937,11 @@ void MipRaceRecord::initialise(const HighsInt mip_race_concurrency, this->clear(); MipRaceIncumbent incumbent_; incumbent_.initialise(num_col); - for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) + // Loop from 1... + for (HighsInt instance = 1; instance < mip_race_concurrency; instance++) this->incumbent.push_back(incumbent_); + // ... and move incumbent_ to complete the vector of incumbents + this->incumbent.push_back(std::move(incumbent_)); } HighsInt MipRaceRecord::concurrency() const { @@ -2980,11 +2983,11 @@ void MipRace::initialise(const HighsInt mip_race_concurrency, const HighsInt my_instance_, MipRaceRecord* record_, const HighsLogOptions log_options_) { this->clear(); - assert(mip_race_concurrency > 0); this->my_instance = my_instance_; this->record = record_; this->log_options = log_options_; - this->last_incumbent_read.assign(mip_race_concurrency, kMipRaceNoSolution); + if (mip_race_concurrency > 0) + this->last_incumbent_read.assign(mip_race_concurrency, kMipRaceNoSolution); } HighsInt MipRace::concurrency() const { @@ -3034,6 +3037,7 @@ void HighsTerminator::clear() { void HighsTerminator::initialise(HighsInt num_instance_, HighsInt my_instance_, HighsModelStatus* record_) { + this->clear(); this->num_instance = num_instance_; this->my_instance = my_instance_; this->record = record_; From 18b43565cf01ddd552abc17fcf39146895ac976f Mon Sep 17 00:00:00 2001 From: JAJHall Date: Tue, 22 Jul 2025 00:03:56 +0100 Subject: [PATCH 37/58] Ready to externd terminator to sub-MIPs! --- check/TestMipSolver.cpp | 2 +- highs/lp_data/Highs.cpp | 10 ++++------ highs/mip/HighsMipSolver.cpp | 6 +++--- highs/mip/HighsMipSolver.h | 4 ++-- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index 84eefa5d03f..638f5b1434f 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -1037,7 +1037,7 @@ TEST_CASE("issue-2432", "[highs_test_mip_solver]") { } TEST_CASE("mip-race", "[highs_test_mip_solver]") { - const bool ci_test = false;//true; // + const bool ci_test = true; // false;// const std::string model = ci_test ? "flugpl" : "fiball"; // "neos-3381206-awhea"; const std::string model_file = diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index 86bcdfee334..f4e43042fef 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -4079,11 +4079,10 @@ HighsStatus Highs::callSolveMip() { 0, mip_race_concurrency, [&](HighsInt start, HighsInt end) { for (HighsInt instance = start; instance < end; instance++) { if (instance == 0) { - solver.mip_race_.initialise(mip_race_concurrency, instance, - &mip_race_record, - options_.log_options); solver.initialiseTerminator(mip_race_concurrency, instance, terminator_record.data()); + solver.initialiseMipRace(mip_race_concurrency, instance, + &mip_race_record); double this_time = timer_.read(); highsLogUser(options_.log_options, HighsLogType::kInfo, "instance0: call run() %f6.4\n", this_time); @@ -4094,11 +4093,10 @@ HighsStatus Highs::callSolveMip() { } else { HighsMipSolver worker(worker_callback, worker_options[instance], lp, solution_); - worker.mip_race_.initialise(mip_race_concurrency, instance, - &mip_race_record, - worker_options[instance].log_options); worker.initialiseTerminator(mip_race_concurrency, instance, terminator_record.data()); + worker.initialiseMipRace(mip_race_concurrency, instance, + &mip_race_record); double this_time = timer_.read(); highsLogUser(options_.log_options, HighsLogType::kInfo, "instance%d: call run() %f6.4\n", int(instance), diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index 741aa9d971c..71324cd39bd 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -1014,9 +1014,9 @@ void HighsMipSolver::initialiseTerminator(HighsInt num_instance_, } void HighsMipSolver::initialiseMipRace(const HighsInt mip_race_concurrency, - const HighsInt my_instance, - MipRaceRecord* record) { + const HighsInt my_instance, + MipRaceRecord* record) { this->mip_race_.clear(); this->mip_race_.initialise(mip_race_concurrency, my_instance, record, - this->options_mip_->log_options); + this->options_mip_->log_options); } diff --git a/highs/mip/HighsMipSolver.h b/highs/mip/HighsMipSolver.h index dab26acfffd..9f04d83f05e 100644 --- a/highs/mip/HighsMipSolver.h +++ b/highs/mip/HighsMipSolver.h @@ -169,8 +169,8 @@ class HighsMipSolver { void initialiseMipRace(const HighsInt mip_race_concurrency = 0, const HighsInt my_instance_ = kNoThreadInstance, - MipRaceRecord* record_ = nullptr); - + MipRaceRecord* record_ = nullptr); + std::vector initialiseTerminatorRecord( HighsInt num_instance) const; void initialiseTerminator(HighsInt num_instance_ = 0, From 3487a2a1aedb6cb6c776a33db933a470f58b2fc6 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Tue, 22 Jul 2025 09:57:48 +0100 Subject: [PATCH 38/58] Now propagating terminator to sub-MIPs, but still have to test for it! --- highs/mip/HighsMipSolver.cpp | 9 +++++++++ highs/mip/HighsMipSolver.h | 1 + highs/mip/HighsPrimalHeuristics.cpp | 18 ++++++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index 71324cd39bd..c1db8fada91 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -1013,6 +1013,15 @@ void HighsMipSolver::initialiseTerminator(HighsInt num_instance_, this->terminator_.initialise(num_instance_, my_instance_, record_); } +void HighsMipSolver::initialiseTerminator(const HighsMipSolver& mip_solver) { + this->terminator_.clear(); + if (!mip_solver.mipdata_->terminatorActive()) return; + assert(mip_solver.mipdata_->terminatorConcurrency() > 0); + this->initialiseTerminator(mip_solver.mipdata_->terminatorConcurrency(), + mip_solver.mipdata_->terminatorMyInstance(), + mip_solver.terminator_.record); +} + void HighsMipSolver::initialiseMipRace(const HighsInt mip_race_concurrency, const HighsInt my_instance, MipRaceRecord* record) { diff --git a/highs/mip/HighsMipSolver.h b/highs/mip/HighsMipSolver.h index 9f04d83f05e..24905586870 100644 --- a/highs/mip/HighsMipSolver.h +++ b/highs/mip/HighsMipSolver.h @@ -176,6 +176,7 @@ class HighsMipSolver { void initialiseTerminator(HighsInt num_instance_ = 0, HighsInt my_instance_ = kNoThreadInstance, HighsModelStatus* record_ = nullptr); + void initialiseTerminator(const HighsMipSolver& mip_solver); bool terminate() const { return this->termination_status_ != HighsModelStatus::kNotset; } diff --git a/highs/mip/HighsPrimalHeuristics.cpp b/highs/mip/HighsPrimalHeuristics.cpp index 80390d18a75..e493d209801 100644 --- a/highs/mip/HighsPrimalHeuristics.cpp +++ b/highs/mip/HighsPrimalHeuristics.cpp @@ -142,6 +142,9 @@ bool HighsPrimalHeuristics::solveSubMip( mipsolver.analysis_.mipTimerStart(kMipClockSubMipSolve); HighsMipSolver submipsolver(*mipsolver.callback_, submipoptions, submip, solution, true, mipsolver.submip_level + 1); + // Initialise termination_status_ and propagate any terminator to + // the sub-MIP + submipsolver.initialiseTerminator(mipsolver); submipsolver.rootbasis = &basis; HighsPseudocostInitialization pscostinit(mipsolver.mipdata_->pseudocost, 1); submipsolver.pscostinit = &pscostinit; @@ -152,6 +155,21 @@ bool HighsPrimalHeuristics::solveSubMip( mipsolver.max_submip_level = std::max(submipsolver.max_submip_level + 1, mipsolver.max_submip_level); if (!mipsolver.submip) mipsolver.analysis_.mipTimerStop(kMipClockSubMipSolve); + // 22/07/25: Seems impossible for submipsolver.mipdata_ to be a null + // pointer after calling HighsMipSolver::run(), and assert isn't + // triggered for anything in ctest, but use direct test of + // submipsolver.termination_status_, rather than + // submipsolver.mipdata_.terminatorTerminated() + if (!submipsolver.mipdata_) { + printf("HighsPrimalHeuristics::solveSubMip: submipsolver.mipdata_ is nullptr\n"); + assert(submipsolver.mipdata_); + } + if (submipsolver.termination_status_ != HighsModelStatus::kNotset) { + printf("HighsPrimalHeuristics::solveSubMip: termination status is %d\n", int(submipsolver.termination_status_)); + mipsolver.termination_status_ = submipsolver.termination_status_; + assert(111==333); + return; + } if (submipsolver.mipdata_) { double numUnfixed = mipsolver.mipdata_->integral_cols.size() + mipsolver.mipdata_->continuous_cols.size(); From c502d1fe511d4f1b2151c027020be637eaccf87f Mon Sep 17 00:00:00 2001 From: JAJHall Date: Tue, 22 Jul 2025 10:47:44 +0100 Subject: [PATCH 39/58] Flip termination conditional in HighsMipSolver::cleanupSolve() in preparation for limiting termination by sub-MIPs --- check/TestMipSolver.cpp | 4 ++-- highs/mip/HighsMipSolver.cpp | 19 ++++++++++++------- highs/mip/HighsMipSolverData.cpp | 15 ++++++++------- highs/mip/HighsPrimalHeuristics.cpp | 10 +++++++--- 4 files changed, 29 insertions(+), 19 deletions(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index 638f5b1434f..8f734601e52 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -1038,13 +1038,13 @@ TEST_CASE("issue-2432", "[highs_test_mip_solver]") { TEST_CASE("mip-race", "[highs_test_mip_solver]") { const bool ci_test = true; // false;// - const std::string model = ci_test ? "flugpl" : "fiball"; + const std::string model = ci_test ? "rgn" : "fiball"; //flugpl // "neos-3381206-awhea"; const std::string model_file = ci_test ? std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps" : "/srv/miplib2017/" + model + ".mps.gz"; Highs h; - if (ci_test) h.setOptionValue("output_flag", dev_run); + // if (ci_test) h.setOptionValue("output_flag", dev_run); const HighsInt mip_race_concurrency = ci_test ? 2 : 4; h.setOptionValue("mip_race_concurrency", mip_race_concurrency); REQUIRE(h.readModel(model_file) == HighsStatus::kOk); diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index c1db8fada91..1d2e10262e1 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -302,6 +302,10 @@ void HighsMipSolver::run() { mipdata_->heuristics.flushStatistics(); analysis_.mipTimerStop(kMipClockDivePrimalHeuristics); + if (mipdata_->terminatorTerminated()) { + cleanupSolve(); + return; + } } } @@ -694,21 +698,22 @@ void HighsMipSolver::run() { } void HighsMipSolver::cleanupSolve() { - if (!submip && mipdata_->terminatorActive()) { + if (mipdata_->terminatorActive()) { if (!mipdata_->terminatorTerminated()) { - // No other instance has terminated the MIP race, so terminate - // it + // No other instance has terminated, so terminate it highsLogUser(options_mip_->log_options, HighsLogType::kInfo, - "instance%d: terminate %6.4f (MIP)\n", + "instance%d: terminate %6.4f (%sMIP)\n", int(this->mipdata_->terminatorMyInstance()), - this->timer_.read()); + this->timer_.read(), + submip ? "sub-" : ""); mipdata_->terminatorTerminate(); } else { // Indicate that this instance has been interrupted highsLogUser(options_mip_->log_options, HighsLogType::kInfo, - "instance%d: terminated %6.4f (MIP)\n", + "instance%d: terminated %6.4f (%sMIP)\n", int(this->mipdata_->terminatorMyInstance()), - this->timer_.read()); + this->timer_.read(), + submip ? "sub-" : ""); modelstatus_ = HighsModelStatus::kHighsInterrupt; } mipdata_->terminatorReport(); diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index 27c3c2f8966..dd6c2c5db01 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -2420,17 +2420,19 @@ void HighsMipSolverData::evaluateRootNode() { bool HighsMipSolverData::checkLimits(int64_t nodeOffset) const { const HighsOptions& options = *mipsolver.options_mip_; - // MIP race may have terminated - if (!mipsolver.submip && terminatorActive()) { + // This MIP instance may have been terminated + if (terminatorActive()) { highsLogUser(options.log_options, HighsLogType::kInfo, - "instance%d: terminated? %6.4f (MIP)\n", + "instance%d: terminated? %6.4f (%sMIP)\n", int(this->terminatorMyInstance()), - this->mipsolver.timer_.read()); + this->mipsolver.timer_.read(), + mipsolver.submip ? "sub-" : ""); if (this->terminatorTerminated()) { highsLogUser(options.log_options, HighsLogType::kInfo, - "instance%d: terminated %6.4f (MIP)\n", + "instance%d: terminated %6.4f (%sMIP)\n", int(this->terminatorMyInstance()), - this->mipsolver.timer_.read()); + this->mipsolver.timer_.read(), + mipsolver.submip ? "sub-" : ""); return true; } } @@ -2726,7 +2728,6 @@ HighsInt HighsMipSolverData::mipRaceNewSolution(const HighsInt instance, } void HighsMipSolverData::mipRaceReport() const { - assert(!mipsolver.submip); if (mipsolver.mip_race_.record) mipsolver.mip_race_.report(); } diff --git a/highs/mip/HighsPrimalHeuristics.cpp b/highs/mip/HighsPrimalHeuristics.cpp index e493d209801..332a39e36ac 100644 --- a/highs/mip/HighsPrimalHeuristics.cpp +++ b/highs/mip/HighsPrimalHeuristics.cpp @@ -145,6 +145,8 @@ bool HighsPrimalHeuristics::solveSubMip( // Initialise termination_status_ and propagate any terminator to // the sub-MIP submipsolver.initialiseTerminator(mipsolver); + printf("HighsPrimalHeuristics::solveSubMip: %d submipsolver.termination_status_ = %d\n", + int(submipsolver.terminator_.my_instance), int(submipsolver.termination_status_)); submipsolver.rootbasis = &basis; HighsPseudocostInitialization pscostinit(mipsolver.mipdata_->pseudocost, 1); submipsolver.pscostinit = &pscostinit; @@ -165,10 +167,10 @@ bool HighsPrimalHeuristics::solveSubMip( assert(submipsolver.mipdata_); } if (submipsolver.termination_status_ != HighsModelStatus::kNotset) { - printf("HighsPrimalHeuristics::solveSubMip: termination status is %d\n", int(submipsolver.termination_status_)); + printf("HighsPrimalHeuristics::solveSubMip: %d termination status is %d\n", + int(submipsolver.terminator_.my_instance), int(submipsolver.termination_status_)); mipsolver.termination_status_ = submipsolver.termination_status_; - assert(111==333); - return; + return false; } if (submipsolver.mipdata_) { double numUnfixed = mipsolver.mipdata_->integral_cols.size() + @@ -561,6 +563,7 @@ void HighsPrimalHeuristics::RENS(const std::vector& tmp) { 500, // std::max(50, int(0.05 * // (mipsolver.mipdata_->num_leaves))), 200 + mipsolver.mipdata_->num_nodes / 20, 12); + if (mipsolver.mipdata_->terminatorTerminated()) return; if (!solve_sub_mip_return) { int64_t new_lp_iterations = lp_iterations + heur.getLocalLpIterations(); if (new_lp_iterations + mipsolver.mipdata_->heuristic_lp_iterations > @@ -853,6 +856,7 @@ void HighsPrimalHeuristics::RINS(const std::vector& relaxationsol) { 500, // std::max(50, int(0.05 * // (mipsolver.mipdata_->num_leaves))), 200 + mipsolver.mipdata_->num_nodes / 20, 12); + if (mipsolver.mipdata_->terminatorTerminated()) return; if (!solve_sub_mip_return) { int64_t new_lp_iterations = lp_iterations + heur.getLocalLpIterations(); if (new_lp_iterations + mipsolver.mipdata_->heuristic_lp_iterations > From 6f740feb6abaf7e4da4c5e7c58b0c45d234b12a5 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Tue, 22 Jul 2025 11:07:35 +0100 Subject: [PATCH 40/58] Now termination is not performed by sub-MIPs, but they will act on a termination from a MIP solver --- check/TestMipSolver.cpp | 2 ++ highs/mip/HighsMipSolver.cpp | 23 +++++++++++++++-------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index 8f734601e52..f4ab28d6293 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -1049,11 +1049,13 @@ TEST_CASE("mip-race", "[highs_test_mip_solver]") { h.setOptionValue("mip_race_concurrency", mip_race_concurrency); REQUIRE(h.readModel(model_file) == HighsStatus::kOk); REQUIRE(h.run() == HighsStatus::kOk); + REQUIRE(h.getModelStatus() == HighsModelStatus::kOptimal); if (ci_test) { h.clearSolver(); h.setOptionValue("mip_race_read_solutions", false); REQUIRE(h.run() == HighsStatus::kOk); + REQUIRE(h.getModelStatus() == HighsModelStatus::kOptimal); } h.resetGlobalScheduler(true); } diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index 1d2e10262e1..6695f592815 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -699,22 +699,29 @@ void HighsMipSolver::run() { void HighsMipSolver::cleanupSolve() { if (mipdata_->terminatorActive()) { - if (!mipdata_->terminatorTerminated()) { - // No other instance has terminated, so terminate it + mipdata_->terminatorReport(); + if (mipdata_->terminatorTerminated()) { + // Indicate that this instance has been interrupted highsLogUser(options_mip_->log_options, HighsLogType::kInfo, - "instance%d: terminate %6.4f (%sMIP)\n", + "instance%d: terminated %6.4f (%sMIP)\n", int(this->mipdata_->terminatorMyInstance()), this->timer_.read(), submip ? "sub-" : ""); - mipdata_->terminatorTerminate(); - } else { - // Indicate that this instance has been interrupted + modelstatus_ = HighsModelStatus::kHighsInterrupt; + } else if (!submip) { + // When sub-MIPs call cleanupSolve(), they generally don't have + // a termination criterion for the whole MIP solver + // + // Possibly allow sub-MIPs to terminate if the time limit is + // reached + // + // No other instance has terminated, so terminate highsLogUser(options_mip_->log_options, HighsLogType::kInfo, - "instance%d: terminated %6.4f (%sMIP)\n", + "instance%d: terminate %6.4f (%sMIP)\n", int(this->mipdata_->terminatorMyInstance()), this->timer_.read(), submip ? "sub-" : ""); - modelstatus_ = HighsModelStatus::kHighsInterrupt; + mipdata_->terminatorTerminate(); } mipdata_->terminatorReport(); // Report on any active MIP race From 884f745b2ac58d29ae64bea43cb2a53b6d7750dd Mon Sep 17 00:00:00 2001 From: JAJHall Date: Tue, 22 Jul 2025 11:11:18 +0100 Subject: [PATCH 41/58] fiball now solves in 5.22 seconds --- check/TestMipSolver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index f4ab28d6293..f7ace0c73a4 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -1037,7 +1037,7 @@ TEST_CASE("issue-2432", "[highs_test_mip_solver]") { } TEST_CASE("mip-race", "[highs_test_mip_solver]") { - const bool ci_test = true; // false;// + const bool ci_test = false;//true; // const std::string model = ci_test ? "rgn" : "fiball"; //flugpl // "neos-3381206-awhea"; const std::string model_file = From 9b7bc10c33b2b5f09076d334cd56dc6628cc0905 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Tue, 22 Jul 2025 12:29:52 +0100 Subject: [PATCH 42/58] Create gap string method --- check/TestMipSolver.cpp | 4 +- highs/Highs.h | 3 ++ highs/lp_data/Highs.cpp | 39 +++-------------- highs/lp_data/HighsInterface.cpp | 72 ++++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 35 deletions(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index f7ace0c73a4..f9783719f30 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -1037,7 +1037,7 @@ TEST_CASE("issue-2432", "[highs_test_mip_solver]") { } TEST_CASE("mip-race", "[highs_test_mip_solver]") { - const bool ci_test = false;//true; // + const bool ci_test = true; // false;// const std::string model = ci_test ? "rgn" : "fiball"; //flugpl // "neos-3381206-awhea"; const std::string model_file = @@ -1051,11 +1051,13 @@ TEST_CASE("mip-race", "[highs_test_mip_solver]") { REQUIRE(h.run() == HighsStatus::kOk); REQUIRE(h.getModelStatus() == HighsModelStatus::kOptimal); + /* if (ci_test) { h.clearSolver(); h.setOptionValue("mip_race_read_solutions", false); REQUIRE(h.run() == HighsStatus::kOk); REQUIRE(h.getModelStatus() == HighsModelStatus::kOptimal); } + */ h.resetGlobalScheduler(true); } diff --git a/highs/Highs.h b/highs/Highs.h index 20035127272..d451017971b 100644 --- a/highs/Highs.h +++ b/highs/Highs.h @@ -1714,6 +1714,9 @@ class Highs { bool optionsHasHighsFiles() const; void saveHighsFiles(); void getHighsFiles(); + HighsStatus mipRaceResults(HighsMipSolverInfo& mip_solver_info, + const std::vector& worker_info, + const std::vector& mip_time); }; // Start of deprecated methods not in the Highs class diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index f4e43042fef..4ba1a507d23 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -4108,41 +4108,12 @@ HighsStatus Highs::callSolveMip() { } } }); - // Report on the solver and workers, and identify which has won! - HighsInt winning_instance = -1; - HighsModelStatus winning_model_status = HighsModelStatus::kNotset; - highsLogUser(options_.log_options, HighsLogType::kInfo, - "MIP race results:\n"); - for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) { - const HighsMipSolverInfo& solver_info = - instance == 0 ? mip_solver_info : worker_info[instance]; - HighsModelStatus instance_model_status = solver_info.modelstatus; - highsLogUser(options_.log_options, HighsLogType::kInfo, - " Solver %d has best objective %15.8g, gap %6.2f\% (time " - "= %6.2f), and status %s\n", - int(instance), solver_info.solution_objective, - 1e2 * solver_info.gap, mip_time[instance], - modelStatusToString(instance_model_status).c_str()); - if (instance_model_status != HighsModelStatus::kHighsInterrupt) { - // Definitive status for this instance, so check compatibility - // with any current winning model status - if (winning_model_status != HighsModelStatus::kNotset) { - if (winning_model_status != instance_model_status) { - highsLogUser(options_.log_options, HighsLogType::kError, - "MIP race: conflict between status \"%s\" for " - "instance %d and status \"%s\" for instance %d\n", - modelStatusToString(winning_model_status).c_str(), - int(winning_instance), - modelStatusToString(instance_model_status).c_str(), - int(instance)); - } - } else { - winning_model_status = instance_model_status; - winning_instance = instance; - } - } + // Determine the winner and report on the solution + HighsStatus call_status = this->mipRaceResults(mip_solver_info, worker_info, mip_time); + if (call_status == HighsStatus::kError) { + const bool undo_mods = true; + return returnFromOptimizeModel(HighsStatus::kError, undo_mods); } - if (winning_instance > 0) mip_solver_info = worker_info[winning_instance]; } else { // Run a single MIP solver solver.run(); diff --git a/highs/lp_data/HighsInterface.cpp b/highs/lp_data/HighsInterface.cpp index fb8f5ccee7d..707c5e2eb89 100644 --- a/highs/lp_data/HighsInterface.cpp +++ b/highs/lp_data/HighsInterface.cpp @@ -4258,3 +4258,75 @@ void HighsMipSolverInfo::clear() { this->total_lp_iterations = -kHighsSize_tInf; this->primal_dual_integral = -kHighsInf; } + +HighsStatus Highs::mipRaceResults(HighsMipSolverInfo& mip_solver_info, + const std::vector& worker_info, + const std::vector& mip_time) { + + const HighsInt mip_race_concurrency = this->options_.mip_race_concurrency; + HighsInt winning_instance = -1; + HighsModelStatus winning_model_status = HighsModelStatus::kNotset; + for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) { + const HighsMipSolverInfo& solver_info = + instance == 0 ? mip_solver_info : worker_info[instance]; + HighsModelStatus instance_model_status = solver_info.modelstatus; + highsLogUser(options_.log_options, HighsLogType::kInfo, + " Solver %d has best objective %15.8g, gap %6.2f\% (time " + "= %6.2f), and status %s\n", + int(instance), solver_info.solution_objective, + 1e2 * solver_info.gap, mip_time[instance], + modelStatusToString(instance_model_status).c_str()); + if (instance_model_status != HighsModelStatus::kHighsInterrupt) { + // Definitive status for this instance, so check compatibility + // with any current winning model status + if (winning_model_status != HighsModelStatus::kNotset) { + if (winning_model_status != instance_model_status) { + highsLogUser(options_.log_options, HighsLogType::kError, + "MIP race: conflict between status \"%s\" for " + "instance %d and status \"%s\" for instance %d\n", + modelStatusToString(winning_model_status).c_str(), + int(winning_instance), + modelStatusToString(instance_model_status).c_str(), + int(instance)); + return HighsStatus::kError; + } + } else { + winning_model_status = instance_model_status; + winning_instance = instance; + } + } + } + if (winning_instance > 0) mip_solver_info = worker_info[winning_instance]; + highsLogUser(options_.log_options, HighsLogType::kInfo, "Solving report\n"); + highsLogUser(options_.log_options, HighsLogType::kInfo, " Model %s\n", + this->model_.lp_.model_name_.c_str()); + highsLogUser(options_.log_options, HighsLogType::kInfo, " Status %s\n", + modelStatusToString(mip_solver_info.modelstatus).c_str()); + highsLogUser(options_.log_options, HighsLogType::kInfo, " Primal bound %.12g\n", + mip_solver_info.primal_bound); + highsLogUser(options_.log_options, HighsLogType::kInfo, " Dual bound %.12g\n", + mip_solver_info.dual_bound); + highsLogUser(options_.log_options, HighsLogType::kInfo, " Gap %g%% (tolerance: %g%%)\n", + 1e2*mip_solver_info.gap, 1e2*options_.mip_rel_gap); + highsLogUser(options_.log_options, HighsLogType::kInfo, " P-D integral %.12g\n", + mip_solver_info.primal_dual_integral); + highsLogUser(options_.log_options, HighsLogType::kInfo, " Solution status %.12g\n", + mip_solver_info.solution_objective); + highsLogUser(options_.log_options, HighsLogType::kInfo, " %.12g (bound viol.)\n", + mip_solver_info.bound_violation); + highsLogUser(options_.log_options, HighsLogType::kInfo, " %.12g (int. viol.)\n", + mip_solver_info.integrality_violation); + highsLogUser(options_.log_options, HighsLogType::kInfo, " %.12g (row viol.)\n", + mip_solver_info.row_violation); + highsLogUser(options_.log_options, HighsLogType::kInfo, " Timing %.2f\n", + mip_time[winning_instance]); + highsLogUser(options_.log_options, HighsLogType::kInfo, " Nodes %llu\n", + mip_solver_info.node_count); + highsLogUser(options_.log_options, HighsLogType::kInfo, " LP iterations %llu\n", + mip_solver_info.total_lp_iterations); + /* + Solution status feasible + Max sub-MIP depth 1 + */ + return HighsStatus::kOk; +} From 6570af795a76db6c52b3be97ffd4683d519dfa3a Mon Sep 17 00:00:00 2001 From: JAJHall Date: Tue, 22 Jul 2025 13:33:53 +0100 Subject: [PATCH 43/58] Instance 0 no longer logging MIP solver solution report to console, but using Highs::mipRaceResults --- check/TestMipSolver.cpp | 8 +- highs/Highs.h | 4 +- highs/lp_data/HStruct.h | 1 + highs/lp_data/Highs.cpp | 6 +- highs/lp_data/HighsInterface.cpp | 137 +++++++++++++++++----------- highs/mip/HighsMipSolver.cpp | 109 ++++++++++++---------- highs/mip/HighsMipSolver.h | 3 + highs/mip/HighsMipSolverData.cpp | 5 +- highs/mip/HighsPrimalHeuristics.cpp | 16 +++- 9 files changed, 172 insertions(+), 117 deletions(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index f9783719f30..6b07a122f3a 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -1037,27 +1037,25 @@ TEST_CASE("issue-2432", "[highs_test_mip_solver]") { } TEST_CASE("mip-race", "[highs_test_mip_solver]") { - const bool ci_test = true; // false;// - const std::string model = ci_test ? "rgn" : "fiball"; //flugpl + const bool ci_test = true; + const std::string model = ci_test ? "flugpl" : "fiball"; // "neos-3381206-awhea"; const std::string model_file = ci_test ? std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps" : "/srv/miplib2017/" + model + ".mps.gz"; Highs h; - // if (ci_test) h.setOptionValue("output_flag", dev_run); + if (ci_test) h.setOptionValue("output_flag", dev_run); const HighsInt mip_race_concurrency = ci_test ? 2 : 4; h.setOptionValue("mip_race_concurrency", mip_race_concurrency); REQUIRE(h.readModel(model_file) == HighsStatus::kOk); REQUIRE(h.run() == HighsStatus::kOk); REQUIRE(h.getModelStatus() == HighsModelStatus::kOptimal); - /* if (ci_test) { h.clearSolver(); h.setOptionValue("mip_race_read_solutions", false); REQUIRE(h.run() == HighsStatus::kOk); REQUIRE(h.getModelStatus() == HighsModelStatus::kOptimal); } - */ h.resetGlobalScheduler(true); } diff --git a/highs/Highs.h b/highs/Highs.h index d451017971b..2c23103045c 100644 --- a/highs/Highs.h +++ b/highs/Highs.h @@ -1715,8 +1715,8 @@ class Highs { void saveHighsFiles(); void getHighsFiles(); HighsStatus mipRaceResults(HighsMipSolverInfo& mip_solver_info, - const std::vector& worker_info, - const std::vector& mip_time); + const std::vector& worker_info, + const std::vector& mip_time); }; // Start of deprecated methods not in the Highs class diff --git a/highs/lp_data/HStruct.h b/highs/lp_data/HStruct.h index a9531e16780..86490e0a642 100644 --- a/highs/lp_data/HStruct.h +++ b/highs/lp_data/HStruct.h @@ -194,6 +194,7 @@ struct HighsMipSolverInfo { double dual_bound; double primal_bound; double gap; + HighsInt max_submip_level; int64_t node_count; int64_t total_lp_iterations; double primal_dual_integral; diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index 4ba1a507d23..aec16fe3bdb 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -4109,9 +4109,10 @@ HighsStatus Highs::callSolveMip() { } }); // Determine the winner and report on the solution - HighsStatus call_status = this->mipRaceResults(mip_solver_info, worker_info, mip_time); + HighsStatus call_status = + this->mipRaceResults(mip_solver_info, worker_info, mip_time); if (call_status == HighsStatus::kError) { - const bool undo_mods = true; + const bool undo_mods = true; return returnFromOptimizeModel(HighsStatus::kError, undo_mods); } } else { @@ -4897,6 +4898,7 @@ HighsMipSolverInfo getMipSolverInfo(const HighsMipSolver& mip_solver) { mip_solver_info.dual_bound = mip_solver.dual_bound_; mip_solver_info.primal_bound = mip_solver.primal_bound_; mip_solver_info.gap = mip_solver.gap_; + mip_solver_info.max_submip_level = mip_solver.max_submip_level; mip_solver_info.node_count = mip_solver.node_count_; mip_solver_info.total_lp_iterations = mip_solver.total_lp_iterations_; mip_solver_info.primal_dual_integral = mip_solver.primal_dual_integral_; diff --git a/highs/lp_data/HighsInterface.cpp b/highs/lp_data/HighsInterface.cpp index 707c5e2eb89..beae22ec233 100644 --- a/highs/lp_data/HighsInterface.cpp +++ b/highs/lp_data/HighsInterface.cpp @@ -13,6 +13,7 @@ #include "Highs.h" #include "lp_data/HighsLpUtils.h" #include "lp_data/HighsModelUtils.h" +#include "mip/HighsMipSolver.h" // For getGapString #include "model/HighsHessianUtils.h" #include "simplex/HSimplex.h" #include "util/HighsMatrixUtils.h" @@ -4254,79 +4255,109 @@ void HighsMipSolverInfo::clear() { this->dual_bound = -kHighsInf; this->primal_bound = -kHighsInf; this->gap = -kHighsInf; + this->max_submip_level = -1; this->node_count = -kHighsSize_tInf; this->total_lp_iterations = -kHighsSize_tInf; this->primal_dual_integral = -kHighsInf; } -HighsStatus Highs::mipRaceResults(HighsMipSolverInfo& mip_solver_info, - const std::vector& worker_info, - const std::vector& mip_time) { - +HighsStatus Highs::mipRaceResults( + HighsMipSolverInfo& mip_solver_info, + const std::vector& worker_info, + const std::vector& mip_time) { const HighsInt mip_race_concurrency = this->options_.mip_race_concurrency; HighsInt winning_instance = -1; HighsModelStatus winning_model_status = HighsModelStatus::kNotset; + highsLogUser(options_.log_options, HighsLogType::kInfo, + "/nMIP race results\n"); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) { const HighsMipSolverInfo& solver_info = - instance == 0 ? mip_solver_info : worker_info[instance]; + instance == 0 ? mip_solver_info : worker_info[instance]; HighsModelStatus instance_model_status = solver_info.modelstatus; highsLogUser(options_.log_options, HighsLogType::kInfo, - " Solver %d has best objective %15.8g, gap %6.2f\% (time " - "= %6.2f), and status %s\n", - int(instance), solver_info.solution_objective, - 1e2 * solver_info.gap, mip_time[instance], - modelStatusToString(instance_model_status).c_str()); + " Solver %d has best objective %15.8g, gap %6.2f\% (time " + "= %6.2f), and status %s\n", + int(instance), solver_info.solution_objective, + 1e2 * solver_info.gap, mip_time[instance], + modelStatusToString(instance_model_status).c_str()); if (instance_model_status != HighsModelStatus::kHighsInterrupt) { // Definitive status for this instance, so check compatibility // with any current winning model status if (winning_model_status != HighsModelStatus::kNotset) { - if (winning_model_status != instance_model_status) { - highsLogUser(options_.log_options, HighsLogType::kError, - "MIP race: conflict between status \"%s\" for " - "instance %d and status \"%s\" for instance %d\n", - modelStatusToString(winning_model_status).c_str(), - int(winning_instance), - modelStatusToString(instance_model_status).c_str(), - int(instance)); - return HighsStatus::kError; - } + if (winning_model_status != instance_model_status) { + highsLogUser(options_.log_options, HighsLogType::kError, + "MIP race: conflict between status \"%s\" for " + "instance %d and status \"%s\" for instance %d\n", + modelStatusToString(winning_model_status).c_str(), + int(winning_instance), + modelStatusToString(instance_model_status).c_str(), + int(instance)); + return HighsStatus::kError; + } } else { - winning_model_status = instance_model_status; - winning_instance = instance; + winning_model_status = instance_model_status; + winning_instance = instance; } } } if (winning_instance > 0) mip_solver_info = worker_info[winning_instance]; - highsLogUser(options_.log_options, HighsLogType::kInfo, "Solving report\n"); - highsLogUser(options_.log_options, HighsLogType::kInfo, " Model %s\n", - this->model_.lp_.model_name_.c_str()); - highsLogUser(options_.log_options, HighsLogType::kInfo, " Status %s\n", - modelStatusToString(mip_solver_info.modelstatus).c_str()); - highsLogUser(options_.log_options, HighsLogType::kInfo, " Primal bound %.12g\n", - mip_solver_info.primal_bound); - highsLogUser(options_.log_options, HighsLogType::kInfo, " Dual bound %.12g\n", - mip_solver_info.dual_bound); - highsLogUser(options_.log_options, HighsLogType::kInfo, " Gap %g%% (tolerance: %g%%)\n", - 1e2*mip_solver_info.gap, 1e2*options_.mip_rel_gap); - highsLogUser(options_.log_options, HighsLogType::kInfo, " P-D integral %.12g\n", - mip_solver_info.primal_dual_integral); - highsLogUser(options_.log_options, HighsLogType::kInfo, " Solution status %.12g\n", - mip_solver_info.solution_objective); - highsLogUser(options_.log_options, HighsLogType::kInfo, " %.12g (bound viol.)\n", - mip_solver_info.bound_violation); - highsLogUser(options_.log_options, HighsLogType::kInfo, " %.12g (int. viol.)\n", - mip_solver_info.integrality_violation); - highsLogUser(options_.log_options, HighsLogType::kInfo, " %.12g (row viol.)\n", - mip_solver_info.row_violation); - highsLogUser(options_.log_options, HighsLogType::kInfo, " Timing %.2f\n", - mip_time[winning_instance]); - highsLogUser(options_.log_options, HighsLogType::kInfo, " Nodes %llu\n", - mip_solver_info.node_count); - highsLogUser(options_.log_options, HighsLogType::kInfo, " LP iterations %llu\n", - mip_solver_info.total_lp_iterations); - /* - Solution status feasible - Max sub-MIP depth 1 - */ + std::array gapString = getGapString( + mip_solver_info.gap, mip_solver_info.primal_bound, &options_); + + bool havesolution = mip_solver_info.solution_objective != kHighsInf; + bool feasible; + std::string solutionstatus = "-"; + if (havesolution) { + feasible = + mip_solver_info.bound_violation <= options_.mip_feasibility_tolerance && + mip_solver_info.integrality_violation <= + options_.mip_feasibility_tolerance && + mip_solver_info.row_violation <= options_.mip_feasibility_tolerance; + } else { + feasible = false; + } + solutionstatus = feasible ? "feasible" : "infeasible"; + + highsLogUser(options_.log_options, HighsLogType::kInfo, "Solving report\n"); + highsLogUser(options_.log_options, HighsLogType::kInfo, + " Model %s\n", + this->model_.lp_.model_name_.c_str()); + highsLogUser(options_.log_options, HighsLogType::kInfo, + " Status %s\n", + modelStatusToString(mip_solver_info.modelstatus).c_str()); + highsLogUser(options_.log_options, HighsLogType::kInfo, + " Primal bound %.12g\n", mip_solver_info.primal_bound); + highsLogUser(options_.log_options, HighsLogType::kInfo, + " Dual bound %.12g\n", mip_solver_info.dual_bound); + highsLogUser(options_.log_options, HighsLogType::kInfo, + " Gap %s\n", gapString.data()); + highsLogUser(options_.log_options, HighsLogType::kInfo, + " P-D integral %.12g\n", + mip_solver_info.primal_dual_integral); + highsLogUser(options_.log_options, HighsLogType::kInfo, + " Solution status %s\n", solutionstatus.c_str()); + highsLogUser(options_.log_options, HighsLogType::kInfo, + " %.12g\n", + mip_solver_info.solution_objective); + highsLogUser(options_.log_options, HighsLogType::kInfo, + " %.12g (bound viol.)\n", + mip_solver_info.bound_violation); + highsLogUser(options_.log_options, HighsLogType::kInfo, + " %.12g (int. viol.)\n", + mip_solver_info.integrality_violation); + highsLogUser(options_.log_options, HighsLogType::kInfo, + " %.12g (row viol.)\n", + mip_solver_info.row_violation); + highsLogUser(options_.log_options, HighsLogType::kInfo, + " Timing %.2f\n", mip_time[winning_instance]); + highsLogUser(options_.log_options, HighsLogType::kInfo, + " Max sub-MIP depth %d\n", + int(mip_solver_info.max_submip_level)); + highsLogUser(options_.log_options, HighsLogType::kInfo, + " Nodes %llu\n", + (long long unsigned)(mip_solver_info.node_count)); + highsLogUser(options_.log_options, HighsLogType::kInfo, + " LP iterations %llu\n", + (long long unsigned)(mip_solver_info.total_lp_iterations)); return HighsStatus::kOk; } diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index 6695f592815..3aa3a217a16 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -302,10 +302,10 @@ void HighsMipSolver::run() { mipdata_->heuristics.flushStatistics(); analysis_.mipTimerStop(kMipClockDivePrimalHeuristics); - if (mipdata_->terminatorTerminated()) { - cleanupSolve(); - return; - } + if (mipdata_->terminatorTerminated()) { + cleanupSolve(); + return; + } } } @@ -705,8 +705,7 @@ void HighsMipSolver::cleanupSolve() { highsLogUser(options_mip_->log_options, HighsLogType::kInfo, "instance%d: terminated %6.4f (%sMIP)\n", int(this->mipdata_->terminatorMyInstance()), - this->timer_.read(), - submip ? "sub-" : ""); + this->timer_.read(), submip ? "sub-" : ""); modelstatus_ = HighsModelStatus::kHighsInterrupt; } else if (!submip) { // When sub-MIPs call cleanupSolve(), they generally don't have @@ -719,8 +718,7 @@ void HighsMipSolver::cleanupSolve() { highsLogUser(options_mip_->log_options, HighsLogType::kInfo, "instance%d: terminate %6.4f (%sMIP)\n", int(this->mipdata_->terminatorMyInstance()), - this->timer_.read(), - submip ? "sub-" : ""); + this->timer_.read(), submip ? "sub-" : ""); mipdata_->terminatorTerminate(); } mipdata_->terminatorReport(); @@ -788,10 +786,12 @@ void HighsMipSolver::cleanupSolve() { std::string solutionstatus = "-"; if (havesolution) { - bool feasible = + // 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"; } @@ -803,44 +803,24 @@ void HighsMipSolver::cleanupSolve() { else gap_ = kHighsInf; - std::array gapString = {}; - - if (gap_ == kHighsInf) - std::strcpy(gapString.data(), "inf"); - else { - double printTol = std::max(std::min(1e-2, 1e-1 * gap_), 1e-6); - auto gapValString = highsDoubleToString(100.0 * gap_, printTol); - double gapTol = options_mip_->mip_rel_gap; - - if (options_mip_->mip_abs_gap > options_mip_->mip_feasibility_tolerance) { - gapTol = primal_bound_ == 0.0 - ? kHighsInf - : std::max(gapTol, - options_mip_->mip_abs_gap / fabs(primal_bound_)); - } + std::array gapString = + getGapString(gap_, primal_bound_, options_mip_); - if (gapTol == 0.0) - std::snprintf(gapString.data(), gapString.size(), "%s%%", - gapValString.data()); - else if (gapTol != kHighsInf) { - printTol = std::max(std::min(1e-2, 1e-1 * gapTol), 1e-6); - auto gapTolString = highsDoubleToString(100.0 * gapTol, printTol); - std::snprintf(gapString.data(), gapString.size(), - "%s%% (tolerance: %s%%)", gapValString.data(), - gapTolString.data()); - } else - std::snprintf(gapString.data(), gapString.size(), "%s%% (tolerance: inf)", - gapValString.data()); + // Don't log to console if this is in a MIP race + HighsOptions temp_options = *options_mip_; + if (mipdata_->terminatorActive()) { + temp_options.log_to_console = false; + temp_options.setLogOptions(); } - bool timeless_log = options_mip_->timeless_log; - highsLogUser(options_mip_->log_options, HighsLogType::kInfo, + bool timeless_log = temp_options.timeless_log; + highsLogUser(temp_options.log_options, HighsLogType::kInfo, "\nSolving report\n"); if (this->orig_model_->model_name_.length()) - highsLogUser(options_mip_->log_options, HighsLogType::kInfo, + highsLogUser(temp_options.log_options, HighsLogType::kInfo, " Model %s\n", this->orig_model_->model_name_.c_str()); - highsLogUser(options_mip_->log_options, HighsLogType::kInfo, + highsLogUser(temp_options.log_options, HighsLogType::kInfo, " Status %s\n" " Primal bound %.12g\n" " Dual bound %.12g\n" @@ -848,13 +828,13 @@ void HighsMipSolver::cleanupSolve() { utilModelStatusToString(modelstatus_).c_str(), primal_bound_, dual_bound_, gapString.data()); if (!timeless_log) - highsLogUser(options_mip_->log_options, HighsLogType::kInfo, + highsLogUser(temp_options.log_options, HighsLogType::kInfo, " P-D integral %.12g\n", mipdata_->primal_dual_integral.value); - highsLogUser(options_mip_->log_options, HighsLogType::kInfo, + highsLogUser(temp_options.log_options, HighsLogType::kInfo, " Solution status %s\n", solutionstatus.c_str()); if (solutionstatus != "-") - highsLogUser(options_mip_->log_options, HighsLogType::kInfo, + highsLogUser(temp_options.log_options, HighsLogType::kInfo, " %.12g (objective)\n" " %.12g (bound viol.)\n" " %.12g (int. viol.)\n" @@ -862,7 +842,7 @@ void HighsMipSolver::cleanupSolve() { solution_objective_, bound_violation_, integrality_violation_, row_violation_); if (!timeless_log) - highsLogUser(options_mip_->log_options, HighsLogType::kInfo, + highsLogUser(temp_options.log_options, HighsLogType::kInfo, " Timing %.2f (total)\n" " %.2f (presolve)\n" " %.2f (solve)\n" @@ -870,7 +850,7 @@ void HighsMipSolver::cleanupSolve() { timer_.read(), analysis_.mipTimerRead(kMipClockPresolve), analysis_.mipTimerRead(kMipClockSolve), analysis_.mipTimerRead(kMipClockPostsolve)); - highsLogUser(options_mip_->log_options, HighsLogType::kInfo, + highsLogUser(temp_options.log_options, HighsLogType::kInfo, " Max sub-MIP depth %d\n" " Nodes %llu\n" " Repair LPs %llu (%llu feasible; %llu iterations)\n" @@ -933,6 +913,41 @@ void HighsMipSolver::callbackGetCutPool() const { callback_->user_callback_data); } +std::array getGapString(const double gap_, + const double primal_bound_, + const HighsOptions* options_mip_) { + std::array gapString = {}; + if (gap_ == kHighsInf) + std::strcpy(gapString.data(), "inf"); + else { + double printTol = std::max(std::min(1e-2, 1e-1 * gap_), 1e-6); + auto gapValString = highsDoubleToString(100.0 * gap_, printTol); + double gapTol = options_mip_->mip_rel_gap; + + if (options_mip_->mip_abs_gap > options_mip_->mip_feasibility_tolerance) { + gapTol = primal_bound_ == 0.0 + ? kHighsInf + : std::max(gapTol, + options_mip_->mip_abs_gap / fabs(primal_bound_)); + } + + if (gapTol == 0.0) + std::snprintf(gapString.data(), gapString.size(), "%s%%", + gapValString.data()); + else if (gapTol != kHighsInf) { + printTol = std::max(std::min(1e-2, 1e-1 * gapTol), 1e-6); + auto gapTolString = highsDoubleToString(100.0 * gapTol, printTol); + std::snprintf(gapString.data(), gapString.size(), + "%s%% (tolerance: %s%%)", gapValString.data(), + gapTolString.data()); + } else + std::snprintf(gapString.data(), gapString.size(), "%s%% (tolerance: inf)", + gapValString.data()); + } + + return gapString; +} + bool HighsMipSolver::solutionFeasible(const HighsLp* lp, const std::vector& col_value, const std::vector* pass_row_value, @@ -1030,8 +1045,8 @@ void HighsMipSolver::initialiseTerminator(const HighsMipSolver& mip_solver) { if (!mip_solver.mipdata_->terminatorActive()) return; assert(mip_solver.mipdata_->terminatorConcurrency() > 0); this->initialiseTerminator(mip_solver.mipdata_->terminatorConcurrency(), - mip_solver.mipdata_->terminatorMyInstance(), - mip_solver.terminator_.record); + mip_solver.mipdata_->terminatorMyInstance(), + mip_solver.terminator_.record); } void HighsMipSolver::initialiseMipRace(const HighsInt mip_race_concurrency, diff --git a/highs/mip/HighsMipSolver.h b/highs/mip/HighsMipSolver.h index 24905586870..be48e238d8f 100644 --- a/highs/mip/HighsMipSolver.h +++ b/highs/mip/HighsMipSolver.h @@ -185,4 +185,7 @@ class HighsMipSolver { } }; +std::array getGapString(const double gap_, + const double primal_bound_, + const HighsOptions* options_mip_); #endif diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index dd6c2c5db01..64afa1b016f 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -2425,14 +2425,13 @@ bool HighsMipSolverData::checkLimits(int64_t nodeOffset) const { highsLogUser(options.log_options, HighsLogType::kInfo, "instance%d: terminated? %6.4f (%sMIP)\n", int(this->terminatorMyInstance()), - this->mipsolver.timer_.read(), - mipsolver.submip ? "sub-" : ""); + this->mipsolver.timer_.read(), mipsolver.submip ? "sub-" : ""); if (this->terminatorTerminated()) { highsLogUser(options.log_options, HighsLogType::kInfo, "instance%d: terminated %6.4f (%sMIP)\n", int(this->terminatorMyInstance()), this->mipsolver.timer_.read(), - mipsolver.submip ? "sub-" : ""); + mipsolver.submip ? "sub-" : ""); return true; } } diff --git a/highs/mip/HighsPrimalHeuristics.cpp b/highs/mip/HighsPrimalHeuristics.cpp index 332a39e36ac..bd211902108 100644 --- a/highs/mip/HighsPrimalHeuristics.cpp +++ b/highs/mip/HighsPrimalHeuristics.cpp @@ -145,8 +145,11 @@ bool HighsPrimalHeuristics::solveSubMip( // Initialise termination_status_ and propagate any terminator to // the sub-MIP submipsolver.initialiseTerminator(mipsolver); - printf("HighsPrimalHeuristics::solveSubMip: %d submipsolver.termination_status_ = %d\n", - int(submipsolver.terminator_.my_instance), int(submipsolver.termination_status_)); + printf( + "HighsPrimalHeuristics::solveSubMip: %d submipsolver.termination_status_ " + "= %d\n", + int(submipsolver.terminator_.my_instance), + int(submipsolver.termination_status_)); submipsolver.rootbasis = &basis; HighsPseudocostInitialization pscostinit(mipsolver.mipdata_->pseudocost, 1); submipsolver.pscostinit = &pscostinit; @@ -163,12 +166,15 @@ bool HighsPrimalHeuristics::solveSubMip( // submipsolver.termination_status_, rather than // submipsolver.mipdata_.terminatorTerminated() if (!submipsolver.mipdata_) { - printf("HighsPrimalHeuristics::solveSubMip: submipsolver.mipdata_ is nullptr\n"); + printf( + "HighsPrimalHeuristics::solveSubMip: submipsolver.mipdata_ is " + "nullptr\n"); assert(submipsolver.mipdata_); } if (submipsolver.termination_status_ != HighsModelStatus::kNotset) { - printf("HighsPrimalHeuristics::solveSubMip: %d termination status is %d\n", - int(submipsolver.terminator_.my_instance), int(submipsolver.termination_status_)); + printf("HighsPrimalHeuristics::solveSubMip: %d termination status is %d\n", + int(submipsolver.terminator_.my_instance), + int(submipsolver.termination_status_)); mipsolver.termination_status_ = submipsolver.termination_status_; return false; } From c52e2af3849ec828ac5191751b86305a4a8701a9 Mon Sep 17 00:00:00 2001 From: jajhall Date: Tue, 22 Jul 2025 13:43:36 +0100 Subject: [PATCH 44/58] Fixed compiler warning --- highs/lp_data/HighsLpUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/highs/lp_data/HighsLpUtils.cpp b/highs/lp_data/HighsLpUtils.cpp index e5b1572e162..ed8ea21cd69 100644 --- a/highs/lp_data/HighsLpUtils.cpp +++ b/highs/lp_data/HighsLpUtils.cpp @@ -2564,7 +2564,7 @@ HighsStatus assessLpPrimalSolution(const std::string message, HighsStatus return_status = calculateRowValuesQuad(lp, solution.col_value, row_value); if (return_status != HighsStatus::kOk) return return_status; - const bool have_row_names = lp.row_names_.size() >= lp.num_row_; + const bool have_row_names = lp.row_names_.size() >= static_cast(lp.num_row_); for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { const double primal = solution.row_value[iRow]; const double lower = lp.row_lower_[iRow]; From d27d46b8f10919c3c0945d57f002ba66a6142ba8 Mon Sep 17 00:00:00 2001 From: jajhall Date: Tue, 22 Jul 2025 18:16:01 +0100 Subject: [PATCH 45/58] Cleared out development logging --- check/TestMipSolver.cpp | 5 +++-- highs/lp_data/Highs.cpp | 13 +++---------- highs/lp_data/HighsInterface.cpp | 2 +- highs/mip/HighsMipSolver.cpp | 17 ----------------- highs/mip/HighsMipSolverData.cpp | 18 +++--------------- highs/mip/HighsPrimalHeuristics.cpp | 8 -------- 6 files changed, 10 insertions(+), 53 deletions(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index 6b07a122f3a..06276451ae5 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -1037,8 +1037,9 @@ TEST_CASE("issue-2432", "[highs_test_mip_solver]") { } TEST_CASE("mip-race", "[highs_test_mip_solver]") { - const bool ci_test = true; - const std::string model = ci_test ? "flugpl" : "fiball"; + const bool ci_test = false; + const std::string test_build_model = "fiball"; + const std::string model = ci_test ? "flugpl" : test_build_model; // "neos-3381206-awhea"; const std::string model_file = ci_test ? std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps" diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index aec16fe3bdb..5d1889ee1d8 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -4049,7 +4049,7 @@ HighsStatus Highs::callSolveMip() { worker_callback.clear(); // Race the MIP solver! highsLogUser(options_.log_options, HighsLogType::kInfo, - "Starting MIP race with %d instances: performance is " + "Starting MIP race with %d instances: behaviour is " "non-deterministic!\n", int(mip_race_concurrency)); // Define the HighsMipSolverInfo record for each worker @@ -4083,10 +4083,7 @@ HighsStatus Highs::callSolveMip() { terminator_record.data()); solver.initialiseMipRace(mip_race_concurrency, instance, &mip_race_record); - double this_time = timer_.read(); - highsLogUser(options_.log_options, HighsLogType::kInfo, - "instance0: call run() %f6.4\n", this_time); - mip_time[instance] = -this_time; + mip_time[instance] = -timer_.read(); solver.run(); mip_time[instance] += timer_.read(); mip_solver_info = getMipSolverInfo(solver); @@ -4097,11 +4094,7 @@ HighsStatus Highs::callSolveMip() { terminator_record.data()); worker.initialiseMipRace(mip_race_concurrency, instance, &mip_race_record); - double this_time = timer_.read(); - highsLogUser(options_.log_options, HighsLogType::kInfo, - "instance%d: call run() %f6.4\n", int(instance), - this_time); - mip_time[instance] = -this_time; + mip_time[instance] = -timer_.read(); worker.run(); mip_time[instance] += timer_.read(); worker_info[instance] = getMipSolverInfo(worker); diff --git a/highs/lp_data/HighsInterface.cpp b/highs/lp_data/HighsInterface.cpp index beae22ec233..f203b8361e8 100644 --- a/highs/lp_data/HighsInterface.cpp +++ b/highs/lp_data/HighsInterface.cpp @@ -4269,7 +4269,7 @@ HighsStatus Highs::mipRaceResults( HighsInt winning_instance = -1; HighsModelStatus winning_model_status = HighsModelStatus::kNotset; highsLogUser(options_.log_options, HighsLogType::kInfo, - "/nMIP race results\n"); + "\nMIP race results\n"); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) { const HighsMipSolverInfo& solver_info = instance == 0 ? mip_solver_info : worker_info[instance]; diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index 3aa3a217a16..55b6f98fb11 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -75,11 +75,6 @@ HighsMipSolver::~HighsMipSolver() = default; void HighsMipSolver::run() { modelstatus_ = HighsModelStatus::kNotset; - if (!submip) - highsLogUser(options_mip_->log_options, HighsLogType::kInfo, - "instance%d: top run() %6.4f (MIP)\n", - int(this->mip_race_.my_instance), this->timer_.read()); - if (submip) { analysis_.analyse_mip_time = false; } else { @@ -699,13 +694,8 @@ void HighsMipSolver::run() { void HighsMipSolver::cleanupSolve() { if (mipdata_->terminatorActive()) { - mipdata_->terminatorReport(); if (mipdata_->terminatorTerminated()) { // Indicate that this instance has been interrupted - highsLogUser(options_mip_->log_options, HighsLogType::kInfo, - "instance%d: terminated %6.4f (%sMIP)\n", - int(this->mipdata_->terminatorMyInstance()), - this->timer_.read(), submip ? "sub-" : ""); modelstatus_ = HighsModelStatus::kHighsInterrupt; } else if (!submip) { // When sub-MIPs call cleanupSolve(), they generally don't have @@ -715,15 +705,8 @@ void HighsMipSolver::cleanupSolve() { // reached // // No other instance has terminated, so terminate - highsLogUser(options_mip_->log_options, HighsLogType::kInfo, - "instance%d: terminate %6.4f (%sMIP)\n", - int(this->mipdata_->terminatorMyInstance()), - this->timer_.read(), submip ? "sub-" : ""); mipdata_->terminatorTerminate(); } - mipdata_->terminatorReport(); - // Report on any active MIP race - mipdata_->mipRaceReport(); } // Force a final logging line diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index 64afa1b016f..f0098e9c071 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -2421,20 +2421,8 @@ bool HighsMipSolverData::checkLimits(int64_t nodeOffset) const { const HighsOptions& options = *mipsolver.options_mip_; // This MIP instance may have been terminated - if (terminatorActive()) { - highsLogUser(options.log_options, HighsLogType::kInfo, - "instance%d: terminated? %6.4f (%sMIP)\n", - int(this->terminatorMyInstance()), - this->mipsolver.timer_.read(), mipsolver.submip ? "sub-" : ""); - if (this->terminatorTerminated()) { - highsLogUser(options.log_options, HighsLogType::kInfo, - "instance%d: terminated %6.4f (%sMIP)\n", - int(this->terminatorMyInstance()), - this->mipsolver.timer_.read(), - mipsolver.submip ? "sub-" : ""); - return true; - } - } + if (terminatorActive()) + if (this->terminatorTerminated()) return true; // Possible user interrupt if (!mipsolver.submip && mipsolver.callback_->user_callback) { @@ -2999,7 +2987,7 @@ void MipRace::update(const double objective, const std::vector& solution) { assert(this->record); this->record->update(this->my_instance, objective, solution); - this->report(); + // this->report(); } bool MipRace::newSolution(const HighsInt instance, double objective, diff --git a/highs/mip/HighsPrimalHeuristics.cpp b/highs/mip/HighsPrimalHeuristics.cpp index bd211902108..080c695e9c0 100644 --- a/highs/mip/HighsPrimalHeuristics.cpp +++ b/highs/mip/HighsPrimalHeuristics.cpp @@ -145,11 +145,6 @@ bool HighsPrimalHeuristics::solveSubMip( // Initialise termination_status_ and propagate any terminator to // the sub-MIP submipsolver.initialiseTerminator(mipsolver); - printf( - "HighsPrimalHeuristics::solveSubMip: %d submipsolver.termination_status_ " - "= %d\n", - int(submipsolver.terminator_.my_instance), - int(submipsolver.termination_status_)); submipsolver.rootbasis = &basis; HighsPseudocostInitialization pscostinit(mipsolver.mipdata_->pseudocost, 1); submipsolver.pscostinit = &pscostinit; @@ -172,9 +167,6 @@ bool HighsPrimalHeuristics::solveSubMip( assert(submipsolver.mipdata_); } if (submipsolver.termination_status_ != HighsModelStatus::kNotset) { - printf("HighsPrimalHeuristics::solveSubMip: %d termination status is %d\n", - int(submipsolver.terminator_.my_instance), - int(submipsolver.termination_status_)); mipsolver.termination_status_ = submipsolver.termination_status_; return false; } From 1c5cf453997d024a4fe450b668aaede7c317cba1 Mon Sep 17 00:00:00 2001 From: jajhall Date: Wed, 23 Jul 2025 08:20:28 +0100 Subject: [PATCH 46/58] Cleaned up, and first of @mathgeekcoder's suggestions implemented --- check/TestMipSolver.cpp | 3 ++- highs/mip/HighsMipSolver.h | 30 ++++++++++++++++++++++++++++++ highs/mip/HighsMipSolverData.cpp | 7 +++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index 06276451ae5..32ac39538e4 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -1037,7 +1037,7 @@ TEST_CASE("issue-2432", "[highs_test_mip_solver]") { } TEST_CASE("mip-race", "[highs_test_mip_solver]") { - const bool ci_test = false; + const bool ci_test = true; const std::string test_build_model = "fiball"; const std::string model = ci_test ? "flugpl" : test_build_model; // "neos-3381206-awhea"; @@ -1048,6 +1048,7 @@ TEST_CASE("mip-race", "[highs_test_mip_solver]") { if (ci_test) h.setOptionValue("output_flag", dev_run); const HighsInt mip_race_concurrency = ci_test ? 2 : 4; h.setOptionValue("mip_race_concurrency", mip_race_concurrency); + h.setOptionValue("mip_race_read_solutions", true); REQUIRE(h.readModel(model_file) == HighsStatus::kOk); REQUIRE(h.run() == HighsStatus::kOk); REQUIRE(h.getModelStatus() == HighsModelStatus::kOptimal); diff --git a/highs/mip/HighsMipSolver.h b/highs/mip/HighsMipSolver.h index be48e238d8f..a7b86c65fcf 100644 --- a/highs/mip/HighsMipSolver.h +++ b/highs/mip/HighsMipSolver.h @@ -33,6 +33,36 @@ struct MipRaceIncumbent { std::vector& solution_) const; }; + +/* + struct MipRaceIncumbent { + std::atomic start_write_incumbent = kMipRaceNoSolution; + std::atomic finish_write_incumbent = kMipRaceNoSolution; + double objective = -kHighsInf; + std::vector solution; + void clear(); + void initialise(const HighsInt num_col); + void update(const double objective, const std::vector& solution); + HighsInt read(const HighsInt last_incumbent_read, double& objective_, + std::vector& solution_) const; + + MipRaceIncumbent() = default; + + MipRaceIncumbent(const MipRaceIncumbent& copy) { + start_write_incumbent = copy.start_write_incumbent.load(); + finish_write_incumbent = copy.finish_write_incumbent.load(); + objective = copy.objective; + solution = copy.solution; + } + + MipRaceIncumbent(MipRaceIncumbent&& moving) { + start_write_incumbent = moving.start_write_incumbent.load(); + finish_write_incumbent = moving.finish_write_incumbent.load(); + objective = moving.objective; + solution = std::move(moving.solution); + } +}; +*/ struct MipRaceRecord { std::vector incumbent; void clear(); diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index f0098e9c071..19f02793794 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -2923,6 +2923,12 @@ void MipRaceRecord::clear() { this->incumbent.clear(); } void MipRaceRecord::initialise(const HighsInt mip_race_concurrency, const HighsInt num_col) { this->clear(); + this->incumbent.resize(mip_race_concurrency); + + for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) + this->incumbent[instance].initialise(num_col); + + /* MipRaceIncumbent incumbent_; incumbent_.initialise(num_col); // Loop from 1... @@ -2930,6 +2936,7 @@ void MipRaceRecord::initialise(const HighsInt mip_race_concurrency, this->incumbent.push_back(incumbent_); // ... and move incumbent_ to complete the vector of incumbents this->incumbent.push_back(std::move(incumbent_)); + */ } HighsInt MipRaceRecord::concurrency() const { From 5a1c095f49503f7bc1c84f8b178259e668c0c4ee Mon Sep 17 00:00:00 2001 From: jajhall Date: Wed, 23 Jul 2025 08:21:37 +0100 Subject: [PATCH 47/58] Formatted --- highs/lp_data/HighsLpUtils.cpp | 3 ++- highs/mip/HighsMipSolver.h | 1 - highs/mip/HighsMipSolverData.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/highs/lp_data/HighsLpUtils.cpp b/highs/lp_data/HighsLpUtils.cpp index ed8ea21cd69..206ee252bde 100644 --- a/highs/lp_data/HighsLpUtils.cpp +++ b/highs/lp_data/HighsLpUtils.cpp @@ -2564,7 +2564,8 @@ HighsStatus assessLpPrimalSolution(const std::string message, HighsStatus return_status = calculateRowValuesQuad(lp, solution.col_value, row_value); if (return_status != HighsStatus::kOk) return return_status; - const bool have_row_names = lp.row_names_.size() >= static_cast(lp.num_row_); + const bool have_row_names = + lp.row_names_.size() >= static_cast(lp.num_row_); for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { const double primal = solution.row_value[iRow]; const double lower = lp.row_lower_[iRow]; diff --git a/highs/mip/HighsMipSolver.h b/highs/mip/HighsMipSolver.h index a7b86c65fcf..57516fb2913 100644 --- a/highs/mip/HighsMipSolver.h +++ b/highs/mip/HighsMipSolver.h @@ -33,7 +33,6 @@ struct MipRaceIncumbent { std::vector& solution_) const; }; - /* struct MipRaceIncumbent { std::atomic start_write_incumbent = kMipRaceNoSolution; diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index 19f02793794..cb2c21e15a6 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -2421,7 +2421,7 @@ bool HighsMipSolverData::checkLimits(int64_t nodeOffset) const { const HighsOptions& options = *mipsolver.options_mip_; // This MIP instance may have been terminated - if (terminatorActive()) + if (terminatorActive()) if (this->terminatorTerminated()) return true; // Possible user interrupt From a2974ce2e3fda0f3876bf0886e98b2e35978683d Mon Sep 17 00:00:00 2001 From: Julian Hall Date: Tue, 29 Jul 2025 12:31:47 +0100 Subject: [PATCH 48/58] Now reporting whole loop time for race --- check/TestMipSolver.cpp | 2 +- highs/Highs.h | 3 ++- highs/lp_data/Highs.cpp | 7 ++++++- highs/lp_data/HighsInterface.cpp | 9 ++++++--- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index 32ac39538e4..30410c3bc5f 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -1037,7 +1037,7 @@ TEST_CASE("issue-2432", "[highs_test_mip_solver]") { } TEST_CASE("mip-race", "[highs_test_mip_solver]") { - const bool ci_test = true; + const bool ci_test = false; const std::string test_build_model = "fiball"; const std::string model = ci_test ? "flugpl" : test_build_model; // "neos-3381206-awhea"; diff --git a/highs/Highs.h b/highs/Highs.h index 2c23103045c..cfb026a5062 100644 --- a/highs/Highs.h +++ b/highs/Highs.h @@ -1716,7 +1716,8 @@ class Highs { void getHighsFiles(); HighsStatus mipRaceResults(HighsMipSolverInfo& mip_solver_info, const std::vector& worker_info, - const std::vector& mip_time); + const std::vector& mip_time, + const double& report_mip_time); }; // Start of deprecated methods not in the Highs class diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index 5d1889ee1d8..0328cf92a12 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -4075,6 +4075,9 @@ HighsStatus Highs::callSolveMip() { lp, solution_); worker.push_back(&worker_instance); */ } + // Time the master outside the parallel loop so that this "real" + // time is reported + double loop_mip_time = -timer_.read(); highs::parallel::for_each( 0, mip_race_concurrency, [&](HighsInt start, HighsInt end) { for (HighsInt instance = start; instance < end; instance++) { @@ -4101,9 +4104,11 @@ HighsStatus Highs::callSolveMip() { } } }); + loop_mip_time += timer_.read(); // Determine the winner and report on the solution HighsStatus call_status = - this->mipRaceResults(mip_solver_info, worker_info, mip_time); + this->mipRaceResults(mip_solver_info, worker_info, mip_time, + loop_mip_time); if (call_status == HighsStatus::kError) { const bool undo_mods = true; return returnFromOptimizeModel(HighsStatus::kError, undo_mods); diff --git a/highs/lp_data/HighsInterface.cpp b/highs/lp_data/HighsInterface.cpp index f203b8361e8..3f6e62c40c2 100644 --- a/highs/lp_data/HighsInterface.cpp +++ b/highs/lp_data/HighsInterface.cpp @@ -4264,7 +4264,8 @@ void HighsMipSolverInfo::clear() { HighsStatus Highs::mipRaceResults( HighsMipSolverInfo& mip_solver_info, const std::vector& worker_info, - const std::vector& mip_time) { + const std::vector& mip_time, + const double& report_mip_time) { const HighsInt mip_race_concurrency = this->options_.mip_race_concurrency; HighsInt winning_instance = -1; HighsModelStatus winning_model_status = HighsModelStatus::kNotset; @@ -4275,7 +4276,7 @@ HighsStatus Highs::mipRaceResults( instance == 0 ? mip_solver_info : worker_info[instance]; HighsModelStatus instance_model_status = solver_info.modelstatus; highsLogUser(options_.log_options, HighsLogType::kInfo, - " Solver %d has best objective %15.8g, gap %6.2f\% (time " + " Solver %2d has best objective %15.8g, gap %6.2f\% (time " "= %6.2f), and status %s\n", int(instance), solver_info.solution_objective, 1e2 * solver_info.gap, mip_time[instance], @@ -4348,8 +4349,10 @@ HighsStatus Highs::mipRaceResults( highsLogUser(options_.log_options, HighsLogType::kInfo, " %.12g (row viol.)\n", mip_solver_info.row_violation); + // Report the solution time for the whole concurrent loop, as that's + // "real" time highsLogUser(options_.log_options, HighsLogType::kInfo, - " Timing %.2f\n", mip_time[winning_instance]); + " Timing %.2f\n", report_mip_time); highsLogUser(options_.log_options, HighsLogType::kInfo, " Max sub-MIP depth %d\n", int(mip_solver_info.max_submip_level)); From 34260a468ffda8d01e2ce03aa8a57f25e2242096 Mon Sep 17 00:00:00 2001 From: Julian Hall Date: Tue, 29 Jul 2025 18:04:45 +0100 Subject: [PATCH 49/58] Made @mathgeekcoder's changes, and computing the correct reduced objective value for maximization, as well as minimization problems; formatted --- check/TestMipSolver.cpp | 18 +++++++-- highs/Highs.h | 2 +- highs/lp_data/Highs.cpp | 5 +-- highs/lp_data/HighsInterface.cpp | 5 +-- highs/mip/HighsMipSolver.h | 15 ++++--- highs/mip/HighsMipSolverData.cpp | 69 ++++++++++++++++++++++++++++---- 6 files changed, 89 insertions(+), 25 deletions(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index 30410c3bc5f..347eb13479b 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -1037,8 +1037,8 @@ TEST_CASE("issue-2432", "[highs_test_mip_solver]") { } TEST_CASE("mip-race", "[highs_test_mip_solver]") { - const bool ci_test = false; - const std::string test_build_model = "fiball"; + const bool ci_test = true; + const std::string test_build_model = "neos-3381206-awhea"; //"fiball"; const std::string model = ci_test ? "flugpl" : test_build_model; // "neos-3381206-awhea"; const std::string model_file = @@ -1050,8 +1050,18 @@ TEST_CASE("mip-race", "[highs_test_mip_solver]") { h.setOptionValue("mip_race_concurrency", mip_race_concurrency); h.setOptionValue("mip_race_read_solutions", true); REQUIRE(h.readModel(model_file) == HighsStatus::kOk); - REQUIRE(h.run() == HighsStatus::kOk); - REQUIRE(h.getModelStatus() == HighsModelStatus::kOptimal); + for (int k = 0; k < 2; k++) { + if (k == 1) { + HighsLp lp = h.getLp(); + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + lp.col_cost_[iCol] = -lp.col_cost_[iCol]; + REQUIRE(h.changeColsCost(0, lp.num_col_ - 1, lp.col_cost_.data()) == + HighsStatus::kOk); + REQUIRE(h.changeObjectiveSense(ObjSense::kMaximize) == HighsStatus::kOk); + } + REQUIRE(h.run() == HighsStatus::kOk); + REQUIRE(h.getModelStatus() == HighsModelStatus::kOptimal); + } if (ci_test) { h.clearSolver(); diff --git a/highs/Highs.h b/highs/Highs.h index cfb026a5062..b12fa47c5ab 100644 --- a/highs/Highs.h +++ b/highs/Highs.h @@ -1717,7 +1717,7 @@ class Highs { HighsStatus mipRaceResults(HighsMipSolverInfo& mip_solver_info, const std::vector& worker_info, const std::vector& mip_time, - const double& report_mip_time); + const double& report_mip_time); }; // Start of deprecated methods not in the Highs class diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index 0328cf92a12..2840acf0fd0 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -4106,9 +4106,8 @@ HighsStatus Highs::callSolveMip() { }); loop_mip_time += timer_.read(); // Determine the winner and report on the solution - HighsStatus call_status = - this->mipRaceResults(mip_solver_info, worker_info, mip_time, - loop_mip_time); + HighsStatus call_status = this->mipRaceResults(mip_solver_info, worker_info, + mip_time, loop_mip_time); if (call_status == HighsStatus::kError) { const bool undo_mods = true; return returnFromOptimizeModel(HighsStatus::kError, undo_mods); diff --git a/highs/lp_data/HighsInterface.cpp b/highs/lp_data/HighsInterface.cpp index 3f6e62c40c2..0565bd19fe9 100644 --- a/highs/lp_data/HighsInterface.cpp +++ b/highs/lp_data/HighsInterface.cpp @@ -4264,8 +4264,7 @@ void HighsMipSolverInfo::clear() { HighsStatus Highs::mipRaceResults( HighsMipSolverInfo& mip_solver_info, const std::vector& worker_info, - const std::vector& mip_time, - const double& report_mip_time) { + const std::vector& mip_time, const double& report_mip_time) { const HighsInt mip_race_concurrency = this->options_.mip_race_concurrency; HighsInt winning_instance = -1; HighsModelStatus winning_model_status = HighsModelStatus::kNotset; @@ -4276,7 +4275,7 @@ HighsStatus Highs::mipRaceResults( instance == 0 ? mip_solver_info : worker_info[instance]; HighsModelStatus instance_model_status = solver_info.modelstatus; highsLogUser(options_.log_options, HighsLogType::kInfo, - " Solver %2d has best objective %15.8g, gap %6.2f\% (time " + " Solver %2d has best objective %15.8g, gap %6.2f%% (time " "= %6.2f), and status %s\n", int(instance), solver_info.solution_objective, 1e2 * solver_info.gap, mip_time[instance], diff --git a/highs/mip/HighsMipSolver.h b/highs/mip/HighsMipSolver.h index 57516fb2913..bc15d41345a 100644 --- a/highs/mip/HighsMipSolver.h +++ b/highs/mip/HighsMipSolver.h @@ -8,6 +8,8 @@ #ifndef MIP_HIGHS_MIP_SOLVER_H_ #define MIP_HIGHS_MIP_SOLVER_H_ +#include + #include "Highs.h" #include "lp_data/HighsCallback.h" #include "lp_data/HighsOptions.h" @@ -21,6 +23,7 @@ class HighsImplications; const HighsInt kMipRaceNoSolution = -1; +/* struct MipRaceIncumbent { HighsInt start_write_incumbent = kMipRaceNoSolution; HighsInt finish_write_incumbent = kMipRaceNoSolution; @@ -32,11 +35,11 @@ struct MipRaceIncumbent { HighsInt read(const HighsInt last_incumbent_read, double& objective_, std::vector& solution_) const; }; +*/ -/* - struct MipRaceIncumbent { - std::atomic start_write_incumbent = kMipRaceNoSolution; - std::atomic finish_write_incumbent = kMipRaceNoSolution; +struct MipRaceIncumbent { + std::atomic start_write_incumbent{kMipRaceNoSolution}; + std::atomic finish_write_incumbent{kMipRaceNoSolution}; double objective = -kHighsInf; std::vector solution; void clear(); @@ -61,7 +64,7 @@ struct MipRaceIncumbent { solution = std::move(moving.solution); } }; -*/ + struct MipRaceRecord { std::vector incumbent; void clear(); @@ -83,7 +86,7 @@ struct MipRace { const HighsLogOptions log_options_); HighsInt concurrency() const; void update(const double objective, const std::vector& solution); - bool newSolution(const HighsInt instance, double objective, + bool newSolution(const HighsInt instance, double& objective, std::vector& solution); void report() const; }; diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index cb2c21e15a6..baf53fe95db 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -1401,6 +1401,20 @@ bool HighsMipSolverData::addIncumbent(const std::vector& sol, sol, possibly_store_as_new_incumbent) : 0; + if (solution_source == kSolutionSourceHighsSolution) { + printf( + "HighsMipSolverData::addIncumbent HiGHS solution Offset = %15.8g; Obj " + "= %15.8g; UB = %15.8g; PossAdd = %s", + mipsolver.model_->offset_, solobj, upper_bound, + possibly_store_as_new_incumbent ? "T" : "F"); + if (possibly_store_as_new_incumbent) { + printf("; TransObj = %15.8g; TransSolobj < UB %s \n", transformed_solobj, + transformed_solobj < upper_bound ? "T" : "F"); + } else { + printf("\n"); + } + fflush(stdout); + } if (possibly_store_as_new_incumbent) { solobj = transformed_solobj; if (solobj >= upper_bound) return false; @@ -2677,18 +2691,57 @@ void HighsMipSolverData::queryExternalSolution( if (!mipsolver.options_mip_->mip_race_read_solutions) return; MipRace& mip_race = mipsolver.mip_race_; if (!mip_race.record) return; - double instance_solution_objective_value = kHighsInf; + double instance_objective_value = kHighsInf; std::vector instance_solution; for (HighsInt instance = 0; instance < mipRaceConcurrency(); instance++) { if (instance == mip_race.my_instance) continue; - if (!mip_race.newSolution(instance, instance_solution_objective_value, + if (!mip_race.newSolution(instance, instance_objective_value, instance_solution)) continue; // Have read a new incumbent - std::vector reduced_instance_solution; - reduced_instance_solution = + // + // Objective is assumed to be original_offset + (original_c)^T(original_x), + // but MIP solver bounds are based on the reduced objective + // (reduced_c)^T(reduced_x) + // + // Now, original_sense*[reduced_offset + (reduced_c)^T(reduced_x)] is an + // objective in the original space, so + // + // f0 + c0^Tx0 = s*(f1 + c1^Tx1) + // + // where 0 => original; 1 => reduced + // + // This allows the reduced objective value to be deduced as + // + // c1^Tx1 = s*(f0 + c0^Tx0) - f1 + // + // (reduced_c)^T(reduced_x) = original_sense*[original_offset + + // (original_c)^T(original_x) - reduced_offset] + // + double reduced_instance_objective_value = instance_objective_value; + reduced_instance_objective_value *= int(mipsolver.orig_model_->sense_); + reduced_instance_objective_value -= mipsolver.model_->offset_; + // Get the solution in the reduced space + std::vector reduced_instance_solution = postSolveStack.getReducedPrimalSolution(instance_solution); - addIncumbent(reduced_instance_solution, instance_solution_objective_value, + + double check_objective_value = 0; + for (HighsInt iCol = 0; iCol < mipsolver.model_->num_col_; iCol++) + check_objective_value += + mipsolver.colCost(iCol) * reduced_instance_solution[iCol]; + double dl_objective_value = + std::fabs(check_objective_value - reduced_instance_objective_value); + assert(dl_objective_value < 1e-12 * (1 + std::fabs(check_objective_value))); + printf( + "HighsMipSolverData::queryExternalSolution: (sense = %d; offset = " + "%11.4g) modified objective from %11.4g to %11.4g (Check = %11.4g; " + "Delta = %11.4g)\n", + int(mipsolver.orig_model_->sense_), mipsolver.model_->offset_, + instance_objective_value, reduced_instance_objective_value, + check_objective_value, dl_objective_value); + fflush(stdout); + + addIncumbent(reduced_instance_solution, reduced_instance_objective_value, kSolutionSourceHighsSolution); } } @@ -2956,7 +3009,7 @@ void MipRaceRecord::report(const HighsLogOptions log_options) const { highsLogUser(log_options, HighsLogType::kInfo, "\nStartWrite: "); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) highsLogUser(log_options, HighsLogType::kInfo, " %20d", - this->incumbent[instance].start_write_incumbent); + int(this->incumbent[instance].start_write_incumbent)); highsLogUser(log_options, HighsLogType::kInfo, "\nObjective: "); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) highsLogUser(log_options, HighsLogType::kInfo, " %20.12g", @@ -2964,7 +3017,7 @@ void MipRaceRecord::report(const HighsLogOptions log_options) const { highsLogUser(log_options, HighsLogType::kInfo, "\nFinishWrite: "); for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) highsLogUser(log_options, HighsLogType::kInfo, " %20d", - this->incumbent[instance].finish_write_incumbent); + int(this->incumbent[instance].finish_write_incumbent)); highsLogUser(log_options, HighsLogType::kInfo, "\n"); } @@ -2997,7 +3050,7 @@ void MipRace::update(const double objective, // this->report(); } -bool MipRace::newSolution(const HighsInt instance, double objective, +bool MipRace::newSolution(const HighsInt instance, double& objective, std::vector& solution) { assert(this->record); HighsInt new_incumbent_read = this->record->incumbent[instance].read( From 6add9566a92108769786ab81e954311cda4081b4 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Tue, 29 Jul 2025 21:58:28 +0100 Subject: [PATCH 50/58] Need to check for feasibility in transformed space when solution received from another thread --- check/TestMipSolver.cpp | 2 +- highs/mip/HighsMipSolverData.cpp | 59 +++++++++++++++++++------------- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index 347eb13479b..3d0a3a925f2 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -1037,7 +1037,7 @@ TEST_CASE("issue-2432", "[highs_test_mip_solver]") { } TEST_CASE("mip-race", "[highs_test_mip_solver]") { - const bool ci_test = true; + const bool ci_test = false; const std::string test_build_model = "neos-3381206-awhea"; //"fiball"; const std::string model = ci_test ? "flugpl" : test_build_model; // "neos-3381206-awhea"; diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index baf53fe95db..9de7cb251c4 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -1401,18 +1401,17 @@ bool HighsMipSolverData::addIncumbent(const std::vector& sol, sol, possibly_store_as_new_incumbent) : 0; - if (solution_source == kSolutionSourceHighsSolution) { - printf( - "HighsMipSolverData::addIncumbent HiGHS solution Offset = %15.8g; Obj " - "= %15.8g; UB = %15.8g; PossAdd = %s", - mipsolver.model_->offset_, solobj, upper_bound, - possibly_store_as_new_incumbent ? "T" : "F"); - if (possibly_store_as_new_incumbent) { - printf("; TransObj = %15.8g; TransSolobj < UB %s \n", transformed_solobj, - transformed_solobj < upper_bound ? "T" : "F"); - } else { - printf("\n"); - } + if (solution_source == kSolutionSourceHighsSolution + //&& possibly_store_as_new_incumbent + ) { + highsLogUser(mipsolver.options_mip_->log_options, HighsLogType::kInfo, + "HighsMipSolverData::addIncumbent HiGHS solution Offset = %15.8g; Obj " + "= %15.8g; UB = %15.8g; Obj-UB = %11.4g; PossAdd = %s; TransObj = %15.8g; TransObj-UB = %11.4g; TransSolobj < UB %s \n", + mipsolver.model_->offset_, solobj, upper_bound, + solobj-upper_bound, + possibly_store_as_new_incumbent ? "T" : "F", transformed_solobj, + transformed_solobj - upper_bound, + transformed_solobj < upper_bound ? "T" : "F"); fflush(stdout); } if (possibly_store_as_new_incumbent) { @@ -2718,9 +2717,15 @@ void HighsMipSolverData::queryExternalSolution( // (reduced_c)^T(reduced_x) = original_sense*[original_offset + // (original_c)^T(original_x) - reduced_offset] // + // However, this isn't right when one solver has performed + // restart, and another hasn't. So, ignore the objective value + // that's passed, and compute it anew + + /* double reduced_instance_objective_value = instance_objective_value; reduced_instance_objective_value *= int(mipsolver.orig_model_->sense_); reduced_instance_objective_value -= mipsolver.model_->offset_; + */ // Get the solution in the reduced space std::vector reduced_instance_solution = postSolveStack.getReducedPrimalSolution(instance_solution); @@ -2729,18 +2734,24 @@ void HighsMipSolverData::queryExternalSolution( for (HighsInt iCol = 0; iCol < mipsolver.model_->num_col_; iCol++) check_objective_value += mipsolver.colCost(iCol) * reduced_instance_solution[iCol]; - double dl_objective_value = - std::fabs(check_objective_value - reduced_instance_objective_value); - assert(dl_objective_value < 1e-12 * (1 + std::fabs(check_objective_value))); - printf( - "HighsMipSolverData::queryExternalSolution: (sense = %d; offset = " - "%11.4g) modified objective from %11.4g to %11.4g (Check = %11.4g; " - "Delta = %11.4g)\n", - int(mipsolver.orig_model_->sense_), mipsolver.model_->offset_, - instance_objective_value, reduced_instance_objective_value, - check_objective_value, dl_objective_value); - fflush(stdout); - + double reduced_instance_objective_value = check_objective_value; + /* + double abs_dl_objective_value = std::fabs(check_objective_value - reduced_instance_objective_value); + double rlv_dl_objective_value = abs_dl_objective_value / (1 + std::fabs(check_objective_value)); + if (rlv_dl_objective_value >= 0 || + std::fabs(mipsolver.model_->offset_) > 1) { + highsLogUser(mipsolver.options_mip_->log_options, HighsLogType::kInfo, + "HighsMipSolverData::queryExternalSolution: Instance %1d (sense = %d; offset = " + "%11.4g; OGoffset = %11.4g) modified objective from %11.4g to %11.4g (Check = %11.4g; " + "Delta = (abs = %11.4g; rlv = %11.4g)\n", + int(instance), int(mipsolver.orig_model_->sense_), mipsolver.model_->offset_, mipsolver.orig_model_->offset_, + instance_objective_value, reduced_instance_objective_value, + check_objective_value, abs_dl_objective_value, rlv_dl_objective_value); + fflush(stdout); + reduced_instance_objective_value = check_objective_value; + // assert(rlv_dl_objective_value < 1e-12); + } + */ addIncumbent(reduced_instance_solution, reduced_instance_objective_value, kSolutionSourceHighsSolution); } From 7d4c8be3f2d749fc1408517c0e868f9f814c2a76 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 30 Jul 2025 09:46:58 +0100 Subject: [PATCH 51/58] Now checking instance solution for feasibility, and computing the corresponding objective locally --- check/TestMipSolver.cpp | 16 ++--------- highs/mip/HighsMipSolverData.cpp | 47 +++++++++++++------------------- 2 files changed, 22 insertions(+), 41 deletions(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index 3d0a3a925f2..226ac9ceff1 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -1038,7 +1038,7 @@ TEST_CASE("issue-2432", "[highs_test_mip_solver]") { TEST_CASE("mip-race", "[highs_test_mip_solver]") { const bool ci_test = false; - const std::string test_build_model = "neos-3381206-awhea"; //"fiball"; + const std::string test_build_model = "fiball";//"neos-3381206-awhea"; // const std::string model = ci_test ? "flugpl" : test_build_model; // "neos-3381206-awhea"; const std::string model_file = @@ -1050,18 +1050,8 @@ TEST_CASE("mip-race", "[highs_test_mip_solver]") { h.setOptionValue("mip_race_concurrency", mip_race_concurrency); h.setOptionValue("mip_race_read_solutions", true); REQUIRE(h.readModel(model_file) == HighsStatus::kOk); - for (int k = 0; k < 2; k++) { - if (k == 1) { - HighsLp lp = h.getLp(); - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) - lp.col_cost_[iCol] = -lp.col_cost_[iCol]; - REQUIRE(h.changeColsCost(0, lp.num_col_ - 1, lp.col_cost_.data()) == - HighsStatus::kOk); - REQUIRE(h.changeObjectiveSense(ObjSense::kMaximize) == HighsStatus::kOk); - } - REQUIRE(h.run() == HighsStatus::kOk); - REQUIRE(h.getModelStatus() == HighsModelStatus::kOptimal); - } + REQUIRE(h.run() == HighsStatus::kOk); + REQUIRE(h.getModelStatus() == HighsModelStatus::kOptimal); if (ci_test) { h.clearSolver(); diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index 9de7cb251c4..2a2d791404e 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -1400,20 +1400,21 @@ bool HighsMipSolverData::addIncumbent(const std::vector& sol, get_transformed_solution ? transformNewIntegerFeasibleSolution( sol, possibly_store_as_new_incumbent) : 0; - + /* if (solution_source == kSolutionSourceHighsSolution //&& possibly_store_as_new_incumbent ) { highsLogUser(mipsolver.options_mip_->log_options, HighsLogType::kInfo, - "HighsMipSolverData::addIncumbent HiGHS solution Offset = %15.8g; Obj " + "HighsMipSolverData::addIncumbent HiGHS solution Obj " "= %15.8g; UB = %15.8g; Obj-UB = %11.4g; PossAdd = %s; TransObj = %15.8g; TransObj-UB = %11.4g; TransSolobj < UB %s \n", - mipsolver.model_->offset_, solobj, upper_bound, + solobj, upper_bound, solobj-upper_bound, possibly_store_as_new_incumbent ? "T" : "F", transformed_solobj, transformed_solobj - upper_bound, transformed_solobj < upper_bound ? "T" : "F"); fflush(stdout); } + */ if (possibly_store_as_new_incumbent) { solobj = transformed_solobj; if (solobj >= upper_bound) return false; @@ -2721,37 +2722,27 @@ void HighsMipSolverData::queryExternalSolution( // restart, and another hasn't. So, ignore the objective value // that's passed, and compute it anew - /* - double reduced_instance_objective_value = instance_objective_value; - reduced_instance_objective_value *= int(mipsolver.orig_model_->sense_); - reduced_instance_objective_value -= mipsolver.model_->offset_; - */ // Get the solution in the reduced space std::vector reduced_instance_solution = postSolveStack.getReducedPrimalSolution(instance_solution); - double check_objective_value = 0; + // Reduced solution can be infeasible if restart has been + // performed + if (!checkSolution(reduced_instance_solution)) { + /* + highsLogUser( + mipsolver.options_mip_->log_options, HighsLogType::kWarning, + "Solution from instance %2d is not feasible for instance %2d\n", + int(instance), int(mip_race.my_instance)); + */ + continue; + } + + + double reduced_instance_objective_value = 0; for (HighsInt iCol = 0; iCol < mipsolver.model_->num_col_; iCol++) - check_objective_value += + reduced_instance_objective_value += mipsolver.colCost(iCol) * reduced_instance_solution[iCol]; - double reduced_instance_objective_value = check_objective_value; - /* - double abs_dl_objective_value = std::fabs(check_objective_value - reduced_instance_objective_value); - double rlv_dl_objective_value = abs_dl_objective_value / (1 + std::fabs(check_objective_value)); - if (rlv_dl_objective_value >= 0 || - std::fabs(mipsolver.model_->offset_) > 1) { - highsLogUser(mipsolver.options_mip_->log_options, HighsLogType::kInfo, - "HighsMipSolverData::queryExternalSolution: Instance %1d (sense = %d; offset = " - "%11.4g; OGoffset = %11.4g) modified objective from %11.4g to %11.4g (Check = %11.4g; " - "Delta = (abs = %11.4g; rlv = %11.4g)\n", - int(instance), int(mipsolver.orig_model_->sense_), mipsolver.model_->offset_, mipsolver.orig_model_->offset_, - instance_objective_value, reduced_instance_objective_value, - check_objective_value, abs_dl_objective_value, rlv_dl_objective_value); - fflush(stdout); - reduced_instance_objective_value = check_objective_value; - // assert(rlv_dl_objective_value < 1e-12); - } - */ addIncumbent(reduced_instance_solution, reduced_instance_objective_value, kSolutionSourceHighsSolution); } From 84f4bb51b798b4c88c64fbcfc344e01a53c7933e Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 30 Jul 2025 10:14:07 +0100 Subject: [PATCH 52/58] Add single presolve option to MIP race --- highs/mip/HighsMipSolverData.cpp | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index 2a2d791404e..1a247badcec 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -1400,8 +1400,9 @@ bool HighsMipSolverData::addIncumbent(const std::vector& sol, get_transformed_solution ? transformNewIntegerFeasibleSolution( sol, possibly_store_as_new_incumbent) : 0; - /* + const bool highs_solution_report = true; if (solution_source == kSolutionSourceHighsSolution + && highs_solution_report //&& possibly_store_as_new_incumbent ) { highsLogUser(mipsolver.options_mip_->log_options, HighsLogType::kInfo, @@ -1414,7 +1415,6 @@ bool HighsMipSolverData::addIncumbent(const std::vector& sol, transformed_solobj < upper_bound ? "T" : "F"); fflush(stdout); } - */ if (possibly_store_as_new_incumbent) { solobj = transformed_solobj; if (solobj >= upper_bound) return false; @@ -2729,20 +2729,19 @@ void HighsMipSolverData::queryExternalSolution( // Reduced solution can be infeasible if restart has been // performed if (!checkSolution(reduced_instance_solution)) { - /* - highsLogUser( - mipsolver.options_mip_->log_options, HighsLogType::kWarning, - "Solution from instance %2d is not feasible for instance %2d\n", - int(instance), int(mip_race.my_instance)); - */ + const bool feasibility_warning = true; + if (feasibility_warning) { + highsLogUser(mipsolver.options_mip_->log_options, HighsLogType::kWarning, + "Solution from instance %2d is not feasible for instance %2d\n", + int(instance), int(mip_race.my_instance)); + } continue; } - - - double reduced_instance_objective_value = 0; + HighsCDouble reduced_instance_quad_objective_value = 0; for (HighsInt iCol = 0; iCol < mipsolver.model_->num_col_; iCol++) - reduced_instance_objective_value += - mipsolver.colCost(iCol) * reduced_instance_solution[iCol]; + reduced_instance_quad_objective_value += + mipsolver.colCost(iCol) * reduced_instance_solution[iCol]; + double reduced_instance_objective_value = double(reduced_instance_quad_objective_value); addIncumbent(reduced_instance_solution, reduced_instance_objective_value, kSolutionSourceHighsSolution); } From 6011753047fe862b4169aea47d68295310ffc6ee Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 30 Jul 2025 11:20:16 +0100 Subject: [PATCH 53/58] Still passes bin/unit_tests mip-race --- check/TestMipSolver.cpp | 10 ++++-- highs/Highs.h | 5 +-- highs/lp_data/Highs.cpp | 53 +++++++++++++++++++++++--------- highs/lp_data/HighsInterface.cpp | 15 ++++++--- highs/lp_data/HighsOptions.h | 8 +++++ 5 files changed, 67 insertions(+), 24 deletions(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index 226ac9ceff1..a260b705520 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -1049,9 +1049,13 @@ TEST_CASE("mip-race", "[highs_test_mip_solver]") { const HighsInt mip_race_concurrency = ci_test ? 2 : 4; h.setOptionValue("mip_race_concurrency", mip_race_concurrency); h.setOptionValue("mip_race_read_solutions", true); - REQUIRE(h.readModel(model_file) == HighsStatus::kOk); - REQUIRE(h.run() == HighsStatus::kOk); - REQUIRE(h.getModelStatus() == HighsModelStatus::kOptimal); + for (HighsInt k = 0; k < 1; k++) { + bool mip_race_single_presolve = k == 0 ? false : true; + h.setOptionValue("mip_race_single_presolve", mip_race_single_presolve); + REQUIRE(h.readModel(model_file) == HighsStatus::kOk); + REQUIRE(h.run() == HighsStatus::kOk); + REQUIRE(h.getModelStatus() == HighsModelStatus::kOptimal); + } if (ci_test) { h.clearSolver(); diff --git a/highs/Highs.h b/highs/Highs.h index b12fa47c5ab..b19512ac91d 100644 --- a/highs/Highs.h +++ b/highs/Highs.h @@ -1714,10 +1714,11 @@ class Highs { bool optionsHasHighsFiles() const; void saveHighsFiles(); void getHighsFiles(); - HighsStatus mipRaceResults(HighsMipSolverInfo& mip_solver_info, + HighsStatus mipRaceResults(bool use_mip_race_single_presolve, + HighsMipSolverInfo& mip_solver_info, const std::vector& worker_info, const std::vector& mip_time, - const double& report_mip_time); + double mip_race_time); }; // Start of deprecated methods not in the Highs class diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index 2840acf0fd0..da22ac54ed8 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -4031,13 +4031,45 @@ HighsStatus Highs::callSolveMip() { options_.primal_feasibility_tolerance); } HighsLp& lp = has_semi_variables ? use_lp : model_.lp_; - + // Start timing any MIP race before its presolve and parallel MIP + // solver calls. This timer is stopped in Highs::mipRaceResults + // after any postsolve is performed + double mip_race_time = -this->timer_.read(); + HighsLp presolved_lp; + const HighsInt mip_race_concurrency = this->options_.mip_race_concurrency; + const bool run_mip_race = mip_race_concurrency > 1; + // Determine whether to do a single presolve before any MIP race + // + // Doesn't work when there are semi-variables since they have to be + // converted into the use_lp instance, and this->presolve(); works + // on the incumbent model + const bool use_mip_race_single_presolve = + run_mip_race && + options_.mip_race_single_presolve && + options_.presolve != kHighsOffString && + !has_semi_variables; + if (use_mip_race_single_presolve) { + // Perform presolve before the MIP race + // + // NB This is normally called externally, so calls + // returnFromHighs(). This will stop the run clock, and check that + // called_return_from_optimize_model is true - when it isn't. So, + // add a hack to set called_return_from_optimize_model true before + // the call to presolve... + assert(!this->called_return_from_optimize_model); + this->called_return_from_optimize_model = true; + this->presolve(); + // ... then set it back to false and restart the run clock + this->called_return_from_optimize_model = false; + this->timer_.start(); + presolved_lp = this->getPresolvedLp(); + lp = presolved_lp; + } // Create the master MIP solver instance that will exist beyond any - // race + // MIP race HighsMipSolver solver(callback_, options_, lp, solution_); HighsMipSolverInfo mip_solver_info; - const HighsInt mip_race_concurrency = this->options_.mip_race_concurrency; - if (mip_race_concurrency > 1) { + if (run_mip_race) { // Set up the shared memory for the MIP solver race MipRaceRecord mip_race_record; mip_race_record.initialise(mip_race_concurrency, lp.num_col_); @@ -4070,14 +4102,7 @@ HighsStatus Highs::callSolveMip() { if (options_.output_flag) highsOpenLogFile(instance_options, worker_log_file); worker_options.push_back(instance_options); - /* - HighsMipSolver worker_instance(worker_callback, worker_options[instance], - lp, solution_); worker.push_back(&worker_instance); - */ } - // Time the master outside the parallel loop so that this "real" - // time is reported - double loop_mip_time = -timer_.read(); highs::parallel::for_each( 0, mip_race_concurrency, [&](HighsInt start, HighsInt end) { for (HighsInt instance = start; instance < end; instance++) { @@ -4104,10 +4129,10 @@ HighsStatus Highs::callSolveMip() { } } }); - loop_mip_time += timer_.read(); // Determine the winner and report on the solution - HighsStatus call_status = this->mipRaceResults(mip_solver_info, worker_info, - mip_time, loop_mip_time); + HighsStatus call_status = this->mipRaceResults(use_mip_race_single_presolve, + mip_solver_info, worker_info, + mip_time, mip_race_time); if (call_status == HighsStatus::kError) { const bool undo_mods = true; return returnFromOptimizeModel(HighsStatus::kError, undo_mods); diff --git a/highs/lp_data/HighsInterface.cpp b/highs/lp_data/HighsInterface.cpp index 0565bd19fe9..254689347a7 100644 --- a/highs/lp_data/HighsInterface.cpp +++ b/highs/lp_data/HighsInterface.cpp @@ -4261,10 +4261,11 @@ void HighsMipSolverInfo::clear() { this->primal_dual_integral = -kHighsInf; } -HighsStatus Highs::mipRaceResults( - HighsMipSolverInfo& mip_solver_info, - const std::vector& worker_info, - const std::vector& mip_time, const double& report_mip_time) { +HighsStatus Highs::mipRaceResults(bool use_mip_race_single_presolve, + HighsMipSolverInfo& mip_solver_info, + const std::vector& worker_info, + const std::vector& mip_time, + double mip_race_time) { const HighsInt mip_race_concurrency = this->options_.mip_race_concurrency; HighsInt winning_instance = -1; HighsModelStatus winning_model_status = HighsModelStatus::kNotset; @@ -4301,6 +4302,10 @@ HighsStatus Highs::mipRaceResults( } } if (winning_instance > 0) mip_solver_info = worker_info[winning_instance]; + + if (use_mip_race_single_presolve) assert(111==444); + mip_race_time += this->timer_.read(); + std::array gapString = getGapString( mip_solver_info.gap, mip_solver_info.primal_bound, &options_); @@ -4351,7 +4356,7 @@ HighsStatus Highs::mipRaceResults( // Report the solution time for the whole concurrent loop, as that's // "real" time highsLogUser(options_.log_options, HighsLogType::kInfo, - " Timing %.2f\n", report_mip_time); + " Timing %.2f\n", mip_race_time); highsLogUser(options_.log_options, HighsLogType::kInfo, " Max sub-MIP depth %d\n", int(mip_solver_info.max_submip_level)); diff --git a/highs/lp_data/HighsOptions.h b/highs/lp_data/HighsOptions.h index 33e9670a77e..b37eea1567c 100644 --- a/highs/lp_data/HighsOptions.h +++ b/highs/lp_data/HighsOptions.h @@ -426,6 +426,7 @@ struct HighsOptionsStruct { bool mip_allow_restart; HighsInt mip_race_concurrency; bool mip_race_read_solutions; + bool mip_race_single_presolve; HighsInt mip_max_nodes; HighsInt mip_max_stall_nodes; HighsInt mip_max_start_nodes; @@ -579,6 +580,7 @@ struct HighsOptionsStruct { mip_allow_restart(false), mip_race_concurrency(0), mip_race_read_solutions(false), + mip_race_single_presolve(false), mip_max_nodes(0), mip_max_stall_nodes(0), mip_max_start_nodes(0), @@ -1032,6 +1034,12 @@ class HighsOptions : public HighsOptionsStruct { &mip_race_read_solutions, true); records.push_back(record_bool); + record_bool = new OptionRecordBool( + "mip_race_single_presolve", + "Whether the MIP races should follow a single presolve", advanced, + &mip_race_single_presolve, true); + records.push_back(record_bool); + record_int = new OptionRecordInt("mip_max_nodes", "MIP solver max number of nodes", advanced, &mip_max_nodes, 0, kHighsIInf, kHighsIInf); From acd2cfce1dab593ca1438016d54bc6eaddb63757 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 30 Jul 2025 13:26:42 +0100 Subject: [PATCH 54/58] Now able to run MIP race on single presolved model --- check/TestMipSolver.cpp | 6 +++--- highs/lp_data/Highs.cpp | 13 ++++++++++--- highs/lp_data/HighsInterface.cpp | 25 +++++++++++++++++++++++-- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index a260b705520..2d9f50c3a84 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -1037,7 +1037,7 @@ TEST_CASE("issue-2432", "[highs_test_mip_solver]") { } TEST_CASE("mip-race", "[highs_test_mip_solver]") { - const bool ci_test = false; + const bool ci_test = true; const std::string test_build_model = "fiball";//"neos-3381206-awhea"; // const std::string model = ci_test ? "flugpl" : test_build_model; // "neos-3381206-awhea"; @@ -1045,12 +1045,12 @@ TEST_CASE("mip-race", "[highs_test_mip_solver]") { ci_test ? std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps" : "/srv/miplib2017/" + model + ".mps.gz"; Highs h; - if (ci_test) h.setOptionValue("output_flag", dev_run); + // if (ci_test) h.setOptionValue("output_flag", dev_run); const HighsInt mip_race_concurrency = ci_test ? 2 : 4; h.setOptionValue("mip_race_concurrency", mip_race_concurrency); h.setOptionValue("mip_race_read_solutions", true); for (HighsInt k = 0; k < 1; k++) { - bool mip_race_single_presolve = k == 0 ? false : true; + bool mip_race_single_presolve = k == 1 ? false : true; h.setOptionValue("mip_race_single_presolve", mip_race_single_presolve); REQUIRE(h.readModel(model_file) == HighsStatus::kOk); REQUIRE(h.run() == HighsStatus::kOk); diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index da22ac54ed8..8f2fd5e08a4 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -4030,7 +4030,6 @@ HighsStatus Highs::callSolveMip() { use_lp = withoutSemiVariables(model_.lp_, solution_, options_.primal_feasibility_tolerance); } - HighsLp& lp = has_semi_variables ? use_lp : model_.lp_; // Start timing any MIP race before its presolve and parallel MIP // solver calls. This timer is stopped in Highs::mipRaceResults // after any postsolve is performed @@ -4048,10 +4047,13 @@ HighsStatus Highs::callSolveMip() { options_.mip_race_single_presolve && options_.presolve != kHighsOffString && !has_semi_variables; + // Take a copy of the presolve option in case it's switched off for + // a single presolve MIP race + const std::string presolve = this->options_.presolve; if (use_mip_race_single_presolve) { // Perform presolve before the MIP race // - // NB This is normally called externally, so calls + // NB Highs::presolve() is normally called externally, so calls // returnFromHighs(). This will stop the run clock, and check that // called_return_from_optimize_model is true - when it isn't. So, // add a hack to set called_return_from_optimize_model true before @@ -4063,10 +4065,11 @@ HighsStatus Highs::callSolveMip() { this->called_return_from_optimize_model = false; this->timer_.start(); presolved_lp = this->getPresolvedLp(); - lp = presolved_lp; + this->options_.presolve = kHighsOffString; } // Create the master MIP solver instance that will exist beyond any // MIP race + HighsLp& lp = has_semi_variables ? use_lp : (use_mip_race_single_presolve ? presolved_lp : model_.lp_); HighsMipSolver solver(callback_, options_, lp, solution_); HighsMipSolverInfo mip_solver_info; if (run_mip_race) { @@ -4133,6 +4136,10 @@ HighsStatus Highs::callSolveMip() { HighsStatus call_status = this->mipRaceResults(use_mip_race_single_presolve, mip_solver_info, worker_info, mip_time, mip_race_time); + // Restore the presolve option - that will have been set to + // kHighsOffString for a single presolve MIP race + this->options_.presolve = presolve; + if (call_status == HighsStatus::kError) { const bool undo_mods = true; return returnFromOptimizeModel(HighsStatus::kError, undo_mods); diff --git a/highs/lp_data/HighsInterface.cpp b/highs/lp_data/HighsInterface.cpp index 254689347a7..5e25ef63411 100644 --- a/highs/lp_data/HighsInterface.cpp +++ b/highs/lp_data/HighsInterface.cpp @@ -4303,13 +4303,34 @@ HighsStatus Highs::mipRaceResults(bool use_mip_race_single_presolve, } if (winning_instance > 0) mip_solver_info = worker_info[winning_instance]; - if (use_mip_race_single_presolve) assert(111==444); + const bool havesolution = mip_solver_info.solution_objective != kHighsInf; + + if (use_mip_race_single_presolve && havesolution) { + HighsSolution solution; + solution.col_value = mip_solver_info.solution; + // Perform postsolve after the MIP race + // + // NB Highs::postsolve() is normally called externally, so calls + // returnFromHighs(). This will stop the run clock, and check that + // called_return_from_optimize_model is true - when it isn't. So, + // add a hack to set called_return_from_optimize_model true before + // the call to postsolve... + assert(!this->called_return_from_optimize_model); + this->called_return_from_optimize_model = true; + + this->postsolve(solution); + // ... then set it back to false and restart the run clock + this->called_return_from_optimize_model = false; + this->timer_.start(); + + // Now update the MipSolverInfo with the postsolved solution + mip_solver_info.solution = this->getSolution().col_value; + } mip_race_time += this->timer_.read(); std::array gapString = getGapString( mip_solver_info.gap, mip_solver_info.primal_bound, &options_); - bool havesolution = mip_solver_info.solution_objective != kHighsInf; bool feasible; std::string solutionstatus = "-"; if (havesolution) { From 6cb49c3c351600a0cc4653c3621056294822deba Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 30 Jul 2025 16:00:01 +0100 Subject: [PATCH 55/58] Formatted --- check/TestMipSolver.cpp | 8 +++--- highs/Highs.h | 7 +++-- highs/lp_data/Highs.cpp | 38 ++++++++++++++----------- highs/lp_data/HighsInterface.cpp | 49 +++++++++++++++++++------------- highs/lp_data/HighsOptions.h | 2 +- highs/mip/HighsMipSolverData.cpp | 40 +++++++++++++++----------- 6 files changed, 85 insertions(+), 59 deletions(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index 2d9f50c3a84..d8910016897 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -1038,19 +1038,19 @@ TEST_CASE("issue-2432", "[highs_test_mip_solver]") { TEST_CASE("mip-race", "[highs_test_mip_solver]") { const bool ci_test = true; - const std::string test_build_model = "fiball";//"neos-3381206-awhea"; // + const std::string test_build_model = "fiball"; //"neos-3381206-awhea"; // const std::string model = ci_test ? "flugpl" : test_build_model; // "neos-3381206-awhea"; const std::string model_file = ci_test ? std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps" : "/srv/miplib2017/" + model + ".mps.gz"; Highs h; - // if (ci_test) h.setOptionValue("output_flag", dev_run); + if (ci_test) h.setOptionValue("output_flag", dev_run); const HighsInt mip_race_concurrency = ci_test ? 2 : 4; h.setOptionValue("mip_race_concurrency", mip_race_concurrency); h.setOptionValue("mip_race_read_solutions", true); - for (HighsInt k = 0; k < 1; k++) { - bool mip_race_single_presolve = k == 1 ? false : true; + for (HighsInt k = 0; k < 2; k++) { + bool mip_race_single_presolve = k == 0 ? false : true; h.setOptionValue("mip_race_single_presolve", mip_race_single_presolve); REQUIRE(h.readModel(model_file) == HighsStatus::kOk); REQUIRE(h.run() == HighsStatus::kOk); diff --git a/highs/Highs.h b/highs/Highs.h index b19512ac91d..176af9d4bca 100644 --- a/highs/Highs.h +++ b/highs/Highs.h @@ -1503,8 +1503,9 @@ class Highs { HighsStatus callSolveLp(HighsLp& lp, const string message); HighsStatus callSolveQp(); HighsStatus callSolveMip(); - HighsStatus callRunPostsolve(const HighsSolution& solution, - const HighsBasis& basis); + HighsStatus callRunPostsolve( + const HighsSolution& solution, const HighsBasis& basis, + const bool suppress_mip_model_status_warning = false); PresolveComponent presolve_; HighsPresolveStatus runPresolve(const bool force_lp_presolve, @@ -1715,7 +1716,7 @@ class Highs { void saveHighsFiles(); void getHighsFiles(); HighsStatus mipRaceResults(bool use_mip_race_single_presolve, - HighsMipSolverInfo& mip_solver_info, + HighsMipSolverInfo& mip_solver_info, const std::vector& worker_info, const std::vector& mip_time, double mip_race_time); diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index 8f2fd5e08a4..fa3e542dbba 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -4043,10 +4043,8 @@ HighsStatus Highs::callSolveMip() { // converted into the use_lp instance, and this->presolve(); works // on the incumbent model const bool use_mip_race_single_presolve = - run_mip_race && - options_.mip_race_single_presolve && - options_.presolve != kHighsOffString && - !has_semi_variables; + run_mip_race && options_.mip_race_single_presolve && + options_.presolve != kHighsOffString && !has_semi_variables; // Take a copy of the presolve option in case it's switched off for // a single presolve MIP race const std::string presolve = this->options_.presolve; @@ -4060,16 +4058,20 @@ HighsStatus Highs::callSolveMip() { // the call to presolve... assert(!this->called_return_from_optimize_model); this->called_return_from_optimize_model = true; - this->presolve(); + HighsStatus call_status = this->presolve(); // ... then set it back to false and restart the run clock this->called_return_from_optimize_model = false; this->timer_.start(); + if (call_status != HighsStatus::kOk) return call_status; presolved_lp = this->getPresolvedLp(); this->options_.presolve = kHighsOffString; } // Create the master MIP solver instance that will exist beyond any // MIP race - HighsLp& lp = has_semi_variables ? use_lp : (use_mip_race_single_presolve ? presolved_lp : model_.lp_); + HighsLp& lp = + has_semi_variables + ? use_lp + : (use_mip_race_single_presolve ? presolved_lp : model_.lp_); HighsMipSolver solver(callback_, options_, lp, solution_); HighsMipSolverInfo mip_solver_info; if (run_mip_race) { @@ -4084,7 +4086,7 @@ HighsStatus Highs::callSolveMip() { worker_callback.clear(); // Race the MIP solver! highsLogUser(options_.log_options, HighsLogType::kInfo, - "Starting MIP race with %d instances: behaviour is " + "\nStarting MIP race with %d instances: behaviour is " "non-deterministic!\n", int(mip_race_concurrency)); // Define the HighsMipSolverInfo record for each worker @@ -4133,9 +4135,9 @@ HighsStatus Highs::callSolveMip() { } }); // Determine the winner and report on the solution - HighsStatus call_status = this->mipRaceResults(use_mip_race_single_presolve, - mip_solver_info, worker_info, - mip_time, mip_race_time); + HighsStatus call_status = + this->mipRaceResults(use_mip_race_single_presolve, mip_solver_info, + worker_info, mip_time, mip_race_time); // Restore the presolve option - that will have been set to // kHighsOffString for a single presolve MIP race this->options_.presolve = presolve; @@ -4238,9 +4240,10 @@ HighsStatus Highs::callSolveMip() { return return_status; } -// Only called from Highs::postsolve -HighsStatus Highs::callRunPostsolve(const HighsSolution& solution, - const HighsBasis& basis) { +// Only called from Highs::postsolve and Highs::mipRaceResults +HighsStatus Highs::callRunPostsolve( + const HighsSolution& solution, const HighsBasis& basis, + const bool suppress_mip_model_status_warning) { HighsStatus return_status = HighsStatus::kOk; HighsStatus call_status; const HighsLp& presolved_lp = presolve_.getReducedProblem(); @@ -4305,9 +4308,12 @@ HighsStatus Highs::callRunPostsolve(const HighsSolution& solution, max_integrality_violation); } } - highsLogUser( - options_.log_options, HighsLogType::kWarning, - "Postsolve performed for MIP, but model status cannot be known\n"); + // When calling postsolve after MIP race on presolved model, + // model status can be trusted so suppress the warning message + if (!suppress_mip_model_status_warning) + highsLogUser( + options_.log_options, HighsLogType::kWarning, + "Postsolve performed for MIP, but model status cannot be known\n"); } else { highsLogUser(options_.log_options, HighsLogType::kError, "Postsolve return status is %d\n", (int)postsolve_status); diff --git a/highs/lp_data/HighsInterface.cpp b/highs/lp_data/HighsInterface.cpp index 5e25ef63411..055dbe27334 100644 --- a/highs/lp_data/HighsInterface.cpp +++ b/highs/lp_data/HighsInterface.cpp @@ -4261,11 +4261,10 @@ void HighsMipSolverInfo::clear() { this->primal_dual_integral = -kHighsInf; } -HighsStatus Highs::mipRaceResults(bool use_mip_race_single_presolve, - HighsMipSolverInfo& mip_solver_info, - const std::vector& worker_info, - const std::vector& mip_time, - double mip_race_time) { +HighsStatus Highs::mipRaceResults( + bool use_mip_race_single_presolve, HighsMipSolverInfo& mip_solver_info, + const std::vector& worker_info, + const std::vector& mip_time, double mip_race_time) { const HighsInt mip_race_concurrency = this->options_.mip_race_concurrency; HighsInt winning_instance = -1; HighsModelStatus winning_model_status = HighsModelStatus::kNotset; @@ -4310,22 +4309,34 @@ HighsStatus Highs::mipRaceResults(bool use_mip_race_single_presolve, solution.col_value = mip_solver_info.solution; // Perform postsolve after the MIP race // - // NB Highs::postsolve() is normally called externally, so calls - // returnFromHighs(). This will stop the run clock, and check that - // called_return_from_optimize_model is true - when it isn't. So, - // add a hack to set called_return_from_optimize_model true before - // the call to postsolve... - assert(!this->called_return_from_optimize_model); - this->called_return_from_optimize_model = true; - - this->postsolve(solution); - // ... then set it back to false and restart the run clock - this->called_return_from_optimize_model = false; - this->timer_.start(); - + // Set up an empty basis so that callRunPostsolve can be used + HighsBasis basis; + // Need to suppress the warning about the HighsModelStatus for + // MIPs that (in general) can't be set after postsolve + const bool suppress_mip_model_status_warning = true; + HighsStatus call_status = this->callRunPostsolve( + solution, basis, suppress_mip_model_status_warning); + // call_status will be HighsStatus::kWarning, since model_status_ + // is typically HighsModelStatus::kUnknown due to the lack of + // optimality test for a MIP. However, since this postsolve was + // run for a solution that was optimal for the presolved problem + // (and that status is in mip_solver_info) the warning can be + // ignored. + if (call_status == HighsStatus::kError) return call_status; // Now update the MipSolverInfo with the postsolved solution - mip_solver_info.solution = this->getSolution().col_value; + mip_solver_info.solution = this->solution_.col_value; } + const HighsInt mip_solver_info_solution_size = + mip_solver_info.solution.size(); + if (0 < mip_solver_info_solution_size && + mip_solver_info_solution_size < this->model_.lp_.num_col_) { + printf( + "Highs::mipRaceResults MipSolverInfo solution size = %d < %d = " + "num_col\n", + int(mip_solver_info_solution_size), int(this->model_.lp_.num_col_)); + assert(11 == 33); + } + mip_race_time += this->timer_.read(); std::array gapString = getGapString( diff --git a/highs/lp_data/HighsOptions.h b/highs/lp_data/HighsOptions.h index b37eea1567c..187bf1600e9 100644 --- a/highs/lp_data/HighsOptions.h +++ b/highs/lp_data/HighsOptions.h @@ -580,7 +580,7 @@ struct HighsOptionsStruct { mip_allow_restart(false), mip_race_concurrency(0), mip_race_read_solutions(false), - mip_race_single_presolve(false), + mip_race_single_presolve(false), mip_max_nodes(0), mip_max_stall_nodes(0), mip_max_start_nodes(0), diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index 1a247badcec..40e24f1890b 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -8,6 +8,7 @@ #include "mip/HighsMipSolverData.h" #include +#include // #include "lp_data/HighsLpUtils.h" #include "../extern/pdqsort/pdqsort.h" @@ -1400,19 +1401,24 @@ bool HighsMipSolverData::addIncumbent(const std::vector& sol, get_transformed_solution ? transformNewIntegerFeasibleSolution( sol, possibly_store_as_new_incumbent) : 0; - const bool highs_solution_report = true; - if (solution_source == kSolutionSourceHighsSolution - && highs_solution_report + const bool highs_solution_report = false; + if (solution_source == kSolutionSourceHighsSolution && highs_solution_report //&& possibly_store_as_new_incumbent - ) { + ) { + std::stringstream ss; + ss.str(std::string()); + ss << highsFormatToString( + "HighsMipSolverData::addIncumbent HiGHS solution Obj " + "= %15.8g; UB = %15.8g; Obj-UB = %11.4g; PossAdd = %s", + solobj, upper_bound, solobj - upper_bound, + possibly_store_as_new_incumbent ? "T" : "F"); + if (possibly_store_as_new_incumbent) + ss << highsFormatToString( + "; TransObj = %15.8g; TransObj-UB = %11.4g; TransSolobj < UB %s", + transformed_solobj, transformed_solobj - upper_bound, + transformed_solobj < upper_bound ? "T" : "F"); highsLogUser(mipsolver.options_mip_->log_options, HighsLogType::kInfo, - "HighsMipSolverData::addIncumbent HiGHS solution Obj " - "= %15.8g; UB = %15.8g; Obj-UB = %11.4g; PossAdd = %s; TransObj = %15.8g; TransObj-UB = %11.4g; TransSolobj < UB %s \n", - solobj, upper_bound, - solobj-upper_bound, - possibly_store_as_new_incumbent ? "T" : "F", transformed_solobj, - transformed_solobj - upper_bound, - transformed_solobj < upper_bound ? "T" : "F"); + "%s\n", ss.str().c_str()); fflush(stdout); } if (possibly_store_as_new_incumbent) { @@ -2731,17 +2737,19 @@ void HighsMipSolverData::queryExternalSolution( if (!checkSolution(reduced_instance_solution)) { const bool feasibility_warning = true; if (feasibility_warning) { - highsLogUser(mipsolver.options_mip_->log_options, HighsLogType::kWarning, - "Solution from instance %2d is not feasible for instance %2d\n", - int(instance), int(mip_race.my_instance)); + highsLogUser( + mipsolver.options_mip_->log_options, HighsLogType::kWarning, + "Solution from instance %2d is not feasible for instance %2d\n", + int(instance), int(mip_race.my_instance)); } continue; } HighsCDouble reduced_instance_quad_objective_value = 0; for (HighsInt iCol = 0; iCol < mipsolver.model_->num_col_; iCol++) reduced_instance_quad_objective_value += - mipsolver.colCost(iCol) * reduced_instance_solution[iCol]; - double reduced_instance_objective_value = double(reduced_instance_quad_objective_value); + mipsolver.colCost(iCol) * reduced_instance_solution[iCol]; + double reduced_instance_objective_value = + double(reduced_instance_quad_objective_value); addIncumbent(reduced_instance_solution, reduced_instance_objective_value, kSolutionSourceHighsSolution); } From d77e73982942cdc022f1c45b04f5e21b6b77d3e4 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 30 Jul 2025 18:03:57 +0100 Subject: [PATCH 56/58] Removed stray printf and isolated health warning logging for MIP race --- highs/lp_data/Highs.cpp | 2 +- highs/mip/HighsMipSolverData.cpp | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index fa3e542dbba..06d8437ae83 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -4087,7 +4087,7 @@ HighsStatus Highs::callSolveMip() { // Race the MIP solver! highsLogUser(options_.log_options, HighsLogType::kInfo, "\nStarting MIP race with %d instances: behaviour is " - "non-deterministic!\n", + "non-deterministic!\n\n", int(mip_race_concurrency)); // Define the HighsMipSolverInfo record for each worker std::vector worker_info(mip_race_concurrency); diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index 40e24f1890b..82f2433411f 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -1331,13 +1331,7 @@ void HighsMipSolverData::performRestart() { // Bounds are currently in the original space since presolve will have // changed offset_ runSetup(); - if (mipsolver.terminate()) { - printf( - "HighsMipSolverData::performRestart() mipsolver.termination_status_ = " - "%d\n", - int(mipsolver.termination_status_)); - return; - } + if (mipsolver.terminate()) return; postSolveStack.removeCutsFromModel(numCuts); From 0c3ddc5690e0fc178afd3976caa87e1589fdfae9 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Tue, 26 Aug 2025 09:35:57 +0100 Subject: [PATCH 57/58] Deleted MIP race code --- check/TestMipSolver.cpp | 30 ---- highs/Highs.h | 10 +- highs/lp_data/Highs.cpp | 147 ++---------------- highs/lp_data/HighsInterface.cpp | 140 ----------------- highs/lp_data/HighsOptions.h | 23 --- highs/mip/HighsMipSolver.cpp | 13 +- highs/mip/HighsMipSolver.h | 76 ---------- highs/mip/HighsMipSolverData.cpp | 253 +++---------------------------- 8 files changed, 30 insertions(+), 662 deletions(-) diff --git a/check/TestMipSolver.cpp b/check/TestMipSolver.cpp index d8910016897..0d2a2d792b9 100644 --- a/check/TestMipSolver.cpp +++ b/check/TestMipSolver.cpp @@ -1035,33 +1035,3 @@ TEST_CASE("issue-2432", "[highs_test_mip_solver]") { "found\n"); solve(highs, kHighsOffString, require_model_status, optimal_objective); } - -TEST_CASE("mip-race", "[highs_test_mip_solver]") { - const bool ci_test = true; - const std::string test_build_model = "fiball"; //"neos-3381206-awhea"; // - const std::string model = ci_test ? "flugpl" : test_build_model; - // "neos-3381206-awhea"; - const std::string model_file = - ci_test ? std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps" - : "/srv/miplib2017/" + model + ".mps.gz"; - Highs h; - if (ci_test) h.setOptionValue("output_flag", dev_run); - const HighsInt mip_race_concurrency = ci_test ? 2 : 4; - h.setOptionValue("mip_race_concurrency", mip_race_concurrency); - h.setOptionValue("mip_race_read_solutions", true); - for (HighsInt k = 0; k < 2; k++) { - bool mip_race_single_presolve = k == 0 ? false : true; - h.setOptionValue("mip_race_single_presolve", mip_race_single_presolve); - REQUIRE(h.readModel(model_file) == HighsStatus::kOk); - REQUIRE(h.run() == HighsStatus::kOk); - REQUIRE(h.getModelStatus() == HighsModelStatus::kOptimal); - } - - if (ci_test) { - h.clearSolver(); - h.setOptionValue("mip_race_read_solutions", false); - REQUIRE(h.run() == HighsStatus::kOk); - REQUIRE(h.getModelStatus() == HighsModelStatus::kOptimal); - } - h.resetGlobalScheduler(true); -} diff --git a/highs/Highs.h b/highs/Highs.h index 176af9d4bca..20035127272 100644 --- a/highs/Highs.h +++ b/highs/Highs.h @@ -1503,9 +1503,8 @@ class Highs { HighsStatus callSolveLp(HighsLp& lp, const string message); HighsStatus callSolveQp(); HighsStatus callSolveMip(); - HighsStatus callRunPostsolve( - const HighsSolution& solution, const HighsBasis& basis, - const bool suppress_mip_model_status_warning = false); + HighsStatus callRunPostsolve(const HighsSolution& solution, + const HighsBasis& basis); PresolveComponent presolve_; HighsPresolveStatus runPresolve(const bool force_lp_presolve, @@ -1715,11 +1714,6 @@ class Highs { bool optionsHasHighsFiles() const; void saveHighsFiles(); void getHighsFiles(); - HighsStatus mipRaceResults(bool use_mip_race_single_presolve, - HighsMipSolverInfo& mip_solver_info, - const std::vector& worker_info, - const std::vector& mip_time, - double mip_race_time); }; // Start of deprecated methods not in the Highs class diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index 06d8437ae83..d920177e3ea 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -4030,127 +4030,11 @@ HighsStatus Highs::callSolveMip() { use_lp = withoutSemiVariables(model_.lp_, solution_, options_.primal_feasibility_tolerance); } - // Start timing any MIP race before its presolve and parallel MIP - // solver calls. This timer is stopped in Highs::mipRaceResults - // after any postsolve is performed - double mip_race_time = -this->timer_.read(); - HighsLp presolved_lp; - const HighsInt mip_race_concurrency = this->options_.mip_race_concurrency; - const bool run_mip_race = mip_race_concurrency > 1; - // Determine whether to do a single presolve before any MIP race - // - // Doesn't work when there are semi-variables since they have to be - // converted into the use_lp instance, and this->presolve(); works - // on the incumbent model - const bool use_mip_race_single_presolve = - run_mip_race && options_.mip_race_single_presolve && - options_.presolve != kHighsOffString && !has_semi_variables; - // Take a copy of the presolve option in case it's switched off for - // a single presolve MIP race - const std::string presolve = this->options_.presolve; - if (use_mip_race_single_presolve) { - // Perform presolve before the MIP race - // - // NB Highs::presolve() is normally called externally, so calls - // returnFromHighs(). This will stop the run clock, and check that - // called_return_from_optimize_model is true - when it isn't. So, - // add a hack to set called_return_from_optimize_model true before - // the call to presolve... - assert(!this->called_return_from_optimize_model); - this->called_return_from_optimize_model = true; - HighsStatus call_status = this->presolve(); - // ... then set it back to false and restart the run clock - this->called_return_from_optimize_model = false; - this->timer_.start(); - if (call_status != HighsStatus::kOk) return call_status; - presolved_lp = this->getPresolvedLp(); - this->options_.presolve = kHighsOffString; - } - // Create the master MIP solver instance that will exist beyond any - // MIP race - HighsLp& lp = - has_semi_variables - ? use_lp - : (use_mip_race_single_presolve ? presolved_lp : model_.lp_); + HighsLp& lp = has_semi_variables ? use_lp : model_.lp_; HighsMipSolver solver(callback_, options_, lp, solution_); HighsMipSolverInfo mip_solver_info; - if (run_mip_race) { - // Set up the shared memory for the MIP solver race - MipRaceRecord mip_race_record; - mip_race_record.initialise(mip_race_concurrency, lp.num_col_); - // Set up the shared memory for the concurrent MIP terminator - auto terminator_record = - solver.initialiseTerminatorRecord(mip_race_concurrency); - // Don't allow callbacks for workers - HighsCallback worker_callback = callback_; - worker_callback.clear(); - // Race the MIP solver! - highsLogUser(options_.log_options, HighsLogType::kInfo, - "\nStarting MIP race with %d instances: behaviour is " - "non-deterministic!\n\n", - int(mip_race_concurrency)); - // Define the HighsMipSolverInfo record for each worker - std::vector worker_info(mip_race_concurrency); - // Set up the vector of options settings for workers - std::vector worker_options; - // std::vector worker; - std::vector mip_time(mip_race_concurrency); - for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) { - HighsOptions instance_options = options_; - // No workers log to console - instance_options.log_to_console = false; - instance_options.setLogOptions(); - // Use the instance ID as an offset to the random seed - instance_options.random_seed = options_.random_seed + instance; - std::string worker_log_file = - "mip_worker" + std::to_string(instance) + ".log"; - if (options_.output_flag) - highsOpenLogFile(instance_options, worker_log_file); - worker_options.push_back(instance_options); - } - highs::parallel::for_each( - 0, mip_race_concurrency, [&](HighsInt start, HighsInt end) { - for (HighsInt instance = start; instance < end; instance++) { - if (instance == 0) { - solver.initialiseTerminator(mip_race_concurrency, instance, - terminator_record.data()); - solver.initialiseMipRace(mip_race_concurrency, instance, - &mip_race_record); - mip_time[instance] = -timer_.read(); - solver.run(); - mip_time[instance] += timer_.read(); - mip_solver_info = getMipSolverInfo(solver); - } else { - HighsMipSolver worker(worker_callback, worker_options[instance], - lp, solution_); - worker.initialiseTerminator(mip_race_concurrency, instance, - terminator_record.data()); - worker.initialiseMipRace(mip_race_concurrency, instance, - &mip_race_record); - mip_time[instance] = -timer_.read(); - worker.run(); - mip_time[instance] += timer_.read(); - worker_info[instance] = getMipSolverInfo(worker); - } - } - }); - // Determine the winner and report on the solution - HighsStatus call_status = - this->mipRaceResults(use_mip_race_single_presolve, mip_solver_info, - worker_info, mip_time, mip_race_time); - // Restore the presolve option - that will have been set to - // kHighsOffString for a single presolve MIP race - this->options_.presolve = presolve; - - if (call_status == HighsStatus::kError) { - const bool undo_mods = true; - return returnFromOptimizeModel(HighsStatus::kError, undo_mods); - } - } else { - // Run a single MIP solver - solver.run(); - mip_solver_info = getMipSolverInfo(solver); - } + solver.run(); + mip_solver_info = getMipSolverInfo(solver); options_.log_dev_level = log_dev_level; // Set the return_status, model status and, for completeness, scaled // model status @@ -4160,14 +4044,7 @@ HighsStatus Highs::callSolveMip() { // Extract the solution if (mip_solver_info.solution_objective != kHighsInf) { // There is a primal solution - HighsInt solver_solution_size = mip_solver_info.solution.size(); - const bool solver_solution_size_ok = solver_solution_size >= lp.num_col_; - if (!solver_solution_size) - highsLogUser( - options_.log_options, HighsLogType::kError, - "After MIP race, size of solution is %d < %d = lp.num_col_\n", - int(solver_solution_size), int(lp.num_col_)); - assert(solver_solution_size >= lp.num_col_); + // // If the original model has semi-variables, its solution is // (still) given by the first model_.lp_.num_col_ entries of the // solution from the MIP solver @@ -4240,10 +4117,9 @@ HighsStatus Highs::callSolveMip() { return return_status; } -// Only called from Highs::postsolve and Highs::mipRaceResults -HighsStatus Highs::callRunPostsolve( - const HighsSolution& solution, const HighsBasis& basis, - const bool suppress_mip_model_status_warning) { +// Only called from Highs::postsolve +HighsStatus Highs::callRunPostsolve(const HighsSolution& solution, + const HighsBasis& basis) { HighsStatus return_status = HighsStatus::kOk; HighsStatus call_status; const HighsLp& presolved_lp = presolve_.getReducedProblem(); @@ -4308,12 +4184,9 @@ HighsStatus Highs::callRunPostsolve( max_integrality_violation); } } - // When calling postsolve after MIP race on presolved model, - // model status can be trusted so suppress the warning message - if (!suppress_mip_model_status_warning) - highsLogUser( - options_.log_options, HighsLogType::kWarning, - "Postsolve performed for MIP, but model status cannot be known\n"); + highsLogUser( + options_.log_options, HighsLogType::kWarning, + "Postsolve performed for MIP, but model status cannot be known\n"); } else { highsLogUser(options_.log_options, HighsLogType::kError, "Postsolve return status is %d\n", (int)postsolve_status); diff --git a/highs/lp_data/HighsInterface.cpp b/highs/lp_data/HighsInterface.cpp index 055dbe27334..07e0496a416 100644 --- a/highs/lp_data/HighsInterface.cpp +++ b/highs/lp_data/HighsInterface.cpp @@ -4260,143 +4260,3 @@ void HighsMipSolverInfo::clear() { this->total_lp_iterations = -kHighsSize_tInf; this->primal_dual_integral = -kHighsInf; } - -HighsStatus Highs::mipRaceResults( - bool use_mip_race_single_presolve, HighsMipSolverInfo& mip_solver_info, - const std::vector& worker_info, - const std::vector& mip_time, double mip_race_time) { - const HighsInt mip_race_concurrency = this->options_.mip_race_concurrency; - HighsInt winning_instance = -1; - HighsModelStatus winning_model_status = HighsModelStatus::kNotset; - highsLogUser(options_.log_options, HighsLogType::kInfo, - "\nMIP race results\n"); - for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) { - const HighsMipSolverInfo& solver_info = - instance == 0 ? mip_solver_info : worker_info[instance]; - HighsModelStatus instance_model_status = solver_info.modelstatus; - highsLogUser(options_.log_options, HighsLogType::kInfo, - " Solver %2d has best objective %15.8g, gap %6.2f%% (time " - "= %6.2f), and status %s\n", - int(instance), solver_info.solution_objective, - 1e2 * solver_info.gap, mip_time[instance], - modelStatusToString(instance_model_status).c_str()); - if (instance_model_status != HighsModelStatus::kHighsInterrupt) { - // Definitive status for this instance, so check compatibility - // with any current winning model status - if (winning_model_status != HighsModelStatus::kNotset) { - if (winning_model_status != instance_model_status) { - highsLogUser(options_.log_options, HighsLogType::kError, - "MIP race: conflict between status \"%s\" for " - "instance %d and status \"%s\" for instance %d\n", - modelStatusToString(winning_model_status).c_str(), - int(winning_instance), - modelStatusToString(instance_model_status).c_str(), - int(instance)); - return HighsStatus::kError; - } - } else { - winning_model_status = instance_model_status; - winning_instance = instance; - } - } - } - if (winning_instance > 0) mip_solver_info = worker_info[winning_instance]; - - const bool havesolution = mip_solver_info.solution_objective != kHighsInf; - - if (use_mip_race_single_presolve && havesolution) { - HighsSolution solution; - solution.col_value = mip_solver_info.solution; - // Perform postsolve after the MIP race - // - // Set up an empty basis so that callRunPostsolve can be used - HighsBasis basis; - // Need to suppress the warning about the HighsModelStatus for - // MIPs that (in general) can't be set after postsolve - const bool suppress_mip_model_status_warning = true; - HighsStatus call_status = this->callRunPostsolve( - solution, basis, suppress_mip_model_status_warning); - // call_status will be HighsStatus::kWarning, since model_status_ - // is typically HighsModelStatus::kUnknown due to the lack of - // optimality test for a MIP. However, since this postsolve was - // run for a solution that was optimal for the presolved problem - // (and that status is in mip_solver_info) the warning can be - // ignored. - if (call_status == HighsStatus::kError) return call_status; - // Now update the MipSolverInfo with the postsolved solution - mip_solver_info.solution = this->solution_.col_value; - } - const HighsInt mip_solver_info_solution_size = - mip_solver_info.solution.size(); - if (0 < mip_solver_info_solution_size && - mip_solver_info_solution_size < this->model_.lp_.num_col_) { - printf( - "Highs::mipRaceResults MipSolverInfo solution size = %d < %d = " - "num_col\n", - int(mip_solver_info_solution_size), int(this->model_.lp_.num_col_)); - assert(11 == 33); - } - - mip_race_time += this->timer_.read(); - - std::array gapString = getGapString( - mip_solver_info.gap, mip_solver_info.primal_bound, &options_); - - bool feasible; - std::string solutionstatus = "-"; - if (havesolution) { - feasible = - mip_solver_info.bound_violation <= options_.mip_feasibility_tolerance && - mip_solver_info.integrality_violation <= - options_.mip_feasibility_tolerance && - mip_solver_info.row_violation <= options_.mip_feasibility_tolerance; - } else { - feasible = false; - } - solutionstatus = feasible ? "feasible" : "infeasible"; - - highsLogUser(options_.log_options, HighsLogType::kInfo, "Solving report\n"); - highsLogUser(options_.log_options, HighsLogType::kInfo, - " Model %s\n", - this->model_.lp_.model_name_.c_str()); - highsLogUser(options_.log_options, HighsLogType::kInfo, - " Status %s\n", - modelStatusToString(mip_solver_info.modelstatus).c_str()); - highsLogUser(options_.log_options, HighsLogType::kInfo, - " Primal bound %.12g\n", mip_solver_info.primal_bound); - highsLogUser(options_.log_options, HighsLogType::kInfo, - " Dual bound %.12g\n", mip_solver_info.dual_bound); - highsLogUser(options_.log_options, HighsLogType::kInfo, - " Gap %s\n", gapString.data()); - highsLogUser(options_.log_options, HighsLogType::kInfo, - " P-D integral %.12g\n", - mip_solver_info.primal_dual_integral); - highsLogUser(options_.log_options, HighsLogType::kInfo, - " Solution status %s\n", solutionstatus.c_str()); - highsLogUser(options_.log_options, HighsLogType::kInfo, - " %.12g\n", - mip_solver_info.solution_objective); - highsLogUser(options_.log_options, HighsLogType::kInfo, - " %.12g (bound viol.)\n", - mip_solver_info.bound_violation); - highsLogUser(options_.log_options, HighsLogType::kInfo, - " %.12g (int. viol.)\n", - mip_solver_info.integrality_violation); - highsLogUser(options_.log_options, HighsLogType::kInfo, - " %.12g (row viol.)\n", - mip_solver_info.row_violation); - // Report the solution time for the whole concurrent loop, as that's - // "real" time - highsLogUser(options_.log_options, HighsLogType::kInfo, - " Timing %.2f\n", mip_race_time); - highsLogUser(options_.log_options, HighsLogType::kInfo, - " Max sub-MIP depth %d\n", - int(mip_solver_info.max_submip_level)); - highsLogUser(options_.log_options, HighsLogType::kInfo, - " Nodes %llu\n", - (long long unsigned)(mip_solver_info.node_count)); - highsLogUser(options_.log_options, HighsLogType::kInfo, - " LP iterations %llu\n", - (long long unsigned)(mip_solver_info.total_lp_iterations)); - return HighsStatus::kOk; -} diff --git a/highs/lp_data/HighsOptions.h b/highs/lp_data/HighsOptions.h index 187bf1600e9..e4d228b6edd 100644 --- a/highs/lp_data/HighsOptions.h +++ b/highs/lp_data/HighsOptions.h @@ -424,9 +424,6 @@ struct HighsOptionsStruct { // Options for MIP solver bool mip_detect_symmetry; bool mip_allow_restart; - HighsInt mip_race_concurrency; - bool mip_race_read_solutions; - bool mip_race_single_presolve; HighsInt mip_max_nodes; HighsInt mip_max_stall_nodes; HighsInt mip_max_start_nodes; @@ -578,9 +575,6 @@ struct HighsOptionsStruct { icrash_breakpoints(false), mip_detect_symmetry(false), mip_allow_restart(false), - mip_race_concurrency(0), - mip_race_read_solutions(false), - mip_race_single_presolve(false), mip_max_nodes(0), mip_max_stall_nodes(0), mip_max_start_nodes(0), @@ -1023,23 +1017,6 @@ class HighsOptions : public HighsOptionsStruct { advanced, &mip_allow_restart, true); records.push_back(record_bool); - record_int = new OptionRecordInt( - "mip_race_concurrency", "Concurrency for non-deterministic MIP race", - advanced, &mip_race_concurrency, 0, 0, kHighsIInf); - records.push_back(record_int); - - record_bool = new OptionRecordBool( - "mip_race_read_solutions", - "Whether the MIP races should read other racers' solutions", advanced, - &mip_race_read_solutions, true); - records.push_back(record_bool); - - record_bool = new OptionRecordBool( - "mip_race_single_presolve", - "Whether the MIP races should follow a single presolve", advanced, - &mip_race_single_presolve, true); - records.push_back(record_bool); - record_int = new OptionRecordInt("mip_max_nodes", "MIP solver max number of nodes", advanced, &mip_max_nodes, 0, kHighsIInf, kHighsIInf); diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index 55b6f98fb11..3c2ebd50245 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -44,11 +44,8 @@ HighsMipSolver::HighsMipSolver(HighsCallback& callback, implicinit(nullptr) { assert(!submip || submip_level > 0); max_submip_level = 0; - // Initialise empty terminator, since this sets termination_status_ - // to HighsModelStatus::kNotset... + // Initialise empty terminator initialiseTerminator(); - // ... and empty MIP race - initialiseMipRace(); assert(termination_status_ == HighsModelStatus::kNotset); if (solution.value_valid) { #ifndef NDEBUG @@ -1031,11 +1028,3 @@ void HighsMipSolver::initialiseTerminator(const HighsMipSolver& mip_solver) { mip_solver.mipdata_->terminatorMyInstance(), mip_solver.terminator_.record); } - -void HighsMipSolver::initialiseMipRace(const HighsInt mip_race_concurrency, - const HighsInt my_instance, - MipRaceRecord* record) { - this->mip_race_.clear(); - this->mip_race_.initialise(mip_race_concurrency, my_instance, record, - this->options_mip_->log_options); -} diff --git a/highs/mip/HighsMipSolver.h b/highs/mip/HighsMipSolver.h index bc15d41345a..eb2fca54d42 100644 --- a/highs/mip/HighsMipSolver.h +++ b/highs/mip/HighsMipSolver.h @@ -21,76 +21,6 @@ struct HighsPseudocostInitialization; class HighsCliqueTable; class HighsImplications; -const HighsInt kMipRaceNoSolution = -1; - -/* -struct MipRaceIncumbent { - HighsInt start_write_incumbent = kMipRaceNoSolution; - HighsInt finish_write_incumbent = kMipRaceNoSolution; - double objective = -kHighsInf; - std::vector solution; - void clear(); - void initialise(const HighsInt num_col); - void update(const double objective, const std::vector& solution); - HighsInt read(const HighsInt last_incumbent_read, double& objective_, - std::vector& solution_) const; -}; -*/ - -struct MipRaceIncumbent { - std::atomic start_write_incumbent{kMipRaceNoSolution}; - std::atomic finish_write_incumbent{kMipRaceNoSolution}; - double objective = -kHighsInf; - std::vector solution; - void clear(); - void initialise(const HighsInt num_col); - void update(const double objective, const std::vector& solution); - HighsInt read(const HighsInt last_incumbent_read, double& objective_, - std::vector& solution_) const; - - MipRaceIncumbent() = default; - - MipRaceIncumbent(const MipRaceIncumbent& copy) { - start_write_incumbent = copy.start_write_incumbent.load(); - finish_write_incumbent = copy.finish_write_incumbent.load(); - objective = copy.objective; - solution = copy.solution; - } - - MipRaceIncumbent(MipRaceIncumbent&& moving) { - start_write_incumbent = moving.start_write_incumbent.load(); - finish_write_incumbent = moving.finish_write_incumbent.load(); - objective = moving.objective; - solution = std::move(moving.solution); - } -}; - -struct MipRaceRecord { - std::vector incumbent; - void clear(); - void initialise(const HighsInt mip_race_concurrency, const HighsInt num_col); - HighsInt concurrency() const; - void update(const HighsInt instance, const double objective, - const std::vector& solution); - void report(const HighsLogOptions log_options) const; -}; - -struct MipRace { - HighsInt my_instance; - MipRaceRecord* record = nullptr; - HighsLogOptions log_options; - std::vector last_incumbent_read; - void clear(); - void initialise(const HighsInt mip_race_concurrency, - const HighsInt my_instance_, MipRaceRecord* record_, - const HighsLogOptions log_options_); - HighsInt concurrency() const; - void update(const double objective, const std::vector& solution); - bool newSolution(const HighsInt instance, double& objective, - std::vector& solution); - void report() const; -}; - struct HighsTerminator { HighsInt num_instance; HighsInt my_instance; @@ -141,8 +71,6 @@ class HighsMipSolver { HighsMipAnalysis analysis_; - MipRace mip_race_; - HighsModelStatus termination_status_; HighsTerminator terminator_; @@ -199,10 +127,6 @@ class HighsMipSolver { double& bound_violation, double& row_violation, double& integrality_violation, HighsCDouble& obj) const; - void initialiseMipRace(const HighsInt mip_race_concurrency = 0, - const HighsInt my_instance_ = kNoThreadInstance, - MipRaceRecord* record_ = nullptr); - std::vector initialiseTerminatorRecord( HighsInt num_instance) const; void initialiseTerminator(HighsInt num_instance_ = 0, diff --git a/highs/mip/HighsMipSolverData.cpp b/highs/mip/HighsMipSolverData.cpp index 82f2433411f..c5c206304b9 100644 --- a/highs/mip/HighsMipSolverData.cpp +++ b/highs/mip/HighsMipSolverData.cpp @@ -770,9 +770,6 @@ void HighsMipSolverData::runSetup() { double new_upper_limit = computeNewUpperLimit(solobj, 0.0, 0.0); - // Possibly write the improving solution to the shared memory space - if (!mipsolver.submip) this->mipRaceUpdate(); - saveReportMipSolution(new_upper_limit); if (new_upper_limit < upper_limit) { upper_limit = new_upper_limit; @@ -2567,9 +2564,6 @@ void HighsMipSolverData::saveReportMipSolution(const double new_upper_limit) { if (mipsolver.submip) return; if (non_improving) return; - // Possibly write the improving solution to the shared memory space - this->mipRaceUpdate(); - if (mipsolver.callback_->user_callback) { if (mipsolver.callback_->active[kCallbackMipImprovingSolution]) { mipsolver.callback_->clearHighsCallbackOutput(); @@ -2658,6 +2652,23 @@ void HighsMipSolverData::queryExternalSolution( callback->callbackAction(kCallbackMipUserSolution, "MIP User solution"); assert(!interrupt); if (callback->data_in.user_has_solution) { + // Objective is assumed to be original_offset + + // (original_c)^T(original_x), but MIP solver bounds are based on the + // reduced objective (reduced_c)^T(reduced_x) + // + // Now, original_sense*[reduced_offset + (reduced_c)^T(reduced_x)] is an + // objective in the original space, so + // + // f0 + c0^Tx0 = s*(f1 + c1^Tx1) + // + // where 0 => original; 1 => reduced + // + // This allows the reduced objective value to be deduced as + // + // c1^Tx1 = s*(f0 + c0^Tx0) - f1 + // + // (reduced_c)^T(reduced_x) = original_sense*[original_offset + + // (original_c)^T(original_x) - reduced_offset] const auto& user_solution = callback->data_in.user_solution; double bound_violation_ = 0; double row_violation_ = 0; @@ -2688,90 +2699,6 @@ void HighsMipSolverData::queryExternalSolution( is_user_solution); } } - if (!mipsolver.options_mip_->mip_race_read_solutions) return; - MipRace& mip_race = mipsolver.mip_race_; - if (!mip_race.record) return; - double instance_objective_value = kHighsInf; - std::vector instance_solution; - for (HighsInt instance = 0; instance < mipRaceConcurrency(); instance++) { - if (instance == mip_race.my_instance) continue; - if (!mip_race.newSolution(instance, instance_objective_value, - instance_solution)) - continue; - // Have read a new incumbent - // - // Objective is assumed to be original_offset + (original_c)^T(original_x), - // but MIP solver bounds are based on the reduced objective - // (reduced_c)^T(reduced_x) - // - // Now, original_sense*[reduced_offset + (reduced_c)^T(reduced_x)] is an - // objective in the original space, so - // - // f0 + c0^Tx0 = s*(f1 + c1^Tx1) - // - // where 0 => original; 1 => reduced - // - // This allows the reduced objective value to be deduced as - // - // c1^Tx1 = s*(f0 + c0^Tx0) - f1 - // - // (reduced_c)^T(reduced_x) = original_sense*[original_offset + - // (original_c)^T(original_x) - reduced_offset] - // - // However, this isn't right when one solver has performed - // restart, and another hasn't. So, ignore the objective value - // that's passed, and compute it anew - - // Get the solution in the reduced space - std::vector reduced_instance_solution = - postSolveStack.getReducedPrimalSolution(instance_solution); - - // Reduced solution can be infeasible if restart has been - // performed - if (!checkSolution(reduced_instance_solution)) { - const bool feasibility_warning = true; - if (feasibility_warning) { - highsLogUser( - mipsolver.options_mip_->log_options, HighsLogType::kWarning, - "Solution from instance %2d is not feasible for instance %2d\n", - int(instance), int(mip_race.my_instance)); - } - continue; - } - HighsCDouble reduced_instance_quad_objective_value = 0; - for (HighsInt iCol = 0; iCol < mipsolver.model_->num_col_; iCol++) - reduced_instance_quad_objective_value += - mipsolver.colCost(iCol) * reduced_instance_solution[iCol]; - double reduced_instance_objective_value = - double(reduced_instance_quad_objective_value); - addIncumbent(reduced_instance_solution, reduced_instance_objective_value, - kSolutionSourceHighsSolution); - } -} - -HighsInt HighsMipSolverData::mipRaceConcurrency() const { - assert(!mipsolver.submip); - if (!mipsolver.mip_race_.record) return 0; - return mipsolver.mip_race_.concurrency(); -} - -void HighsMipSolverData::mipRaceUpdate() { - if (!mipsolver.mip_race_.record) return; - assert(!mipsolver.submip); - mipsolver.mip_race_.update(mipsolver.solution_objective_, - mipsolver.solution_); -} - -HighsInt HighsMipSolverData::mipRaceNewSolution(const HighsInt instance, - double& objective_value, - std::vector& solution) { - assert(!mipsolver.submip); - if (!mipsolver.mip_race_.record) return kMipRaceNoSolution; - return mipsolver.mip_race_.newSolution(instance, objective_value, solution); -} - -void HighsMipSolverData::mipRaceReport() const { - if (mipsolver.mip_race_.record) mipsolver.mip_race_.report(); } HighsInt HighsMipSolverData::terminatorConcurrency() const { @@ -2934,152 +2861,6 @@ void HighsMipSolverData::updatePrimalDualIntegral(const double from_lower_bound, void HighsPrimaDualIntegral::initialise() { this->value = -kHighsInf; } -void MipRaceIncumbent::clear() { - this->start_write_incumbent = kMipRaceNoSolution; - this->finish_write_incumbent = kMipRaceNoSolution; - this->objective = -kHighsInf; - this->solution.clear(); -} - -void MipRaceIncumbent::initialise(const HighsInt num_col) { - this->clear(); - this->solution.resize(num_col); -} - -void MipRaceIncumbent::update(const double objective_, - const std::vector& solution_) { - assert(this->solution.size() == solution_.size()); - this->start_write_incumbent++; - this->objective = objective_; - this->solution = solution_; - this->finish_write_incumbent++; - assert(this->start_write_incumbent == this->finish_write_incumbent); -} - -HighsInt MipRaceIncumbent::read(const HighsInt last_incumbent_read, - double& objective_, - std::vector& solution_) const { - const HighsInt start_write_incumbent = this->start_write_incumbent; - assert(this->finish_write_incumbent <= start_write_incumbent); - if (start_write_incumbent < last_incumbent_read) return kMipRaceNoSolution; - // If a write call has not completed, return failure - if (this->finish_write_incumbent < start_write_incumbent) - return kMipRaceNoSolution; - // finish_write_incumbent = start_write_incumbent so start reading - objective_ = this->objective; - solution_ = this->solution; - // Read is OK if no new write has started - return this->start_write_incumbent == start_write_incumbent - ? start_write_incumbent - : kMipRaceNoSolution; -} - -void MipRaceRecord::clear() { this->incumbent.clear(); } - -void MipRaceRecord::initialise(const HighsInt mip_race_concurrency, - const HighsInt num_col) { - this->clear(); - this->incumbent.resize(mip_race_concurrency); - - for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) - this->incumbent[instance].initialise(num_col); - - /* - MipRaceIncumbent incumbent_; - incumbent_.initialise(num_col); - // Loop from 1... - for (HighsInt instance = 1; instance < mip_race_concurrency; instance++) - this->incumbent.push_back(incumbent_); - // ... and move incumbent_ to complete the vector of incumbents - this->incumbent.push_back(std::move(incumbent_)); - */ -} - -HighsInt MipRaceRecord::concurrency() const { - return static_cast(this->incumbent.size()); -} - -void MipRaceRecord::update(const HighsInt instance, const double objective, - const std::vector& solution) { - this->incumbent[instance].update(objective, solution); -} - -void MipRaceRecord::report(const HighsLogOptions log_options) const { - HighsInt mip_race_concurrency = this->concurrency(); - highsLogUser(log_options, HighsLogType::kInfo, "\nMipRaceRecord: "); - for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) - highsLogUser(log_options, HighsLogType::kInfo, " %20d", int(instance)); - highsLogUser(log_options, HighsLogType::kInfo, "\nStartWrite: "); - for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) - highsLogUser(log_options, HighsLogType::kInfo, " %20d", - int(this->incumbent[instance].start_write_incumbent)); - highsLogUser(log_options, HighsLogType::kInfo, "\nObjective: "); - for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) - highsLogUser(log_options, HighsLogType::kInfo, " %20.12g", - this->incumbent[instance].objective); - highsLogUser(log_options, HighsLogType::kInfo, "\nFinishWrite: "); - for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) - highsLogUser(log_options, HighsLogType::kInfo, " %20d", - int(this->incumbent[instance].finish_write_incumbent)); - highsLogUser(log_options, HighsLogType::kInfo, "\n"); -} - -void MipRace::clear() { - this->my_instance = -1; - this->record = nullptr; - this->last_incumbent_read.clear(); -} - -void MipRace::initialise(const HighsInt mip_race_concurrency, - const HighsInt my_instance_, MipRaceRecord* record_, - const HighsLogOptions log_options_) { - this->clear(); - this->my_instance = my_instance_; - this->record = record_; - this->log_options = log_options_; - if (mip_race_concurrency > 0) - this->last_incumbent_read.assign(mip_race_concurrency, kMipRaceNoSolution); -} - -HighsInt MipRace::concurrency() const { - assert(this->record); - return this->record->concurrency(); -} - -void MipRace::update(const double objective, - const std::vector& solution) { - assert(this->record); - this->record->update(this->my_instance, objective, solution); - // this->report(); -} - -bool MipRace::newSolution(const HighsInt instance, double& objective, - std::vector& solution) { - assert(this->record); - HighsInt new_incumbent_read = this->record->incumbent[instance].read( - this->last_incumbent_read[instance], objective, solution); - if (new_incumbent_read != kMipRaceNoSolution) { - this->last_incumbent_read[instance] = new_incumbent_read; - return true; - } - return false; -} - -void MipRace::report() const { - assert(this->record); - this->record->report(this->log_options); - highsLogUser(this->log_options, HighsLogType::kInfo, "LastIncumbentRead: "); - for (HighsInt instance = 0; instance < this->concurrency(); instance++) { - if (instance == this->my_instance) { - highsLogUser(this->log_options, HighsLogType::kInfo, " %20s", ""); - } else { - highsLogUser(this->log_options, HighsLogType::kInfo, " %20d", - this->last_incumbent_read[instance]); - } - } - highsLogUser(this->log_options, HighsLogType::kInfo, "\n\n"); -} - void HighsTerminator::clear() { this->num_instance = 0; this->my_instance = kNoThreadInstance; From 6b4a6ee5dd657b7cc40d5f0929091ebe660e6b0c Mon Sep 17 00:00:00 2001 From: JAJHall Date: Tue, 26 Aug 2025 09:49:56 +0100 Subject: [PATCH 58/58] Removed MipSolverInfo and last vestiges of MIP race --- highs/lp_data/HStruct.h | 18 ----------- highs/lp_data/Highs.cpp | 51 +++++++++----------------------- highs/lp_data/HighsInterface.cpp | 16 ---------- highs/mip/HighsMipSolver.cpp | 25 ++++++---------- highs/mip/HighsMipSolver.h | 2 -- highs/mip/HighsMipSolverData.h | 6 ---- 6 files changed, 23 insertions(+), 95 deletions(-) diff --git a/highs/lp_data/HStruct.h b/highs/lp_data/HStruct.h index 65d45edd5d4..6c484face1c 100644 --- a/highs/lp_data/HStruct.h +++ b/highs/lp_data/HStruct.h @@ -183,22 +183,4 @@ struct HighsSimplexStats { void initialise(const HighsInt iteration_count_ = 0); }; -struct HighsMipSolverInfo { - // Data pulled from the MIP solver that are required after instance - // is deleted - HighsModelStatus modelstatus; - std::vector solution; - double solution_objective; - double bound_violation; - double integrality_violation; - double row_violation; - double dual_bound; - double primal_bound; - double gap; - HighsInt max_submip_level; - int64_t node_count; - int64_t total_lp_iterations; - double primal_dual_integral; - void clear(); -}; #endif /* LP_DATA_HSTRUCT_H_ */ diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index f5033056892..fff0e0d819e 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -933,8 +933,6 @@ HighsStatus Highs::presolve() { return returnFromHighs(return_status); } -HighsMipSolverInfo getMipSolverInfo(const HighsMipSolver& solver); - HighsStatus Highs::run() { const bool options_had_highs_files = this->optionsHasHighsFiles(); if (options_had_highs_files) { @@ -4025,24 +4023,22 @@ HighsStatus Highs::callSolveMip() { } HighsLp& lp = has_semi_variables ? use_lp : model_.lp_; HighsMipSolver solver(callback_, options_, lp, solution_); - HighsMipSolverInfo mip_solver_info; solver.run(); - mip_solver_info = getMipSolverInfo(solver); options_.log_dev_level = log_dev_level; // Set the return_status, model status and, for completeness, scaled // model status HighsStatus return_status = - highsStatusFromHighsModelStatus(mip_solver_info.modelstatus); - model_status_ = mip_solver_info.modelstatus; + highsStatusFromHighsModelStatus(solver.modelstatus_); + model_status_ = solver.modelstatus_; // Extract the solution - if (mip_solver_info.solution_objective != kHighsInf) { + if (solver.solution_objective_ != kHighsInf) { // There is a primal solution // // If the original model has semi-variables, its solution is // (still) given by the first model_.lp_.num_col_ entries of the // solution from the MIP solver solution_.col_value.resize(model_.lp_.num_col_); - solution_.col_value = mip_solver_info.solution; + solution_.col_value = solver.solution_; this->saved_objective_and_solution_ = solver.saved_objective_and_solution_; model_.lp_.a_matrix_.productQuad(solution_.row_value, solution_.col_value); solution_.value_valid = true; @@ -4062,7 +4058,7 @@ HighsStatus Highs::callSolveMip() { // There is no basis: should be so by default assert(!basis_.valid); // Get the objective and any KKT failures - info_.objective_function_value = mip_solver_info.solution_objective; + info_.objective_function_value = solver.solution_objective_; // Remember to judge primal feasibility according to // mip_feasibility_tolerance, so take a copy of the original // value... @@ -4071,13 +4067,13 @@ HighsStatus Highs::callSolveMip() { // NB getKktFailures sets the primal and dual solution status getKktFailures(options_, model_, solution_, basis_, info_); // Set the MIP-specific values of info_ - info_.mip_node_count = mip_solver_info.node_count; - info_.mip_dual_bound = mip_solver_info.dual_bound; - info_.mip_gap = mip_solver_info.gap; - info_.primal_dual_integral = mip_solver_info.primal_dual_integral; + info_.mip_node_count = solver.node_count_; + info_.mip_dual_bound = solver.dual_bound_; + info_.mip_gap = solver.gap_; + info_.primal_dual_integral = solver.primal_dual_integral_; // Get the number of LP iterations, avoiding overflow if the int64_t // value is too large - int64_t mip_total_lp_iterations = mip_solver_info.total_lp_iterations; + int64_t mip_total_lp_iterations = solver.total_lp_iterations_; info_.simplex_iteration_count = mip_total_lp_iterations > kHighsIInf ? -1 : HighsInt(mip_total_lp_iterations); @@ -4085,9 +4081,9 @@ HighsStatus Highs::callSolveMip() { if (model_status_ == HighsModelStatus::kOptimal) return_status = checkOptimality("MIP"); // Overwrite max infeasibility to include integrality if there is a solution - if (mip_solver_info.solution_objective != kHighsInf) { - const double mip_max_bound_violation = std::max( - mip_solver_info.row_violation, mip_solver_info.bound_violation); + if (solver.solution_objective_ != kHighsInf) { + const double mip_max_bound_violation = + std::max(solver.row_violation_, solver.bound_violation_); const double delta_max_bound_violation = std::abs(mip_max_bound_violation - info_.max_primal_infeasibility); // Possibly report a mis-match between the max bound violation @@ -4099,7 +4095,7 @@ HighsStatus Highs::callSolveMip() { "(%10.4g); Difference of %10.4g\n", mip_max_bound_violation, info_.max_primal_infeasibility, delta_max_bound_violation); - info_.max_integrality_violation = mip_solver_info.integrality_violation; + info_.max_integrality_violation = solver.integrality_violation_; if (info_.max_integrality_violation > options_.mip_feasibility_tolerance) { info_.primal_solution_status = kSolutionStatusInfeasible; assert(model_status_ == HighsModelStatus::kInfeasible); @@ -4809,22 +4805,3 @@ void Highs::getHighsFiles() { this->options_.write_basis_file = this->files_.write_basis_file; this->files_.clear(); } - -HighsMipSolverInfo getMipSolverInfo(const HighsMipSolver& mip_solver) { - HighsMipSolverInfo mip_solver_info; - mip_solver_info.clear(); - mip_solver_info.modelstatus = mip_solver.modelstatus_; - mip_solver_info.solution = mip_solver.solution_; - mip_solver_info.solution_objective = mip_solver.solution_objective_; - mip_solver_info.bound_violation = mip_solver.bound_violation_; - mip_solver_info.integrality_violation = mip_solver.integrality_violation_; - mip_solver_info.row_violation = mip_solver.row_violation_; - mip_solver_info.dual_bound = mip_solver.dual_bound_; - mip_solver_info.primal_bound = mip_solver.primal_bound_; - mip_solver_info.gap = mip_solver.gap_; - mip_solver_info.max_submip_level = mip_solver.max_submip_level; - mip_solver_info.node_count = mip_solver.node_count_; - mip_solver_info.total_lp_iterations = mip_solver.total_lp_iterations_; - mip_solver_info.primal_dual_integral = mip_solver.primal_dual_integral_; - return mip_solver_info; -} diff --git a/highs/lp_data/HighsInterface.cpp b/highs/lp_data/HighsInterface.cpp index 320b377f6c5..5383305518c 100644 --- a/highs/lp_data/HighsInterface.cpp +++ b/highs/lp_data/HighsInterface.cpp @@ -4300,19 +4300,3 @@ void HighsLinearObjective::clear() { this->rel_tolerance = 0.0; this->priority = 0; } - -void HighsMipSolverInfo::clear() { - this->modelstatus = HighsModelStatus::kNotset; - this->solution.clear(); - this->solution_objective = -kHighsInf; - this->bound_violation = -kHighsInf; - this->integrality_violation = -kHighsInf; - this->row_violation = -kHighsInf; - this->dual_bound = -kHighsInf; - this->primal_bound = -kHighsInf; - this->gap = -kHighsInf; - this->max_submip_level = -1; - this->node_count = -kHighsSize_tInf; - this->total_lp_iterations = -kHighsSize_tInf; - this->primal_dual_integral = -kHighsInf; -} diff --git a/highs/mip/HighsMipSolver.cpp b/highs/mip/HighsMipSolver.cpp index 88b570e28a8..94f9a06b0a0 100644 --- a/highs/mip/HighsMipSolver.cpp +++ b/highs/mip/HighsMipSolver.cpp @@ -794,21 +794,14 @@ void HighsMipSolver::cleanupSolve() { std::array gapString = getGapString(gap_, primal_bound_, options_mip_); - // Don't log to console if this is in a MIP race - HighsOptions temp_options = *options_mip_; - if (mipdata_->terminatorActive()) { - temp_options.log_to_console = false; - temp_options.setLogOptions(); - } - - bool timeless_log = temp_options.timeless_log; - highsLogUser(temp_options.log_options, HighsLogType::kInfo, + bool timeless_log = options_mip_->timeless_log; + highsLogUser(options_mip_->log_options, HighsLogType::kInfo, "\nSolving report\n"); if (this->orig_model_->model_name_.length()) - highsLogUser(temp_options.log_options, HighsLogType::kInfo, + highsLogUser(options_mip_->log_options, HighsLogType::kInfo, " Model %s\n", this->orig_model_->model_name_.c_str()); - highsLogUser(temp_options.log_options, HighsLogType::kInfo, + highsLogUser(options_mip_->log_options, HighsLogType::kInfo, " Status %s\n" " Primal bound %.12g\n" " Dual bound %.12g\n" @@ -816,13 +809,13 @@ void HighsMipSolver::cleanupSolve() { utilModelStatusToString(modelstatus_).c_str(), primal_bound_, dual_bound_, gapString.data()); if (!timeless_log) - highsLogUser(temp_options.log_options, HighsLogType::kInfo, + highsLogUser(options_mip_->log_options, HighsLogType::kInfo, " P-D integral %.12g\n", mipdata_->primal_dual_integral.value); - highsLogUser(temp_options.log_options, HighsLogType::kInfo, + highsLogUser(options_mip_->log_options, HighsLogType::kInfo, " Solution status %s\n", solutionstatus.c_str()); if (solutionstatus != "-") - highsLogUser(temp_options.log_options, HighsLogType::kInfo, + highsLogUser(options_mip_->log_options, HighsLogType::kInfo, " %.12g (objective)\n" " %.12g (bound viol.)\n" " %.12g (int. viol.)\n" @@ -830,7 +823,7 @@ void HighsMipSolver::cleanupSolve() { solution_objective_, bound_violation_, integrality_violation_, row_violation_); if (!timeless_log) - highsLogUser(temp_options.log_options, HighsLogType::kInfo, + highsLogUser(options_mip_->log_options, HighsLogType::kInfo, " Timing %.2f (total)\n" " %.2f (presolve)\n" " %.2f (solve)\n" @@ -838,7 +831,7 @@ void HighsMipSolver::cleanupSolve() { timer_.read(), analysis_.mipTimerRead(kMipClockPresolve), analysis_.mipTimerRead(kMipClockSolve), analysis_.mipTimerRead(kMipClockPostsolve)); - highsLogUser(temp_options.log_options, HighsLogType::kInfo, + highsLogUser(options_mip_->log_options, HighsLogType::kInfo, " Max sub-MIP depth %d\n" " Nodes %llu\n" " Repair LPs %llu (%llu feasible; %llu iterations)\n" diff --git a/highs/mip/HighsMipSolver.h b/highs/mip/HighsMipSolver.h index eb2fca54d42..80c32eaaa35 100644 --- a/highs/mip/HighsMipSolver.h +++ b/highs/mip/HighsMipSolver.h @@ -8,8 +8,6 @@ #ifndef MIP_HIGHS_MIP_SOLVER_H_ #define MIP_HIGHS_MIP_SOLVER_H_ -#include - #include "Highs.h" #include "lp_data/HighsCallback.h" #include "lp_data/HighsOptions.h" diff --git a/highs/mip/HighsMipSolverData.h b/highs/mip/HighsMipSolverData.h index 6f16c00186a..2a1c918ba68 100644 --- a/highs/mip/HighsMipSolverData.h +++ b/highs/mip/HighsMipSolverData.h @@ -301,12 +301,6 @@ struct HighsMipSolverData { const double mipsolver_objective_value, const ExternalMipSolutionQueryOrigin external_solution_query_origin); - HighsInt mipRaceConcurrency() const; - void mipRaceUpdate(); - HighsInt mipRaceNewSolution(const HighsInt instance, double& objective_value, - std::vector& solution); - void mipRaceReport() const; - HighsInt terminatorConcurrency() const; bool terminatorActive() const { return terminatorConcurrency() > 0; } HighsInt terminatorMyInstance() const;