Skip to content

Commit ffe29d9

Browse files
committed
Prototype concurrent MIP solver
1 parent 54603c1 commit ffe29d9

File tree

9 files changed

+103
-86
lines changed

9 files changed

+103
-86
lines changed

check/TestMipSolver.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1003,14 +1003,15 @@ TEST_CASE("issue-2432", "[highs_test_mip_solver]") {
10031003
}
10041004

10051005
TEST_CASE("mip-race", "[highs_test_mip_solver]") {
1006-
const std::string model = "bell5";//"flugpl";
1006+
const std::string model =
1007+
"fiball"; //"neos-3381206-awhea"; //bell5";//"flugpl";
10071008
const std::string model_file =
1008-
std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps";
1009+
// std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps";
1010+
"/srv/miplib2017/" + model + ".mps.gz";
10091011
Highs h;
10101012
// h.setOptionValue("output_flag", dev_run);
1011-
h.setOptionValue("mip_race_concurrency", 2);
1013+
h.setOptionValue("mip_race_concurrency", 4);
10121014
// h.setOptionValue("mip_race_read_solutions", false);
10131015
REQUIRE(h.readModel(model_file) == HighsStatus::kOk);
10141016
REQUIRE(h.run() == HighsStatus::kOk);
10151017
}
1016-

highs/Highs.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1714,7 +1714,6 @@ class Highs {
17141714
bool optionsHasHighsFiles() const;
17151715
void saveHighsFiles();
17161716
void getHighsFiles();
1717-
17181717
};
17191718

17201719
// Start of deprecated methods not in the Highs class

