Skip to content

Commit

Permalink
Added user MIP solution callback, and tested
Browse files Browse the repository at this point in the history
  • Loading branch information
jajhall committed Feb 20, 2025
1 parent f0dd257 commit 9ac2d41
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 73 deletions.
59 changes: 32 additions & 27 deletions check/TestCallbacks.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// #include <algorithm>
#include <cstdio>
#include <cstring>

Expand All @@ -6,7 +7,7 @@
#include "catch.hpp"
#include "lp_data/HighsCallback.h"

const bool dev_run = true;
const bool dev_run = false; // true;//

const double egout_optimal_objective = 568.1007;
const double egout_objective_target = 610;
Expand Down Expand Up @@ -189,20 +190,23 @@ HighsCallbackFunctionType userkMipUserSolution =
[](int callback_type, const std::string& message,
const HighsCallbackDataOut* data_out, HighsCallbackDataIn* data_in,
void* user_callback_data) {

UserMipSolution callback_data =
*(static_cast<UserMipSolution*>(user_callback_data));
if (data_out->mip_primal_bound >
callback_data.optimal_objective_value) {

// If current objective value is not optimal, pass the
// optimal solution as a user solution
if (dev_run)
printf("userkMipUserSolution: %g = mip_primal_bound > "
"optimal_objective_value = %g\n",
data_out->mip_primal_bound,
callback_data.optimal_objective_value);
data_in->user_solution = callback_data.optimal_solution;
if (data_out->user_solution_callback_origin ==
callback_data.require_user_solution_callback_origin) {
if (data_out->mip_primal_bound >
callback_data.optimal_objective_value) {
// If current objective value is not optimal, pass the
// optimal solution as a user solution
if (dev_run)
printf(
"userkMipUserSolution: origin = %d; %g = mip_primal_bound > "
"optimal_objective_value = %g\n",
int(data_out->user_solution_callback_origin),
data_out->mip_primal_bound,
callback_data.optimal_objective_value);
data_in->user_solution = callback_data.optimal_solution;
}
}
};

Expand Down Expand Up @@ -408,33 +412,32 @@ TEST_CASE("highs-callback-mip-cut-pool", "[highs-callback]") {
}

TEST_CASE("highs-callback-mip-user-solution", "[highs-callback]") {
const std::vector<std::string> model = {"flugpl", "lseu", "egout", "gt2", "rgn", "bell5", "sp150x300d", "p0548", "dcmulti"};
// const std::string model = "flugpl";
// const std::string model = "lseu";
// const std::string model = "egout";
// const std::string model = "gt2";
// const std::string model = "rgn";
// const std::string model = "bell5";
// const std::string model = "sp150x300d";
// const std::string model = "p0548";
// const std::string model = "dcmulti";
// const std::vector<std::string> model = {"rgn", "flugpl", "gt2", "egout",
// "bell5", "lseu", "sp150x300d"};//, "p0548", "dcmulti"}; const
// std::vector<HighsInt> require_origin = {0, 1, 2, 3, 4, 5, 6};
const std::vector<std::string> model = {"p0548", "flugpl", "gt2", "egout",
"sp150x300d"};
const std::vector<HighsInt> require_origin = {0, 1, 2, 3, 4}; //, 4, 5, 6};
assert(model.size() == require_origin.size());
Highs highs;
highs.setOptionValue("output_flag", dev_run);
highs.setOptionValue("mip_rel_gap", 0);
HighsInt from_model = 1;
HighsInt from_model = 0;
HighsInt to_model = HighsInt(model.size());
for (HighsInt iModel = from_model; iModel < to_model; iModel++) {
const std::string filename =
std::string(HIGHS_DIR) + "/check/instances/" + model[iModel] + ".mps";
std::string(HIGHS_DIR) + "/check/instances/" + model[iModel] + ".mps";
highs.readModel(filename);
highs.run();
std::vector<double> optimal_solution = highs.getSolution().col_value;
double objective_function_value0 = highs.getInfo().objective_function_value;
highs.clearSolver();

UserMipSolution user_callback_data;
user_callback_data.optimal_objective_value = objective_function_value0;
user_callback_data.optimal_solution = optimal_solution.data();
user_callback_data.require_user_solution_callback_origin =
require_origin[iModel];
void* p_user_callback_data = (void*)(&user_callback_data);

// highs.setOptionValue("presolve", kHighsOffString);
Expand All @@ -443,7 +446,9 @@ TEST_CASE("highs-callback-mip-user-solution", "[highs-callback]") {
highs.run();
highs.stopCallback(kCallbackMipUserSolution);
double objective_function_value1 = highs.getInfo().objective_function_value;
double objective_diff = std::fabs(objective_function_value1 - objective_function_value0)/std::max(1.0, std::fabs(objective_function_value0));
double objective_diff =
std::fabs(objective_function_value1 - objective_function_value0) /
std::max(1.0, std::fabs(objective_function_value0));
REQUIRE(objective_diff < 1e-12);
}
}
1 change: 1 addition & 0 deletions check/TestEkk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ void ekk_solve(Highs& highs, std::string presolve,
}

REQUIRE(highs.resetOptions() == HighsStatus::kOk);
highs.setOptionValue("output_flag", dev_run);
}

void ekk_distillation(Highs& highs) {
Expand Down
1 change: 1 addition & 0 deletions check/TestMipSolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,7 @@ void rowlessMIP(Highs& highs) {
TEST_CASE("issue-2122", "[highs_test_mip_solver]") {
std::string filename = std::string(HIGHS_DIR) + "/check/instances/2122.lp";
Highs highs;
highs.setOptionValue("output_flag", dev_run);
highs.setOptionValue("mip_rel_gap", 0);
highs.setOptionValue("mip_abs_gap", 0);
highs.readModel(filename);
Expand Down
2 changes: 2 additions & 0 deletions check/TestSpecialLps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ void solve(Highs& highs, std::string presolve, std::string solver,
REQUIRE(iteration_count == require_iteration_count);
}
REQUIRE(highs.resetOptions() == HighsStatus::kOk);
highs.setOptionValue("output_flag", dev_run);
}

void distillation(Highs& highs) {
Expand Down Expand Up @@ -591,6 +592,7 @@ void singularStartingBasis(Highs& highs) {
optimal_objective, dev_run));

REQUIRE(highs.resetOptions() == HighsStatus::kOk);
highs.setOptionValue("output_flag", dev_run);

special_lps.reportSolution(highs, dev_run);
}
Expand Down
6 changes: 3 additions & 3 deletions src/mip/HighsMipSolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,9 @@ void HighsMipSolver::run() {
}
// Possibly look for primal solution from the user
if (!submip && callback_->user_callback &&
callback_->active[kCallbackMipUserSolution])
callback_->active[kCallbackMipUserSolution])
mipdata_->callbackUserSolution(solution_objective_,
kUserMipSolutionCallbackOriginAfterSetup);
kUserMipSolutionCallbackOriginAfterSetup);

