Skip to content

Commit

Permalink
Repeated priorities illegal; needs more testing
Browse files Browse the repository at this point in the history
  • Loading branch information
jajhall committed Nov 21, 2024
1 parent 5db015b commit b5aac76
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 37 deletions.
42 changes: 34 additions & 8 deletions check/TestMultiObjective.cpp
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -27,18 +27,17 @@ TEST_CASE("multi-objective", "[util]") {
std::vector<HighsLinearObjective> 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);

Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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));
Expand Down
7 changes: 6 additions & 1 deletion src/Highs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
89 changes: 64 additions & 25 deletions src/lp_data/Highs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -929,8 +914,8 @@ bool comparison(std::pair<HighsInt, HighsInt> 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 =
Expand Down Expand Up @@ -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<std::pair<HighsInt, HighsInt>> priority_objective;

for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++)
Expand All @@ -978,16 +978,16 @@ HighsStatus Highs::run() {
std::vector<double> 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];
lp.offset_ = linear_objective.offset;
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));
Expand Down Expand Up @@ -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;
}

Expand Down
6 changes: 3 additions & 3 deletions src/lp_data/HighsInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand All @@ -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);
}
}
Expand All @@ -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);
}
}
62 changes: 62 additions & 0 deletions src/lp_data/HighsInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit b5aac76

Please sign in to comment.