highs/lp_data/Highs.cpp

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4045,12 +4045,15 @@ HighsStatus Highs::callSolveMip() {
40454045
worker_callback.clear();
40464046
// Race the MIP solver!
40474047
highsLogUser(options_.log_options, HighsLogType::kInfo,
4048-
"Starting MIP race with %d instances: performance is non-deterministic!\n", int(mip_race_concurrency));
4048+
"Starting MIP race with %d instances: performance is "
4049+
"non-deterministic!\n",
4050+
int(mip_race_concurrency));
40494051
// Define the HighsMipSolverInfo record for each worker
40504052
std::vector<HighsMipSolverInfo> worker_info(mip_race_concurrency);
40514053
// Set up the vector of options settings for workers
40524054
std::vector<HighsOptions> worker_options;
40534055
// std::vector<HighsMipSolver*> worker;
4056+
std::vector<double> mip_time(mip_race_concurrency);
40544057
for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) {
40554058
HighsOptions instance_options = options_;
40564059
// No workers log to console
@@ -4059,13 +4062,12 @@ HighsStatus Highs::callSolveMip() {
40594062
// Use the instance ID as an offset to the random seed
40604063
instance_options.random_seed = options_.random_seed + instance;
40614064
std::string worker_log_file =
4062-
"mip_worker" + std::to_string(instance) + ".log";
4065+
"mip_worker" + std::to_string(instance) + ".log";
40634066
highsOpenLogFile(instance_options, worker_log_file);
40644067
worker_options.push_back(instance_options);
40654068
/*
4066-
HighsMipSolver worker_instance(worker_callback, worker_options[instance], lp,
4067-
solution_);
4068-
worker.push_back(&worker_instance);
4069+
HighsMipSolver worker_instance(worker_callback, worker_options[instance],
4070+
lp, solution_); worker.push_back(&worker_instance);
40694071
*/
40704072
}
40714073
highs::parallel::for_each(
@@ -4075,49 +4077,58 @@ HighsStatus Highs::callSolveMip() {
40754077
solver.mip_race_.initialise(mip_race_concurrency, instance,
40764078
&mip_race_record,
40774079
options_.log_options);
4080+
mip_time[instance] = -timer_.read();
40784081
solver.run();
4079-
mip_solver_info = getMipSolverInfo(solver);
4082+
mip_time[instance] += timer_.read();
4083+
mip_solver_info = getMipSolverInfo(solver);
40804084
} else {
4081-
HighsMipSolver worker(worker_callback, worker_options[instance], lp,
4082-
solution_);
4085+
HighsMipSolver worker(worker_callback, worker_options[instance],
4086+
lp, solution_);
40834087
worker.mip_race_.initialise(mip_race_concurrency, instance,
40844088
&mip_race_record,
40854089
worker_options[instance].log_options);
4090+
mip_time[instance] = -timer_.read();
40864091
worker.run();
4087-
worker_info[instance] = getMipSolverInfo(worker);
4092+
mip_time[instance] += timer_.read();
4093+
worker_info[instance] = getMipSolverInfo(worker);
40884094
}
40894095
}
40904096
});
40914097
// Report on the solver and workers, and identify which has won!
40924098
HighsInt winning_instance = -1;
40934099
HighsModelStatus winning_model_status = HighsModelStatus::kNotset;
40944100
highsLogUser(options_.log_options, HighsLogType::kInfo,
4095-
"MIP race results:\n");
4101+
"MIP race results:\n");
40964102
for (HighsInt instance = 0; instance < mip_race_concurrency; instance++) {
4097-
const HighsMipSolverInfo& solver_info = instance == 0 ? mip_solver_info : worker_info[instance];
4103+
const HighsMipSolverInfo& solver_info =
4104+
instance == 0 ? mip_solver_info : worker_info[instance];
40984105
HighsModelStatus instance_model_status = solver_info.modelstatus;
40994106
highsLogUser(options_.log_options, HighsLogType::kInfo,
4100-
" Solver %d has best objective %15.8g, gap %6.2f\%, and status %s\n",
4101-
int(instance), solver_info.solution_objective, 1e2 * solver_info.gap,
4102-
modelStatusToString(instance_model_status).c_str());
4107+
" Solver %d has best objective %15.8g, gap %6.2f\% (time "
4108+
"= %6.2f), and status %s\n",
4109+
int(instance), solver_info.solution_objective,
4110+
1e2 * solver_info.gap, mip_time[instance],
4111+
modelStatusToString(instance_model_status).c_str());
41034112
if (instance_model_status != HighsModelStatus::kHighsInterrupt) {
4104-
// Definitive status for this instance, so check compatibility
4105-
// with any current winning model status
4106-
if (winning_model_status != HighsModelStatus::kNotset) {
4107-
if (winning_model_status != instance_model_status) {
4108-
highsLogUser(options_.log_options, HighsLogType::kError,
4109-
"MIP race: conflict between status \"%s\" for instance %d and status \"%s\" for instance %d\n",
4110-
modelStatusToString(winning_model_status).c_str(), int(winning_instance),
4111-
modelStatusToString(instance_model_status).c_str(), int(instance));
4112-
}
4113-
} else {
4114-
winning_model_status = instance_model_status;
4115-
winning_instance = instance;
4116-
}
4113+
// Definitive status for this instance, so check compatibility
4114+
// with any current winning model status
4115+
if (winning_model_status != HighsModelStatus::kNotset) {
4116+
if (winning_model_status != instance_model_status) {
4117+
highsLogUser(options_.log_options, HighsLogType::kError,
4118+
"MIP race: conflict between status \"%s\" for "
4119+
"instance %d and status \"%s\" for instance %d\n",
4120+
modelStatusToString(winning_model_status).c_str(),
4121+
int(winning_instance),
4122+
modelStatusToString(instance_model_status).c_str(),
4123+
int(instance));
4124+
}
4125+
} else {
4126+
winning_model_status = instance_model_status;
4127+
winning_instance = instance;
4128+
}
41174129
}
41184130
}
4119-
if (winning_instance > 0)
4120-
mip_solver_info = worker_info[winning_instance];
4131+
if (winning_instance > 0) mip_solver_info = worker_info[winning_instance];
41214132
} else {
41224133
// Run a single MIP solver
41234134
solver.run();
@@ -4135,8 +4146,10 @@ HighsStatus Highs::callSolveMip() {
41354146
HighsInt solver_solution_size = mip_solver_info.solution.size();
41364147
const bool solver_solution_size_ok = solver_solution_size >= lp.num_col_;
41374148
if (!solver_solution_size)
4138-
highsLogUser(options_.log_options, HighsLogType::kError,
4139-
"After MIP race, size of solution is %d < %d = lp.num_col_\n", int(solver_solution_size), int(lp.num_col_));
4149+
highsLogUser(
4150+
options_.log_options, HighsLogType::kError,
4151+
"After MIP race, size of solution is %d < %d = lp.num_col_\n",
4152+
int(solver_solution_size), int(lp.num_col_));
41404153
assert(solver_solution_size >= lp.num_col_);
41414154
// If the original model has semi-variables, its solution is
41424155
// (still) given by the first model_.lp_.num_col_ entries of the
@@ -4186,8 +4199,8 @@ HighsStatus Highs::callSolveMip() {
41864199
return_status = checkOptimality("MIP");
41874200
// Overwrite max infeasibility to include integrality if there is a solution
41884201
if (mip_solver_info.solution_objective != kHighsInf) {
4189-
const double mip_max_bound_violation =
4190-
std::max(mip_solver_info.row_violation, mip_solver_info.bound_violation);
4202+
const double mip_max_bound_violation = std::max(
4203+
mip_solver_info.row_violation, mip_solver_info.bound_violation);
41914204
const double delta_max_bound_violation =
41924205
std::abs(mip_max_bound_violation - info_.max_primal_infeasibility);
41934206
// Possibly report a mis-match between the max bound violation
@@ -4887,7 +4900,6 @@ void Highs::getHighsFiles() {
48874900
this->files_.clear();
48884901
}
48894902

4890-
48914903
HighsMipSolverInfo getMipSolverInfo(const HighsMipSolver& mip_solver) {
48924904
HighsMipSolverInfo mip_solver_info;
48934905
mip_solver_info.clear();

highs/lp_data/HighsInterface.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4244,7 +4244,6 @@ void HighsLinearObjective::clear() {
42444244
this->priority = 0;
42454245
}
42464246

4247-
42484247
void HighsMipSolverInfo::clear() {
42494248
this->modelstatus = HighsModelStatus::kNotset;
42504249
this->solution.clear();

highs/lp_data/HighsOptions.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -578,7 +578,7 @@ struct HighsOptionsStruct {
578578
mip_detect_symmetry(false),
579579
mip_allow_restart(false),
580580
mip_race_concurrency(0),
581-
mip_race_read_solutions(false),
581+
mip_race_read_solutions(false),
582582
mip_max_nodes(0),
583583
mip_max_stall_nodes(0),
584584
mip_max_start_nodes(0),
@@ -1026,9 +1026,10 @@ class HighsOptions : public HighsOptionsStruct {
10261026
advanced, &mip_race_concurrency, 0, 0, kHighsIInf);
10271027
records.push_back(record_int);
10281028

1029-
record_bool = new OptionRecordBool("mip_race_read_solutions",
1030-
"Whether the MIP races should read other racers' solutions",
1031-
advanced, &mip_race_read_solutions, true);
1029+
record_bool = new OptionRecordBool(
1030+
"mip_race_read_solutions",
1031+
"Whether the MIP races should read other racers' solutions", advanced,
1032+
&mip_race_read_solutions, true);
10321033
records.push_back(record_bool);
10331034

10341035
record_int = new OptionRecordInt("mip_max_nodes",

highs/mip/HighsMipSolver.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ HighsMipSolver::HighsMipSolver(HighsCallback& callback,
6767
HighsMipSolver::~HighsMipSolver() = default;
6868

6969
void HighsMipSolver::run() {
70-
if (!submip) printf("HighsMipSolver::run() with random_seed = %d\n", int(options_mip_->random_seed));
70+
if (!submip)
71+
printf("HighsMipSolver::run() with random_seed = %d\n",
72+
int(options_mip_->random_seed));
7173
modelstatus_ = HighsModelStatus::kNotset;
7274

7375
if (submip) {

highs/mip/HighsMipSolver.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ struct MipRaceIncumbent {
2929
void clear();
3030
void initialise(const HighsInt num_col);
3131
void update(const double objective, const std::vector<double>& solution);
32-
HighsInt read(const HighsInt last_incumbent_read,
33-
double& objective_, std::vector<double>& solution_) const;
32+
HighsInt read(const HighsInt last_incumbent_read, double& objective_,
33+
std::vector<double>& solution_) const;
3434
};
3535

3636
struct MipRaceRecord {
@@ -55,7 +55,8 @@ struct MipRace {
5555
const HighsLogOptions log_options_);
5656
HighsInt concurrency() const;
5757
void update(const double objective, const std::vector<double>& solution);
58-
bool newSolution(const HighsInt instance, double objective, std::vector<double>& solution);
58+
bool newSolution(const HighsInt instance, double objective,
59+
std::vector<double>& solution);
5960
void terminate();
6061
bool terminated() const;
6162
void report() const;

highs/mip/HighsMipSolverData.cpp

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2672,14 +2672,15 @@ void HighsMipSolverData::queryExternalSolution(
26722672
std::vector<double> instance_solution;
26732673
for (HighsInt instance = 0; instance < mip_race.concurrency(); instance++) {
26742674
if (instance == mip_race.my_instance) continue;
2675-
if (!mip_race.newSolution(instance, instance_solution_objective_value, instance_solution)) continue;
2675+
if (!mip_race.newSolution(instance, instance_solution_objective_value,
2676+
instance_solution))
2677+
continue;
26762678
// Have read a new incumbent
26772679
std::vector<double> reduced_instance_solution;
26782680
reduced_instance_solution =
2679-
postSolveStack.getReducedPrimalSolution(instance_solution);
2681+
postSolveStack.getReducedPrimalSolution(instance_solution);
26802682
addIncumbent(reduced_instance_solution, instance_solution_objective_value,
2681-
kSolutionSourceHighsSolution);
2682-
2683+
kSolutionSourceHighsSolution);
26832684
}
26842685
}
26852686

@@ -2697,8 +2698,8 @@ void HighsMipSolverData::mipRaceUpdate() {
26972698
}
26982699

26992700
HighsInt HighsMipSolverData::mipRaceNewSolution(const HighsInt instance,
2700-
double& objective_value,
2701-
std::vector<double>& solution) {
2701+
double& objective_value,
2702+
std::vector<double>& solution) {
27022703
assert(!mipsolver.submip);
27032704
if (!mipsolver.mip_race_.record) return kMipRaceNoSolution;
27042705
return mipsolver.mip_race_.newSolution(instance, objective_value, solution);
@@ -2880,18 +2881,21 @@ void MipRaceIncumbent::update(const double objective_,
28802881
}
28812882

28822883
HighsInt MipRaceIncumbent::read(const HighsInt last_incumbent_read,
2883-
double& objective_,
2884-
std::vector<double>& solution_) const {
2884+
double& objective_,
2885+
std::vector<double>& solution_) const {
28852886
const HighsInt start_write_incumbent = this->start_write_incumbent;
28862887
assert(this->finish_write_incumbent <= start_write_incumbent);
28872888
if (start_write_incumbent < last_incumbent_read) return kMipRaceNoSolution;
28882889
// If a write call has not completed, return failure
2889-
if (this->finish_write_incumbent < start_write_incumbent) return kMipRaceNoSolution;
2890+
if (this->finish_write_incumbent < start_write_incumbent)
2891+
return kMipRaceNoSolution;
28902892
// finish_write_incumbent = start_write_incumbent so start reading
28912893
objective_ = this->objective;
28922894
solution_ = this->solution;
28932895
// Read is OK if no new write has started
2894-
return this->start_write_incumbent == start_write_incumbent ? start_write_incumbent : kMipRaceNoSolution;
2896+
return this->start_write_incumbent == start_write_incumbent
2897+
? start_write_incumbent
2898+
: kMipRaceNoSolution;
28952899
}
28962900

28972901
void MipRaceRecord::clear() {
@@ -2974,14 +2978,13 @@ void MipRace::update(const double objective,
29742978
bool MipRace::newSolution(const HighsInt instance, double objective,
29752979
std::vector<double>& solution) {
29762980
assert(this->record);
2977-
HighsInt new_incumbent_read =
2978-
this->record->incumbent[instance].read(this->last_incumbent_read[instance],
2979-
objective, solution);
2981+
HighsInt new_incumbent_read = this->record->incumbent[instance].read(
2982+
this->last_incumbent_read[instance], objective, solution);
29802983
if (new_incumbent_read != kMipRaceNoSolution) {
29812984
this->last_incumbent_read[instance] = new_incumbent_read;
29822985
return true;
29832986
}
2984-
return false;
2987+
return false;
29852988
}
29862989

29872990
void MipRace::terminate() {
@@ -3001,11 +3004,11 @@ void MipRace::report() const {
30013004
this->record->report(this->log_options);
30023005
highsLogUser(this->log_options, HighsLogType::kInfo, "LastIncumbentRead: ");
30033006
for (HighsInt instance = 0; instance < this->concurrency(); instance++) {
3004-
if (instance == this->my_instance) {
3007+
if (instance == this->my_instance) {
30053008
highsLogUser(this->log_options, HighsLogType::kInfo, " %20s", "");
30063009
} else {
30073010
highsLogUser(this->log_options, HighsLogType::kInfo, " %20d",
3008-
this->last_incumbent_read[instance]);
3011+
this->last_incumbent_read[instance]);
30093012
}
30103013
}
30113014
highsLogUser(this->log_options, HighsLogType::kInfo, "\n\n");

highs/mip/HighsMipSolverData.h

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -43,25 +43,25 @@ enum MipSolutionSource : int {
4343
kSolutionSourceNone = -1,
4444
kSolutionSourceMin = kSolutionSourceNone,
4545
// kSolutionSourceInitial, // 0
46-
kSolutionSourceBranching, // B
47-
kSolutionSourceCentralRounding, // C
48-
kSolutionSourceFeasibilityPump, // F
49-
kSolutionSourceHeuristic, // H
50-
kSolutionSourceShifting, // I
51-
kSolutionSourceFeasibilityJump, // J
52-
kSolutionSourceSubMip, // L
53-
kSolutionSourceEmptyMip, // P
54-
kSolutionSourceRandomizedRounding, // R
55-
kSolutionSourceSolveLp, // S
56-
kSolutionSourceEvaluateNode, // T
57-
kSolutionSourceUnbounded, // U
58-
kSolutionSourceUserSolution, // X
59-
kSolutionSourceHighsSolution, // Y
60-
kSolutionSourceZiRound, // Z
61-
kSolutionSourceTrivialL, // l
62-
kSolutionSourceTrivialP, // p
63-
kSolutionSourceTrivialU, // u
64-
kSolutionSourceTrivialZ, // z
46+
kSolutionSourceBranching, // B
47+
kSolutionSourceCentralRounding, // C
48+
kSolutionSourceFeasibilityPump, // F
49+
kSolutionSourceHeuristic, // H
50+
kSolutionSourceShifting, // I
51+
kSolutionSourceFeasibilityJump, // J
52+
kSolutionSourceSubMip, // L
53+
kSolutionSourceEmptyMip, // P
54+
kSolutionSourceRandomizedRounding, // R
55+
kSolutionSourceSolveLp, // S
56+
kSolutionSourceEvaluateNode, // T
57+
kSolutionSourceUnbounded, // U
58+
kSolutionSourceUserSolution, // X
59+
kSolutionSourceHighsSolution, // Y
60+
kSolutionSourceZiRound, // Z
61+
kSolutionSourceTrivialL, // l
62+
kSolutionSourceTrivialP, // p
63+
kSolutionSourceTrivialU, // u
64+
kSolutionSourceTrivialZ, // z
6565
kSolutionSourceCleanup,
6666
kSolutionSourceCount
6767
};
@@ -303,9 +303,8 @@ struct HighsMipSolverData {
303303

304304
HighsInt mipRaceConcurrency() const;
305305
void mipRaceUpdate();
306-
HighsInt mipRaceNewSolution(const HighsInt instance,
307-
double& objective_value,
308-
std::vector<double>& solution);
306+
HighsInt mipRaceNewSolution(const HighsInt instance, double& objective_value,
307+
std::vector<double>& solution);
309308
void mipRaceTerminate();
310309
bool mipRaceTerminated() const;
311310
void mipRaceReport() const;

0 commit comments

Comments
 (0)