diff --git a/check/TestMultiObjective.cpp b/check/TestMultiObjective.cpp index 2ade329a7d..6039026bc1 100644 --- a/check/TestMultiObjective.cpp +++ b/check/TestMultiObjective.cpp @@ -1,7 +1,7 @@ #include "Highs.h" #include "catch.hpp" -const bool dev_run = true; +const bool dev_run = false; bool smallDoubleDifference(double v0, double v1) { return std::fabs(v0 - v1) < 1e-12; @@ -27,18 +27,17 @@ TEST_CASE("multi-objective", "[util]") { std::vector linear_objectives; // Begin with an illegal linear objective - printf("\nPass illegal linear objective\n"); + if (dev_run) printf("\nPass illegal linear objective\n"); linear_objective.weight = -1; linear_objective.offset = -1; linear_objective.coefficients = {2, 1, 0}; linear_objective.abs_tolerance = 0.0; linear_objective.rel_tolerance = 1.0; - linear_objective.priority = 10; REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kError); // Now legalise the linear objective so LP has nonunique optimal // solutions on the line joining (2, 6) and (5, 3) - printf("\nPass legal linear objective\n"); + if (dev_run) printf("\nPass legal linear objective\n"); linear_objective.coefficients = {1, 1}; REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kOk); @@ -50,11 +49,10 @@ TEST_CASE("multi-objective", "[util]") { // Add a second linear objective with a very small minimization // weight that should push the optimal solution to (2, 6) - printf("\nPass second linear objective\n"); + if (dev_run) printf("\nPass second linear objective\n"); linear_objective.weight = 1e-4; linear_objective.offset = 0; linear_objective.coefficients = {1, 0}; - linear_objective.priority = 0; REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kOk); REQUIRE(h.run() == HighsStatus::kOk); @@ -63,7 +61,7 @@ TEST_CASE("multi-objective", "[util]") { REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); linear_objectives.push_back(linear_objective); - printf("\nClear and pass two linear objectives\n"); + if (dev_run) printf("\nClear and pass two linear objectives\n"); REQUIRE(h.clearLinearObjectives() == HighsStatus::kOk); REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == HighsStatus::kOk); @@ -72,9 +70,37 @@ TEST_CASE("multi-objective", "[util]") { REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 2)); REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); + // Set illegal priorities - that can be passed OK since + // blend_multi_objectives = true + if (dev_run) + printf( + "\nSetting priorities that will be illegal when using lexicographic " + "optimization\n"); + linear_objectives[0].priority = 0; + linear_objectives[1].priority = 0; + REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == + HighsStatus::kOk); + // Now test lexicographic optimization h.setOptionValue("blend_multi_objectives", false); - printf("\nLexicographic using existing multi objective data\n"); + + if (dev_run) printf("\nLexicographic using illegal priorities\n"); + REQUIRE(h.run() == HighsStatus::kError); + + if (dev_run) + printf( + "\nSetting priorities that are illegal now blend_multi_objectives = " + "false\n"); + REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == + HighsStatus::kError); + + if (dev_run) + printf("\nSetting legal priorities for blend_multi_objectives = false\n"); + linear_objectives[0].priority = 10; + REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == + HighsStatus::kOk); + + if (dev_run) printf("\nLexicographic using existing multi objective data\n"); REQUIRE(h.run() == HighsStatus::kOk); h.writeSolution("", kSolutionStylePretty); REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 2)); diff --git a/src/Highs.h b/src/Highs.h index 0bc6bb0239..5f40efb732 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -160,7 +160,8 @@ class Highs { /** * @brief Add a linear objective for the incumbent model */ - HighsStatus addLinearObjective(const HighsLinearObjective& linear_objective); + HighsStatus addLinearObjective(const HighsLinearObjective& linear_objective, + const HighsInt iObj = -1); /** * @brief Clear the multiple linear objective data @@ -1645,6 +1646,10 @@ class Highs { const bool constraint, const double ill_conditioning_bound); bool infeasibleBoundsOk(); + bool validLinearObjective(const HighsLinearObjective& linear_objective, + const HighsInt iObj) const; + bool hasRepeatedLinearObjectivePriorities( + const HighsLinearObjective* linear_objective = nullptr) const; }; // Start of deprecated methods not in the Highs class diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index ecb06af171..4ef0143b3b 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -584,36 +584,21 @@ HighsStatus Highs::passLinearObjectives( if (num_linear_objective < 0) return HighsStatus::kOk; this->multi_linear_objective_.clear(); for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++) - if (this->addLinearObjective(linear_objective[iObj]) != HighsStatus::kOk) + if (this->addLinearObjective(linear_objective[iObj], iObj) != + HighsStatus::kOk) return HighsStatus::kError; - ; return HighsStatus::kOk; } HighsStatus Highs::addLinearObjective( - const HighsLinearObjective& linear_objective) { - HighsInt linear_objective_coefficients_size = - linear_objective.coefficients.size(); - if (linear_objective_coefficients_size != this->model_.lp_.num_col_) { + const HighsLinearObjective& linear_objective, const HighsInt iObj) { + if (model_.isQp()) { highsLogUser(options_.log_options, HighsLogType::kError, - "Coefficient vector for linear objective has size %d != %d = " - "lp.num_col_\n", - int(linear_objective_coefficients_size), - int(this->model_.lp_.num_col_)); + "Cannot define additional linear objective for QP\n"); return HighsStatus::kError; } - if (linear_objective.abs_tolerance < 0) { - highsLogUser(options_.log_options, HighsLogType::kError, - "Illegal absolute linear objective tolerance of %g < 0\n", - linear_objective.abs_tolerance); - return HighsStatus::kError; - } - if (linear_objective.rel_tolerance < 1) { - highsLogUser(options_.log_options, HighsLogType::kError, - "Illegal relative linear objective tolerance of %g < 1\n", - linear_objective.rel_tolerance); + if (!this->validLinearObjective(linear_objective, iObj)) return HighsStatus::kError; - } this->multi_linear_objective_.push_back(linear_objective); return HighsStatus::kOk; } @@ -929,8 +914,8 @@ bool comparison(std::pair x1, HighsStatus Highs::run() { HighsInt num_linear_objective = this->multi_linear_objective_.size(); - printf("Has %d linear objectives\n", int(num_linear_objective)); if (!this->multi_linear_objective_.size()) return this->solve(); + // Handle multiple linear objectives HighsLp& lp = this->model_.lp_; for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++) { HighsLinearObjective& multi_linear_objective = @@ -964,6 +949,21 @@ HighsStatus Highs::run() { } // Objectives are applied lexicographically + if (model_.isQp() && num_linear_objective > 1) { + // Lexicographic optimization with a single linear objective is + // trivially standard optimization, so is OK + highsLogUser( + options_.log_options, HighsLogType::kError, + "Cannot perform non-trivial lexicographic optimization for QP\n"); + return HighsStatus::kError; + } + // Check whether there are repeated linear objective priorities + if (hasRepeatedLinearObjectivePriorities()) { + highsLogUser( + options_.log_options, HighsLogType::kError, + "Repeated priorities for lexicographic optimization is illegal\n"); + return HighsStatus::kError; + } std::vector> priority_objective; for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++) @@ -978,8 +978,8 @@ HighsStatus Highs::run() { std::vector value(lp.num_col_); for (HighsInt iIx = 0; iIx < num_linear_objective; iIx++) { HighsInt iObj = priority_objective[iIx].second; - printf("\nEntry %d is objective %d with priority %d\n", int(iIx), int(iObj), - int(priority_objective[iIx].first)); + printf("\nHighs::run() Entry %d is objective %d with priority %d\n", + int(iIx), int(iObj), int(priority_objective[iIx].first)); // Use this objective HighsLinearObjective& linear_objective = this->multi_linear_objective_[iObj]; @@ -987,7 +987,7 @@ HighsStatus Highs::run() { lp.col_cost_ = linear_objective.coefficients; lp.sense_ = linear_objective.weight > 0 ? ObjSense::kMinimize : ObjSense::kMaximize; - printf("LP objective function is %s %g ", + printf("Highs::run() LP objective function is %s %g ", lp.sense_ == ObjSense::kMinimize ? "min" : "max", lp.offset_); for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) printf(" + %g x[%d]", lp.col_cost_[iCol], int(iCol)); @@ -4663,7 +4663,46 @@ HighsStatus Highs::returnFromRun(const HighsStatus run_return_status, HighsStatus Highs::returnFromLexicographicOptimization( HighsStatus return_status, HighsInt original_lp_num_row) { + const bool lexicographic_optimization_logging = false; + if (lexicographic_optimization_logging) + printf("\nOn return, model status is %s\n", + this->modelStatusToString(this->model_status_).c_str()); + + // Save model_status_ and info_ since they are cleared by calling + // deleteRows + HighsModelStatus model_status = this->model_status_; + HighsInfo info = this->info_; + if (lexicographic_optimization_logging) + writeInfoToFile(stdout, true, info_.records, HighsFileType::kMinimal); + this->deleteRows(original_lp_num_row, this->model_.lp_.num_row_ - 1); + if (lexicographic_optimization_logging) + printf("\nAfter deleteRows, model status %s\n", + this->modelStatusToString(model_status_).c_str()); + + // Recover model_status_ and info_, and then account for lack of basis or dual + // solution + this->model_status_ = model_status; + this->info_ = info; + info_.objective_function_value = 0; + info_.basis_validity = kBasisValidityInvalid; + info_.dual_solution_status = kSolutionStatusNone; + info_.num_dual_infeasibilities = kHighsIllegalInfeasibilityCount; + info_.max_dual_infeasibility = kHighsIllegalInfeasibilityMeasure; + info_.sum_dual_infeasibilities = kHighsIllegalInfeasibilityMeasure; + info_.max_complementarity_violation = kHighsIllegalComplementarityViolation; + info_.sum_complementarity_violations = kHighsIllegalComplementarityViolation; + this->solution_.value_valid = true; + + if (lexicographic_optimization_logging) { + printf("On return solution is\n"); + for (HighsInt iCol = 0; iCol < this->model_.lp_.num_col_; iCol++) + printf("Col %2d Primal = %11.6g; Dual = %11.6g\n", int(iCol), + solution_.col_value[iCol], solution_.col_value[iCol]); + for (HighsInt iRow = 0; iRow < this->model_.lp_.num_row_; iRow++) + printf("Row %2d Primal = %11.6g; Dual = %11.6g\n", int(iRow), + solution_.row_value[iRow], solution_.row_value[iRow]); + } return return_status; } diff --git a/src/lp_data/HighsInfo.cpp b/src/lp_data/HighsInfo.cpp index ce01a585b8..2792f64550 100644 --- a/src/lp_data/HighsInfo.cpp +++ b/src/lp_data/HighsInfo.cpp @@ -292,7 +292,7 @@ void reportInfo(FILE* file, const InfoRecordInt64& info, fprintf(file, "\n# %s\n# [type: int64_t]\n%s = %" PRId64 "\n", info.description.c_str(), info.name.c_str(), *info.value); } else { - fprintf(file, "%s = %" PRId64 "\n", info.name.c_str(), *info.value); + fprintf(file, "%-30s = %" PRId64 "\n", info.name.c_str(), *info.value); } } @@ -306,7 +306,7 @@ void reportInfo(FILE* file, const InfoRecordInt& info, fprintf(file, "\n# %s\n# [type: HighsInt]\n%s = %" HIGHSINT_FORMAT "\n", info.description.c_str(), info.name.c_str(), *info.value); } else { - fprintf(file, "%s = %" HIGHSINT_FORMAT "\n", info.name.c_str(), + fprintf(file, "%-30s = %" HIGHSINT_FORMAT "\n", info.name.c_str(), *info.value); } } @@ -321,6 +321,6 @@ void reportInfo(FILE* file, const InfoRecordDouble& info, fprintf(file, "\n# %s\n# [type: double]\n%s = %g\n", info.description.c_str(), info.name.c_str(), *info.value); } else { - fprintf(file, "%s = %g\n", info.name.c_str(), *info.value); + fprintf(file, "%-30s = %g\n", info.name.c_str(), *info.value); } } diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 0934129e62..ad37bdb923 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -3585,6 +3585,68 @@ bool Highs::infeasibleBoundsOk() { return num_true_infeasible_bound == 0; } +bool Highs::validLinearObjective(const HighsLinearObjective& linear_objective, + const HighsInt iObj) const { + HighsInt linear_objective_coefficients_size = + linear_objective.coefficients.size(); + if (linear_objective_coefficients_size != this->model_.lp_.num_col_) { + highsLogUser( + options_.log_options, HighsLogType::kError, + "Coefficient vector for linear objective %s has size %d != %d = " + "lp.num_col_\n", + iObj >= 0 ? std::to_string(iObj).c_str() : "", + int(linear_objective_coefficients_size), + int(this->model_.lp_.num_col_)); + return false; + } + if (linear_objective.abs_tolerance < 0) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Linear objective %s has illegal absolute linear objective " + "tolerance of %g < 0\n", + iObj >= 0 ? std::to_string(iObj).c_str() : "", + linear_objective.abs_tolerance); + return false; + } + if (linear_objective.rel_tolerance < 1) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Linear objective %s has illegal relative linear objective " + "tolerance of %g < 1\n", + iObj >= 0 ? std::to_string(iObj).c_str() : "", + linear_objective.rel_tolerance); + return false; + } + if (!options_.blend_multi_objectives && + hasRepeatedLinearObjectivePriorities(&linear_objective)) { + highsLogUser( + options_.log_options, HighsLogType::kError, + "Repeated priorities for lexicographic optimization is illegal\n"); + return false; + } + return true; +} + +bool Highs::hasRepeatedLinearObjectivePriorities( + const HighsLinearObjective* linear_objective) const { + // Look for repeated values in the linear objective priorities, also + // comparing linear_objective if it's not a null pointer. Cost is + // O(n^2), but who will have more than O(1) linear objectives! + HighsInt num_linear_objective = this->multi_linear_objective_.size(); + if (num_linear_objective <= 0 || + num_linear_objective <= 1 && !linear_objective) + return false; + for (HighsInt iObj0 = 0; iObj0 < num_linear_objective; iObj0++) { + HighsInt priority0 = this->multi_linear_objective_[iObj0].priority; + for (HighsInt iObj1 = iObj0 + 1; iObj1 < num_linear_objective; iObj1++) { + HighsInt priority1 = this->multi_linear_objective_[iObj1].priority; + if (priority1 == priority0) return true; + } + if (linear_objective) { + if (linear_objective->priority == priority0) return true; + } + } + return false; +} + void HighsLinearObjective::clear() { this->weight = 0.0; this->offset = 0.0;