From 7fd28a11519ea40cea74115ad409a82198189bf3 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sun, 14 Jul 2024 17:29:08 +0100 Subject: [PATCH] Now clearing solution when completeSolutionFromDiscreteAssignment decides it's not worth it --- check/TestCallbacks.cpp | 2 +- check/TestCheckSolution.cpp | 33 ++++-- src/Highs.h | 5 +- src/lp_data/HConst.h | 1 + src/lp_data/Highs.cpp | 197 +++++++++++++++++++++++++++--------- 5 files changed, 178 insertions(+), 60 deletions(-) diff --git a/check/TestCallbacks.cpp b/check/TestCallbacks.cpp index 62e9dc105f..437fdaf7b3 100644 --- a/check/TestCallbacks.cpp +++ b/check/TestCallbacks.cpp @@ -6,7 +6,7 @@ #include "catch.hpp" #include "lp_data/HighsCallback.h" -const bool dev_run = true; +const bool dev_run = false; const double egout_optimal_objective = 568.1007; const double egout_objective_target = 610; diff --git a/check/TestCheckSolution.cpp b/check/TestCheckSolution.cpp index 3ef82eb0bc..d0f2309b1c 100644 --- a/check/TestCheckSolution.cpp +++ b/check/TestCheckSolution.cpp @@ -5,7 +5,7 @@ #include "SpecialLps.h" #include "catch.hpp" -const bool dev_run = true; +const bool dev_run = false; void runWriteReadCheckSolution(Highs& highs, const std::string model, const HighsModelStatus require_model_status, @@ -89,7 +89,7 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") { highs.clear(); - const bool other_tests = false;//true; + const bool other_tests = true; const bool test0 = other_tests; bool valid, integral, feasible; if (test0) { @@ -229,7 +229,7 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") { highs.clear(); } - const bool test6 = true;//other_tests; + const bool test6 = other_tests; if (test6) { if (dev_run) printf( @@ -237,28 +237,41 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") { "solution\n"); HighsInt num_integer_variable = 0; for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) - if (lp.integrality_[iCol] == HighsVarType::kInteger) num_integer_variable++; + if (lp.integrality_[iCol] == HighsVarType::kInteger) + num_integer_variable++; - highs.setOptionValue("output_flag", dev_run); highs.readModel(model_file); + std::vector index; + std::vector value; + // Check that duplicate values are spotted + index.push_back(0); + value.push_back(0); + index.push_back(1); + value.push_back(1); + index.push_back(0); + value.push_back(2); + HighsInt num_entries = index.size(); + return_status = highs.setSolution(num_entries, index.data(), value.data()); + REQUIRE(return_status == HighsStatus::kWarning); + index.clear(); + value.clear(); std::vector is_set; is_set.assign(lp.num_col_, false); - std::vector index; - std::vector value; - HighsInt num_to_set = std::max(10, (8*num_integer_variable)/10); - assert(num_to_set>0); + HighsInt num_to_set = 2; + assert(num_to_set > 0); HighsRandom random; for (HighsInt iSet = 0; iSet < num_to_set;) { HighsInt iCol = random.integer(lp.num_col_); if (lp.integrality_[iCol] != HighsVarType::kInteger) continue; if (is_set[iCol]) continue; + is_set[iCol] = true; index.push_back(iCol); value.push_back(optimal_solution.col_value[iCol]); iSet++; } - HighsInt num_entries = index.size(); + num_entries = index.size(); assert(num_entries == num_to_set); return_status = highs.setSolution(num_entries, index.data(), value.data()); REQUIRE(return_status == HighsStatus::kOk); diff --git a/src/Highs.h b/src/Highs.h index 3a1c637642..f104d72f1d 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -1066,9 +1066,8 @@ class Highs { /** * @brief Pass a sparse primal solution */ - HighsStatus setSolution(const HighsInt num_entries, - const HighsInt* index, - const double* value); + HighsStatus setSolution(const HighsInt num_entries, const HighsInt* index, + const double* value); /** * @brief Set the callback method to use for HiGHS diff --git a/src/lp_data/HConst.h b/src/lp_data/HConst.h index e9359556e2..d7980f4cc8 100644 --- a/src/lp_data/HConst.h +++ b/src/lp_data/HConst.h @@ -27,6 +27,7 @@ const size_t kHighsSize_tInf = std::numeric_limits::max(); const HighsInt kHighsIInf = std::numeric_limits::max(); const HighsInt kHighsIInf32 = std::numeric_limits::max(); const double kHighsInf = std::numeric_limits::infinity(); +const double kHighsUndefined = kHighsInf; const double kHighsTiny = 1e-14; const double kHighsMacheps = std::ldexp(1, -52); const double kHighsZero = 1e-50; diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index 691af85ade..bf3c72b384 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -1966,11 +1966,53 @@ HighsStatus Highs::setSolution(const HighsSolution& solution) { } HighsStatus Highs::setSolution(const HighsInt num_entries, - const HighsInt* index, - const double* value) { + const HighsInt* index, const double* value) { HighsStatus return_status = HighsStatus::kOk; - return_status = HighsStatus::kError; - return returnFromHighs(return_status); + // Warn about duplicates in index + HighsInt num_duplicates = 0; + std::vector is_set; + is_set.assign(model_.lp_.num_col_, false); + for (HighsInt iX = 0; iX < num_entries; iX++) { + HighsInt iCol = index[iX]; + if (iCol < 0 || iCol > model_.lp_.num_col_) { + highsLogUser(options_.log_options, HighsLogType::kError, + "setSolution: User solution index %d has value %d out of " + "range [0, %d)", + int(iX), int(iCol), int(model_.lp_.num_col_)); + return HighsStatus::kError; + } else if (value[iX] < model_.lp_.col_lower_[iCol] - + options_.primal_feasibility_tolerance || + model_.lp_.col_upper_[iCol] + + options_.primal_feasibility_tolerance < + value[iX]) { + highsLogUser(options_.log_options, HighsLogType::kError, + "setSolution: User solution value %d of %g is infeasible " + "for bounds [%g, %g]", + int(iX), value[iX], model_.lp_.col_lower_[iCol], + model_.lp_.col_upper_[iCol]); + return HighsStatus::kError; + } + if (is_set[iCol]) num_duplicates++; + is_set[iCol] = true; + } + if (num_duplicates > 0) { + highsLogUser(options_.log_options, HighsLogType::kWarning, + "setSolution: User set of indices has %d duplicate%s: last " + "value used\n", + int(num_duplicates), num_duplicates > 1 ? "s" : ""); + return_status = HighsStatus::kWarning; + } + + // Clear the solution, indicate the values not determined by the + // user, and insert the values determined by the user + HighsSolution new_solution; + new_solution.col_value.assign(model_.lp_.num_col_, kHighsUndefined); + for (HighsInt iX = 0; iX < num_entries; iX++) { + HighsInt iCol = index[iX]; + new_solution.col_value[iCol] = value[iX]; + } + return interpretCallStatus(options_.log_options, setSolution(new_solution), + return_status, "setSolution"); } HighsStatus Highs::setCallback(HighsCallbackFunctionType user_callback, @@ -3356,62 +3398,125 @@ void Highs::invalidateEkk() { ekk_instance_.invalidate(); } HighsStatus Highs::completeSolutionFromDiscreteAssignment() { // Determine whether the current solution of a MIP is feasible and, - // if not, try to assign values to continuous variables to achieve a - // feasible solution. Valuable in the case where users make a - // heuristic assignment of discrete variables + // if not, try to assign values to continuous variables and discrete + // variables not at integer values to achieve a feasible + // solution. Valuable in the case where users make a heuristic + // (partial) assignment of discrete variables assert(model_.isMip() && solution_.value_valid); HighsLp& lp = model_.lp_; - bool valid, integral, feasible; - // Determine whether this solution is feasible, or just integer feasible - HighsStatus return_status = assessLpPrimalSolution(options_, lp, solution_, - valid, integral, feasible); - assert(return_status != HighsStatus::kError); - assert(valid); - // If the current solution is feasible, then solution can be used by - // MIP solver to get a primal bound - if (feasible) return HighsStatus::kOk; + // Determine whether the solution contains undefined values, in + // order to decide whether to check its feasibility + bool contains_undefined_values = false; + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + if (solution_.col_value[iCol] == kHighsUndefined) { + contains_undefined_values = true; + break; + } + } + if (!contains_undefined_values) { + bool valid, integral, feasible; + // Determine whether this solution is integer feasible + HighsStatus return_status = assessLpPrimalSolution( + options_, lp, solution_, valid, integral, feasible); + assert(return_status != HighsStatus::kError); + assert(valid); + // If the current solution is integer feasible, then it can be + // used by MIP solver to get a primal bound + if (feasible) return HighsStatus::kOk; + } // Save the column bounds and integrality in preparation for fixing - // the non-continuous variables when user-supplied values are + // the discrete variables when user-supplied values are // integer std::vector save_col_lower = lp.col_lower_; std::vector save_col_upper = lp.col_upper_; std::vector save_integrality = lp.integrality_; const bool have_integrality = (lp.integrality_.size() != 0); - bool is_integer = true; + assert(have_integrality); + // Count the number of fixed and unfixed discrete variables + HighsInt num_fixed_discrete_variable = 0; + HighsInt num_unfixed_discrete_variable = 0; for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { - if (lp.integrality_[iCol] == HighsVarType::kContinuous) continue; - // Fix non-continuous variable if it has integer value const double primal = solution_.col_value[iCol]; - const double lower = lp.col_lower_[iCol]; - const double upper = lp.col_upper_[iCol]; - const HighsVarType type = - have_integrality ? lp.integrality_[iCol] : HighsVarType::kContinuous; - double col_infeasibility = 0; - double integer_infeasibility = 0; - assessColPrimalSolution(options_, primal, lower, upper, type, - col_infeasibility, integer_infeasibility); - if (integer_infeasibility > options_.mip_feasibility_tolerance) { - // Variable is not integer feasible, so record that a MIP will - // have to be solved - is_integer = false; + // Default value is lower bound, unless primal is integer for a + // discrete variable + solution_.col_value[iCol] = lp.col_lower_[iCol]; + if (lp.integrality_[iCol] == HighsVarType::kContinuous) continue; + // Fix discrete variable if its value is defined and integer + if (primal == kHighsUndefined) { + num_unfixed_discrete_variable++; } else { - // Variable is integer feasible, so fix it at this value and - // remove its integrality - lp.col_lower_[iCol] = solution_.col_value[iCol]; - lp.col_upper_[iCol] = solution_.col_value[iCol]; - lp.integrality_[iCol] = HighsVarType::kContinuous; + const double lower = lp.col_lower_[iCol]; + const double upper = lp.col_upper_[iCol]; + const HighsVarType type = + have_integrality ? lp.integrality_[iCol] : HighsVarType::kContinuous; + double col_infeasibility = 0; + double integer_infeasibility = 0; + assessColPrimalSolution(options_, primal, lower, upper, type, + col_infeasibility, integer_infeasibility); + if (integer_infeasibility > options_.mip_feasibility_tolerance) { + num_unfixed_discrete_variable++; + } else { + // Variable is integer feasible, so fix it at this value and + // remove its integrality + num_fixed_discrete_variable++; + lp.col_lower_[iCol] = primal; + lp.col_upper_[iCol] = primal; + lp.integrality_[iCol] = HighsVarType::kContinuous; + } } } - // If the solution is integer valued, only an LP needs to be solved, - // so clear all integrality - if (is_integer) lp.integrality_.clear(); + const HighsInt num_discrete_variable = + num_unfixed_discrete_variable + num_fixed_discrete_variable; + const HighsInt num_continuous_variable = lp.num_col_ - num_discrete_variable; + assert(num_continuous_variable >= 0); + bool call_run = true; + if (num_unfixed_discrete_variable == 0) { + // Solution is integer valued + if (num_continuous_variable == 0) { + // There are no continuous variables, so no feasible solution can be + // deduced + highsLogUser(options_.log_options, HighsLogType::kInfo, + "User-supplied values of discrete variables cannot yield " + "feasible solution\n"); + call_run = false; + } else { + // Solve an LP, so clear all integrality + lp.integrality_.clear(); + highsLogUser( + options_.log_options, HighsLogType::kInfo, + "Attempting to find feasible solution " + "by solving LP for user-supplied values of discrete variables\n"); + } + } else { + // There are unfixed discrete variables + if (10 * num_fixed_discrete_variable < num_discrete_variable) { + // Too few discrete variables are fixed + highsLogUser(options_.log_options, HighsLogType::kInfo, + "User-supplied values fix only %d / %d discrete variables, " + "so not attempting to complete a feasible solution\n", + int(num_fixed_discrete_variable), + int(num_discrete_variable)); + call_run = false; + } else { + highsLogUser(options_.log_options, HighsLogType::kInfo, + "Attempting to find feasible solution " + "by solving MIP for user-supplied values of %d / %d " + "discrete variables\n", + int(num_fixed_discrete_variable), + int(num_discrete_variable)); + } + } + HighsStatus return_status = HighsStatus::kOk; + // Clear the current solution since either the user solution has + // been used to fix (a subset of) discrete variables - so a valid + // solution will be obtained from run() if the local model is + // feasible - or it's not worth using the user solution solution_.clear(); - basis_.clear(); - // Solve the model - highsLogUser(options_.log_options, HighsLogType::kInfo, - "Attempting to find feasible solution " - "for (partial) user-supplied values of discrete variables\n"); - return_status = this->run(); + if (call_run) { + // Solve the model + basis_.clear(); + return_status = this->run(); + } // Recover the column bounds and integrality lp.col_lower_ = save_col_lower; lp.col_upper_ = save_col_upper;