From 0ab3f0fabd4428f26060cbedbbb7db5880bf366b Mon Sep 17 00:00:00 2001 From: Pavlo Muts Date: Tue, 22 Jul 2025 10:29:52 +0200 Subject: [PATCH 1/3] setSolution() from Highs accepts solution hint --- ortools/math_opt/solvers/highs_solver.cc | 16 ++++++++++++++++ ortools/math_opt/solvers/highs_solver_test.cc | 4 ++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/ortools/math_opt/solvers/highs_solver.cc b/ortools/math_opt/solvers/highs_solver.cc index 25f66f1e7f..5d5bccf7e1 100644 --- a/ortools/math_opt/solvers/highs_solver.cc +++ b/ortools/math_opt/solvers/highs_solver.cc @@ -925,6 +925,22 @@ absl::StatusOr HighsSolver::Solve( return absl::OkStatus(); }; + if (model_parameters.solution_hints_size() > 0) { + // Take the first solution hint and set the solution. + const SolutionHintProto& hint = model_parameters.solution_hints(0); + const int num_vars = highs_->getModel().lp_.num_col_; + // Highs accepts only full solutions, on partial solutions it will + // return an error. + if (hint.variable_values().ids_size() == num_vars) { + HighsSolution sol = HighsSolution(); + sol.col_value.resize(num_vars); + for (const auto [id, val] : MakeView(hint.variable_values())) { + sol.col_value[variable_data_.at(id).index] = val; + } + RETURN_IF_ERROR(ToStatus(highs_->setSolution(sol))); + } + } + RETURN_IF_ERROR(ListInvertedBounds().ToStatus()); // TODO(b/271595607): delete this code once we upgrade HiGHS, if HiGHS does // return a proper infeasibility status for models with empty integer bounds. diff --git a/ortools/math_opt/solvers/highs_solver_test.cc b/ortools/math_opt/solvers/highs_solver_test.cc index ccb276129b..3afa835800 100644 --- a/ortools/math_opt/solvers/highs_solver_test.cc +++ b/ortools/math_opt/solvers/highs_solver_test.cc @@ -164,8 +164,8 @@ INSTANTIATE_TEST_SUITE_P(HighsLpModelSolveParametersTest, /*supports_duals=*/true, /*supports_primal_only_warm_starts=*/false))); -// MIP hint appears to be supported by Highs::setSolution, this is not yet -// implemented. +// Highs::setSolution is implemented, but it only accepts complete solutions. +// The test below generates partial solutuions, so we skip it. GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MipSolutionHintTest); // HiGHS does not support branching priority. From 97636fdec7abe4146bacce72d2445a8eb6309dad Mon Sep 17 00:00:00 2001 From: Pavlo Muts Date: Tue, 22 Jul 2025 13:29:46 +0200 Subject: [PATCH 2/3] test: enable primal only warm starts for Highs LP model solve parameters --- ortools/math_opt/solvers/highs_solver_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ortools/math_opt/solvers/highs_solver_test.cc b/ortools/math_opt/solvers/highs_solver_test.cc index 3afa835800..c9a062228c 100644 --- a/ortools/math_opt/solvers/highs_solver_test.cc +++ b/ortools/math_opt/solvers/highs_solver_test.cc @@ -162,7 +162,7 @@ INSTANTIATE_TEST_SUITE_P(HighsLpModelSolveParametersTest, Values(LpModelSolveParametersTestParameters( SolverType::kHighs, /*exact_zeros=*/true, /*supports_duals=*/true, - /*supports_primal_only_warm_starts=*/false))); + /*supports_primal_only_warm_starts=*/true))); // Highs::setSolution is implemented, but it only accepts complete solutions. // The test below generates partial solutuions, so we skip it. From fd1443e8200d57638cde1e73dbfb38f5acc6daf5 Mon Sep 17 00:00:00 2001 From: Pavlo Muts Date: Tue, 22 Jul 2025 19:29:26 +0200 Subject: [PATCH 3/3] Implement setting of partial solution, activate corresponding tests --- ortools/math_opt/solvers/highs_solver.cc | 20 +++++++++---------- ortools/math_opt/solvers/highs_solver_test.cc | 15 +++++++++++--- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/ortools/math_opt/solvers/highs_solver.cc b/ortools/math_opt/solvers/highs_solver.cc index 5d5bccf7e1..42e30f9033 100644 --- a/ortools/math_opt/solvers/highs_solver.cc +++ b/ortools/math_opt/solvers/highs_solver.cc @@ -14,7 +14,6 @@ // Unimplemented features: // * Quadratic objective // * TODO(b/272767311): initial basis, more precise returned basis. -// * Starting solution // * TODO(b/271104776): Returning rays #include "ortools/math_opt/solvers/highs_solver.h" @@ -928,17 +927,16 @@ absl::StatusOr HighsSolver::Solve( if (model_parameters.solution_hints_size() > 0) { // Take the first solution hint and set the solution. const SolutionHintProto& hint = model_parameters.solution_hints(0); - const int num_vars = highs_->getModel().lp_.num_col_; - // Highs accepts only full solutions, on partial solutions it will - // return an error. - if (hint.variable_values().ids_size() == num_vars) { - HighsSolution sol = HighsSolution(); - sol.col_value.resize(num_vars); - for (const auto [id, val] : MakeView(hint.variable_values())) { - sol.col_value[variable_data_.at(id).index] = val; - } - RETURN_IF_ERROR(ToStatus(highs_->setSolution(sol))); + HighsInt num_entries = hint.variable_values().ids_size(); + std::vector index(num_entries); + std::vector value(num_entries); + size_t i = 0; + for (const auto [id, val] : MakeView(hint.variable_values())) { + index[i] = variable_data_.at(id).index; + value[i] = val; + ++i; } + RETURN_IF_ERROR(ToStatus(highs_->setSolution(num_entries, index.data(), value.data()))); } RETURN_IF_ERROR(ListInvertedBounds().ToStatus()); diff --git a/ortools/math_opt/solvers/highs_solver_test.cc b/ortools/math_opt/solvers/highs_solver_test.cc index c9a062228c..6c3805bdda 100644 --- a/ortools/math_opt/solvers/highs_solver_test.cc +++ b/ortools/math_opt/solvers/highs_solver_test.cc @@ -164,9 +164,18 @@ INSTANTIATE_TEST_SUITE_P(HighsLpModelSolveParametersTest, /*supports_duals=*/true, /*supports_primal_only_warm_starts=*/true))); -// Highs::setSolution is implemented, but it only accepts complete solutions. -// The test below generates partial solutuions, so we skip it. -GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MipSolutionHintTest); +SolutionHintTestParams MakeHighsSolutionHintParams() { + SolveParameters solve_params; + solve_params.presolve = Emphasis::kOff; + (*solve_params.highs.mutable_int_options())["mip_max_nodes"] = 0; + std::string hint_message_regex = + "Attempting to find feasible solution by " + "solving MIP for user-supplied values of"; + return SolutionHintTestParams(SolverType::kHighs, solve_params, std::nullopt, + hint_message_regex); +} +INSTANTIATE_TEST_SUITE_P(HighsSolutionHintTest, MipSolutionHintTest, + Values(MakeHighsSolutionHintParams())); // HiGHS does not support branching priority. GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(BranchPrioritiesTest);