diff --git a/check/TestCAPI.c b/check/TestCAPI.c index 9b94ccc126..e16fd5b0aa 100644 --- a/check/TestCAPI.c +++ b/check/TestCAPI.c @@ -1876,23 +1876,18 @@ void test_multiObjective() { double a_value[6] = {3, 1, 1, 1, 1, 2}; HighsInt integrality[2] = {kHighsVarTypeInteger, kHighsVarTypeInteger}; - // Highs_setBoolOptionValue(highs, "output_flag", dev_run); + Highs_setBoolOptionValue(highs, "output_flag", dev_run); HighsInt return_status = Highs_passLp(highs, num_col, num_row, num_nz, a_format, sense, offset, col_cost, col_lower, col_upper, row_lower, row_upper, a_start, a_index, a_value); assert(return_status == kHighsStatusOk); - return_status = Highs_run(highs); - assert(return_status == kHighsStatusOk); - HighsInt model_status = Highs_getModelStatus(highs); - assert(model_status == kHighsModelStatusOptimal); - Highs_writeSolutionPretty(highs, ""); return_status = Highs_clearLinearObjectives(highs); assert(return_status == kHighsStatusOk); double weight = -1; double linear_objective_offset = -1; - double coefficients[2] = {2, 1}; + double coefficients[2] = {1, 1}; double abs_tolerance = 0; double rel_tolerance = 0; HighsInt priority = 10; @@ -1909,7 +1904,7 @@ void test_multiObjective() { return_status = Highs_run(highs); assert(return_status == kHighsStatusOk); - model_status = Highs_getModelStatus(highs); + HighsInt model_status = Highs_getModelStatus(highs); assert(model_status == kHighsModelStatusOptimal); Highs_writeSolutionPretty(highs, ""); @@ -1921,13 +1916,53 @@ void test_multiObjective() { Highs_setBoolOptionValue(highs, "blend_multi_objectives", 0); - // double weight = {}; - // double offset = {}; - // double coefficients = {}; - // double abs_tolerance = {}; - // double rel_tolerance = {}; - // HighsInt priority = {}; - + if (dev_run) printf("\n***************\nLexicographic 1\n***************\n"); + double weight2[2] = {-1, 1e-4}; + double linear_objective_offset2[2] = {-1, 0}; + double coefficients2[4] = {1, 1, 1, 0}; + double abs_tolerance2[2] = {0, -1}; + double rel_tolerance2[2] = {0, -1}; + HighsInt priority2[2] = {10, 0}; + return_status = Highs_passLinearObjectives(highs, 2, weight2, linear_objective_offset2, coefficients2, abs_tolerance2, rel_tolerance2, priority2); + return_status = Highs_run(highs); + assert(return_status == kHighsStatusOk); + model_status = Highs_getModelStatus(highs); + assert(model_status == kHighsModelStatusOptimal); + Highs_writeSolutionPretty(highs, ""); + return_status = + Highs_getSolution(highs, col_value, NULL, NULL, NULL); + assertDoubleValuesEqual("col_value[0]", col_value[0], 2); + assertDoubleValuesEqual("col_value[1]", col_value[1], 6); + + // weight2[1] = 1e-5; + coefficients2[0] = 1.0001; + abs_tolerance2[0] = 1e-5; + rel_tolerance2[0] = 0.05; + return_status = Highs_passLinearObjectives(highs, 2, weight2, linear_objective_offset2, coefficients2, abs_tolerance2, rel_tolerance2, priority2); + return_status = Highs_run(highs); + assert(return_status == kHighsStatusOk); + model_status = Highs_getModelStatus(highs); + assert(model_status == kHighsModelStatusOptimal); + Highs_writeSolutionPretty(highs, ""); + return_status = + Highs_getSolution(highs, col_value, NULL, NULL, NULL); + assertDoubleValuesEqual("col_value[0]", col_value[0], 4.9); + assertDoubleValuesEqual("col_value[1]", col_value[1], 3.1); + + if (dev_run) printf("\n***************\nLexicographic 2\n***************\n"); + abs_tolerance2[0] = -1; + + return_status = Highs_passLinearObjectives(highs, 2, weight2, linear_objective_offset2, coefficients2, abs_tolerance2, rel_tolerance2, priority2); + return_status = Highs_run(highs); + assert(return_status == kHighsStatusOk); + model_status = Highs_getModelStatus(highs); + assert(model_status == kHighsModelStatusOptimal); + Highs_writeSolutionPretty(highs, ""); + return_status = + Highs_getSolution(highs, col_value, NULL, NULL, NULL); + assertDoubleValuesEqual("col_value[0]", col_value[0], 1.30069); + assertDoubleValuesEqual("col_value[1]", col_value[1], 6.34966); + Highs_destroy(highs); free(col_value); } diff --git a/check/TestMultiObjective.cpp b/check/TestMultiObjective.cpp index 9761c3045b..1a15e8d9d2 100644 --- a/check/TestMultiObjective.cpp +++ b/check/TestMultiObjective.cpp @@ -56,7 +56,7 @@ TEST_CASE("multi-objective", "[util]") { if (dev_run) printf("\nPass illegal linear objective\n"); linear_objective.weight = -obj_mu; linear_objective.offset = -obj_mu; - linear_objective.coefficients = {obj_mu * 2, obj_mu * 1, obj_mu * 0}; + linear_objective.coefficients = {obj_mu * 1, obj_mu * 1, obj_mu * 0}; linear_objective.abs_tolerance = 0.0; linear_objective.rel_tolerance = 0.0; REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kError); diff --git a/src/Highs.h b/src/Highs.h index bd7090f826..04a4a3ce25 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -1524,8 +1524,6 @@ class Highs { HighsStatus returnFromRun(const HighsStatus return_status, const bool undo_mods); HighsStatus returnFromHighs(const HighsStatus return_status); - HighsStatus returnFromLexicographicOptimization( - const HighsStatus return_status, HighsInt original_lp_num_row); void reportSolvedLpQpStats(); // Interface methods @@ -1623,6 +1621,8 @@ class Highs { HighsInt* iis_col_index, HighsInt* iis_row_index, HighsInt* iis_col_bound, HighsInt* iis_row_bound); + HighsStatus returnFromLexicographicOptimization( + const HighsStatus return_status, HighsInt original_lp_num_row); HighsStatus multiobjectiveSolve(); bool aFormatOk(const HighsInt num_nz, const HighsInt format); diff --git a/src/interfaces/highs_c_api.cpp b/src/interfaces/highs_c_api.cpp index 54ca0ff74b..655c0fce30 100644 --- a/src/interfaces/highs_c_api.cpp +++ b/src/interfaces/highs_c_api.cpp @@ -291,34 +291,45 @@ HighsInt Highs_passHessian(void* highs, const HighsInt dim, ->passHessian(dim, num_nz, format, start, index, value); } -HighsInt Highs_passLinearObjectives(const void* highs, const HighsInt num_linear_objective, - const double* weight, const double* offset, const double* coefficients, - const double* abs_tolerance, const double* rel_tolerance, const HighsInt* priority) { +HighsInt Highs_passLinearObjectives(const void* highs, + const HighsInt num_linear_objective, + const double* weight, const double* offset, + const double* coefficients, + const double* abs_tolerance, + const double* rel_tolerance, + const HighsInt* priority) { HighsInt status = Highs_clearLinearObjectives(highs); if (status != kHighsStatusOk) return status; HighsInt num_col = Highs_getNumCol(highs); for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++) { - status = Highs_addLinearObjectiveWithIndex(highs, weight[iObj], offset[iObj], &coefficients[iObj*num_col], abs_tolerance[iObj], rel_tolerance[iObj], priority[iObj], iObj); + status = Highs_addLinearObjectiveWithIndex( + highs, weight[iObj], offset[iObj], &coefficients[iObj * num_col], + abs_tolerance[iObj], rel_tolerance[iObj], priority[iObj], iObj); if (status != kHighsStatusOk) return status; } return kHighsStatusOk; } -HighsInt Highs_addLinearObjective(const void* highs, - const double weight, const double offset, const double* coefficients, - const double abs_tolerance, const double rel_tolerance, const HighsInt priority) { - return Highs_addLinearObjectiveWithIndex(highs, weight, offset, coefficients, abs_tolerance, rel_tolerance, priority, -1); +HighsInt Highs_addLinearObjective(const void* highs, const double weight, + const double offset, + const double* coefficients, + const double abs_tolerance, + const double rel_tolerance, + const HighsInt priority) { + return Highs_addLinearObjectiveWithIndex(highs, weight, offset, coefficients, + abs_tolerance, rel_tolerance, + priority, -1); } -HighsInt Highs_addLinearObjectiveWithIndex(const void* highs, - const double weight, const double offset, const double* coefficients, - const double abs_tolerance, const double rel_tolerance, const HighsInt priority, - const HighsInt iObj) { +HighsInt Highs_addLinearObjectiveWithIndex( + const void* highs, const double weight, const double offset, + const double* coefficients, const double abs_tolerance, + const double rel_tolerance, const HighsInt priority, const HighsInt iObj) { HighsLinearObjective linear_objective; HighsInt num_col = Highs_getNumCol(highs); linear_objective.weight = weight; linear_objective.offset = offset; - for (HighsInt iCol = 0; iCol < num_col; iCol++) + for (HighsInt iCol = 0; iCol < num_col; iCol++) linear_objective.coefficients.push_back(coefficients[iCol]); linear_objective.abs_tolerance = abs_tolerance; linear_objective.rel_tolerance = rel_tolerance; diff --git a/src/interfaces/highs_c_api.h b/src/interfaces/highs_c_api.h index ba5255d98f..db8764fae6 100644 --- a/src/interfaces/highs_c_api.h +++ b/src/interfaces/highs_c_api.h @@ -557,18 +557,25 @@ HighsInt Highs_passHessian(void* highs, const HighsInt dim, const HighsInt* start, const HighsInt* index, const double* value); -HighsInt Highs_passLinearObjectives(const void* highs, const HighsInt num_linear_objective, - const double* weight, const double* offset, const double* coefficients, - const double* abs_tolerance, const double* rel_tolerance, const HighsInt* priority); - -HighsInt Highs_addLinearObjective(const void* highs, - const double weight, const double offset, const double* coefficients, - const double abs_tolerance, const double rel_tolerance, const HighsInt priority); - -HighsInt Highs_addLinearObjectiveWithIndex(const void* highs, - const double weight, const double offset, const double* coefficients, - const double abs_tolerance, const double rel_tolerance, const HighsInt priority, - const HighsInt iObj); +HighsInt Highs_passLinearObjectives(const void* highs, + const HighsInt num_linear_objective, + const double* weight, const double* offset, + const double* coefficients, + const double* abs_tolerance, + const double* rel_tolerance, + const HighsInt* priority); + +HighsInt Highs_addLinearObjective(const void* highs, const double weight, + const double offset, + const double* coefficients, + const double abs_tolerance, + const double rel_tolerance, + const HighsInt priority); + +HighsInt Highs_addLinearObjectiveWithIndex( + const void* highs, const double weight, const double offset, + const double* coefficients, const double abs_tolerance, + const double rel_tolerance, const HighsInt priority, const HighsInt iObj); HighsInt Highs_clearLinearObjectives(const void* highs); /** diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 3e2676b74a..3669f69f78 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -3664,7 +3664,9 @@ HighsStatus Highs::returnFromLexicographicOptimization( } HighsStatus Highs::multiobjectiveSolve() { + const HighsInt coeff_logging_size_limit = 10; HighsInt num_linear_objective = this->multi_linear_objective_.size(); + assert(num_linear_objective > 0); HighsLp& lp = this->model_.lp_; for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++) { @@ -3680,6 +3682,37 @@ HighsStatus Highs::multiobjectiveSolve() { } } + std::unique_ptr multi_objective_log; + highsLogUser(options_.log_options, HighsLogType::kInfo, + "Solving with %d multiple linear objectives, %s\n", + int(num_linear_objective), + this->options_.blend_multi_objectives + ? "blending objectives by weight" + : "using lexicographic optimization by priority"); + highsLogUser( + options_.log_options, HighsLogType::kInfo, + "Ix weight offset abs_tol rel_tol priority%s\n", + lp.num_col_ < coeff_logging_size_limit ? " coefficients" : ""); + for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++) { + HighsLinearObjective& linear_objective = + this->multi_linear_objective_[iObj]; + multi_objective_log = + std::unique_ptr(new std::stringstream()); + *multi_objective_log << highsFormatToString( + "%2d %11.6g %11.6g %11.6g %11.6g %11d ", int(iObj), + linear_objective.weight, linear_objective.offset, + linear_objective.abs_tolerance, linear_objective.rel_tolerance, + linear_objective.priority); + if (lp.num_col_ < coeff_logging_size_limit) { + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + *multi_objective_log << highsFormatToString( + "%s c_{%1d} = %g", iCol == 0 ? "" : ",", int(iCol), + linear_objective.coefficients[iCol]); + } + *multi_objective_log << "\n"; + highsLogUser(options_.log_options, HighsLogType::kInfo, "%s", + multi_objective_log->str().c_str()); + } this->clearSolver(); if (this->options_.blend_multi_objectives) { // Objectives are blended by weight and minimized @@ -3695,6 +3728,22 @@ HighsStatus Highs::multiobjectiveSolve() { multi_linear_objective.coefficients[iCol]; } lp.sense_ = ObjSense::kMinimize; + + multi_objective_log = + std::unique_ptr(new std::stringstream()); + *multi_objective_log << highsFormatToString( + "Solving with blended objective"); + if (lp.num_col_ < coeff_logging_size_limit) { + *multi_objective_log << highsFormatToString( + ": %s %g", lp.sense_ == ObjSense::kMinimize ? "min" : "max", + lp.offset_); + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + *multi_objective_log << highsFormatToString( + " + (%g) x[%d]", lp.col_cost_[iCol], int(iCol)); + } + *multi_objective_log << "\n"; + highsLogUser(options_.log_options, HighsLogType::kInfo, "%s", + multi_objective_log->str().c_str()); return this->solve(); } @@ -3761,6 +3810,21 @@ HighsStatus Highs::multiobjectiveSolve() { highsBoolToString(feasible).c_str()); } } + multi_objective_log = + std::unique_ptr(new std::stringstream()); + *multi_objective_log << highsFormatToString("Solving with objective %d", + int(iObj)); + if (lp.num_col_ < coeff_logging_size_limit) { + *multi_objective_log << highsFormatToString( + ": %s %g", lp.sense_ == ObjSense::kMinimize ? "min" : "max", + lp.offset_); + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + *multi_objective_log << highsFormatToString( + " + (%g) x[%d]", lp.col_cost_[iCol], int(iCol)); + } + *multi_objective_log << "\n"; + highsLogUser(options_.log_options, HighsLogType::kInfo, "%s", + multi_objective_log->str().c_str()); HighsStatus solve_status = this->solve(); if (solve_status == HighsStatus::kError) return returnFromLexicographicOptimization(HighsStatus::kError, @@ -3772,6 +3836,7 @@ HighsStatus Highs::multiobjectiveSolve() { return returnFromLexicographicOptimization(HighsStatus::kWarning, original_lp_num_row); } + this->writeSolution("", kSolutionStylePretty); if (iIx == num_linear_objective - 1) break; if (lp.isMip()) { // Save the solution to provide an integer feasible solution of @@ -3840,6 +3905,22 @@ HighsStatus Highs::multiobjectiveSolve() { " and relative tolerance being %g < 0\n", int(priority), linear_objective.abs_tolerance, linear_objective.rel_tolerance); + multi_objective_log = + std::unique_ptr(new std::stringstream()); + *multi_objective_log << highsFormatToString( + "Add constraint for objective %d: ", int(iObj)); + if (nnz < coeff_logging_size_limit) { + *multi_objective_log << highsFormatToString("%g <= ", lower_bound); + for (HighsInt iEl = 0; iEl < nnz; iEl++) + *multi_objective_log << highsFormatToString( + "%s(%g) x[%d]", iEl > 0 ? " + " : "", value[iEl], int(index[iEl])); + *multi_objective_log << highsFormatToString(" <= %g\n", upper_bound); + } else { + *multi_objective_log << highsFormatToString("Bounds [%g, %g]\n", + lower_bound, upper_bound); + } + highsLogUser(options_.log_options, HighsLogType::kInfo, "%s", + multi_objective_log->str().c_str()); add_row_status = this->addRow(lower_bound, upper_bound, nnz, index.data(), value.data()); assert(add_row_status == HighsStatus::kOk);