// Apply the trivial heuristics
analysis_.mipTimerStart(kMipClockTrivialHeuristics);
Expand Down Expand Up @@ -214,7 +214,7 @@ void HighsMipSolver::run() {
if (!submip && callback_->user_callback &&
callback_->active[kCallbackMipUserSolution])
mipdata_->callbackUserSolution(solution_objective_,
kUserMipSolutionCallbackOriginBeforeDive);
kUserMipSolutionCallbackOriginBeforeDive);

analysis_.mipTimerStart(kMipClockPerformAging1);
mipdata_->conflictPool.performAging();
Expand Down
94 changes: 53 additions & 41 deletions src/mip/HighsMipSolverData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1274,13 +1274,12 @@ const std::vector<double>& HighsMipSolverData::getSolution() const {
bool HighsMipSolverData::addIncumbent(const std::vector<double>& sol,
double solobj, const int solution_source,
const bool print_display_line,
const bool is_user_solution) {
const bool is_user_solution) {
const bool execute_mip_solution_callback =
!is_user_solution &&
!mipsolver.submip &&
(mipsolver.callback_->user_callback
? mipsolver.callback_->active[kCallbackMipSolution]
: false);
!is_user_solution && !mipsolver.submip &&
(mipsolver.callback_->user_callback
? mipsolver.callback_->active[kCallbackMipSolution]
: false);
// Determine whether the potential new incumbent should be
// transformed
//
Expand Down Expand Up @@ -1311,7 +1310,8 @@ bool HighsMipSolverData::addIncumbent(const std::vector<double>& sol,
incumbent = sol;
double new_upper_limit = computeNewUpperLimit(solobj, 0.0, 0.0);

if (!is_user_solution && !mipsolver.submip) saveReportMipSolution(new_upper_limit);
if (!is_user_solution && !mipsolver.submip)
saveReportMipSolution(new_upper_limit);
if (new_upper_limit < upper_limit) {
++numImprovingSols;
upper_limit = new_upper_limit;
Expand Down Expand Up @@ -1511,7 +1511,8 @@ void HighsMipSolverData::printDisplayLine(const int solution_source) {
ub = mipsolver.options_mip_->objective_bound;

auto print_lp_iters = convertToPrintString(total_lp_iterations);
HighsInt dynamic_constraints_in_lp = lp.numRows() > 0 ? lp.numRows() - lp.getNumModelRows() : 0;
HighsInt dynamic_constraints_in_lp =
lp.numRows() > 0 ? lp.numRows() - lp.getNumModelRows() : 0;
if (upper_bound != kHighsInf) {
std::array<char, 22> gap_string = {};
if (gap >= 9999.)
Expand Down Expand Up @@ -1557,9 +1558,9 @@ void HighsMipSolverData::printDisplayLine(const int solution_source) {
// clang-format on
solutionSourceToString(solution_source).c_str(), print_nodes.data(),
queue_nodes.data(), print_leaves.data(), explored, lb_string.data(),
ub_string.data(), gap, cutpool.getNumCuts(),
dynamic_constraints_in_lp, conflictPool.getNumConflicts(),
print_lp_iters.data(), time_string.c_str());
ub_string.data(), gap, cutpool.getNumCuts(), dynamic_constraints_in_lp,
conflictPool.getNumConflicts(), print_lp_iters.data(),
time_string.c_str());
}
// Check that limitsToBounds yields the same values for the
// dual_bound, primal_bound (modulo optimization sense) and
Expand Down Expand Up @@ -1770,8 +1771,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.solution_objective_,
kUserMipSolutionCallbackOriginEvaluateRootNode0);
mipsolver.mipdata_->callbackUserSolution(
mipsolver.solution_objective_,
kUserMipSolutionCallbackOriginEvaluateRootNode0);

if (firstrootbasis.valid)
lp.getLpSolver().setBasis(firstrootbasis,
Expand Down Expand Up @@ -2008,10 +2010,10 @@ 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.solution_objective_,
kUserMipSolutionCallbackOriginEvaluateRootNode1);

mipsolver.callback_->active[kCallbackMipUserSolution])
mipsolver.mipdata_->callbackUserSolution(
mipsolver.solution_objective_,
kUserMipSolutionCallbackOriginEvaluateRootNode1);
}
mipsolver.analysis_.mipTimerStop(kMipClockSeparation);
if (mipsolver.analysis_.analyse_mip_time) {
Expand Down Expand Up @@ -2066,8 +2068,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.solution_objective_,
kUserMipSolutionCallbackOriginEvaluateRootNode2);
mipsolver.mipdata_->callbackUserSolution(
mipsolver.solution_objective_,
kUserMipSolutionCallbackOriginEvaluateRootNode2);

// Possible cut extraction callback
if (!mipsolver.submip && mipsolver.callback_->user_callback &&
Expand Down Expand Up @@ -2118,10 +2121,10 @@ void HighsMipSolverData::evaluateRootNode() {
printDisplayLine();
// Possibly look for primal solution from the user
if (!mipsolver.submip && mipsolver.callback_->user_callback &&
mipsolver.callback_->active[kCallbackMipUserSolution])
mipsolver.mipdata_->callbackUserSolution(mipsolver.solution_objective_,
kUserMipSolutionCallbackOriginEvaluateRootNode3);

mipsolver.callback_->active[kCallbackMipUserSolution])
mipsolver.mipdata_->callbackUserSolution(
mipsolver.solution_objective_,
kUserMipSolutionCallbackOriginEvaluateRootNode3);
}

if (upper_limit != kHighsInf || mipsolver.submip) break;
Expand Down Expand Up @@ -2154,14 +2157,15 @@ void HighsMipSolverData::evaluateRootNode() {

++nseparounds;
printDisplayLine();
// Possibly look for primal solution from the user
if (!mipsolver.submip && mipsolver.callback_->user_callback &&
mipsolver.callback_->active[kCallbackMipUserSolution])
mipsolver.mipdata_->callbackUserSolution(mipsolver.solution_objective_,
kUserMipSolutionCallbackOriginEvaluateRootNode4);

}

// Possibly look for primal solution from the user
if (!mipsolver.submip && mipsolver.callback_->user_callback &&
mipsolver.callback_->active[kCallbackMipUserSolution])
mipsolver.mipdata_->callbackUserSolution(
mipsolver.solution_objective_,
kUserMipSolutionCallbackOriginEvaluateRootNode4);

removeFixedIndices();
if (lp.getLpSolver().getBasis().valid) lp.removeObsoleteRows();
rootlpsolobj = lp.getObjective();
Expand Down Expand Up @@ -2411,10 +2415,12 @@ bool HighsMipSolverData::interruptFromCallbackWithData(
return mipsolver.callback_->callbackAction(callback_type, message);
}

void HighsMipSolverData::callbackUserSolution(const double mipsolver_objective_value,
const HighsInt user_solution_callback_origin) {
void HighsMipSolverData::callbackUserSolution(
const double mipsolver_objective_value,
const HighsInt user_solution_callback_origin) {
setCallbackDataOut(mipsolver_objective_value);
mipsolver.callback_->data_out.user_solution_callback_origin = user_solution_callback_origin;
mipsolver.callback_->data_out.user_solution_callback_origin =
user_solution_callback_origin;

mipsolver.callback_->clearHighsCallbackDataIn();
const bool interrupt = mipsolver.callback_->callbackAction(
Expand All @@ -2430,21 +2436,27 @@ void HighsMipSolverData::callbackUserSolution(const double mipsolver_objective_v
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);
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_);
highsLogUser(
mipsolver.options_mip_->log_options, HighsLogType::kWarning,
"User-supplied solution has with objective %g has violations: "

Check warning on line 2446 in src/mip/HighsMipSolverData.cpp

View check run for this annotation

Codecov / codecov/patch

src/mip/HighsMipSolverData.cpp#L2445-L2446

Added lines #L2445 - L2446 were not covered by tests
"bound = %.4g; integrality = %.4g; row = %.4g\n",
user_solution_objective_value, bound_violation_,
integrality_violation_, row_violation_);
return;
}

Check warning on line 2451 in src/mip/HighsMipSolverData.cpp

View check run for this annotation

Codecov / codecov/patch

src/mip/HighsMipSolverData.cpp#L2451

Added line #L2451 was not covered by tests
std::vector<double> reduced_user_solution;
reduced_user_solution = postSolveStack.getReducedPrimalSolution(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);
addIncumbent(reduced_user_solution, user_solution_objective_value,
kSolutionSourceUserSolution, print_display_line,
is_user_solution);
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/mip/HighsMipSolverData.h
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ struct HighsMipSolverData {
bool addIncumbent(const std::vector<double>& sol, double solobj,
const int solution_source,
const bool print_display_line = true,
const bool is_user_solution = false);
const bool is_user_solution = false);

const std::vector<double>& getSolution() const;

Expand All @@ -291,7 +291,7 @@ struct HighsMipSolverData {
const double mipsolver_objective_value,
const std::string message = "") const;
void callbackUserSolution(const double mipsolver_objective_value,
const HighsInt user_solution_callback_origin);
const HighsInt user_solution_callback_origin);
};

#endif

0 comments on commit 9ac2d41

Please sign in to comment.