From b3d1b61362ac014533ddc1c91745e53adeb2e30c Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 20 Nov 2024 11:36:24 +0000 Subject: [PATCH 01/21] Added HighsLinearObjective struct and vector of such as member of Highs class; added blend_multi_objectives option --- src/Highs.h | 3 ++- src/lp_data/HStruct.h | 10 ++++++++++ src/lp_data/Highs.cpp | 1 + src/lp_data/HighsInterface.cpp | 9 +++++++++ src/lp_data/HighsOptions.h | 9 +++++++++ 5 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/Highs.h b/src/Highs.h index 34d1044f5f..c7711a896b 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -1389,6 +1389,8 @@ class Highs { ICrashInfo icrash_info_; HighsModel model_; + std::vector multi_linear_objective_; + HighsModel presolved_model_; HighsTimer timer_; @@ -1397,7 +1399,6 @@ class Highs { HighsInfo info_; HighsRanging ranging_; HighsIis iis_; - std::vector saved_objective_and_solution_; HighsPresolveStatus model_presolve_status_ = diff --git a/src/lp_data/HStruct.h b/src/lp_data/HStruct.h index e54657d406..3963d9df1c 100644 --- a/src/lp_data/HStruct.h +++ b/src/lp_data/HStruct.h @@ -144,4 +144,14 @@ struct HighsIllConditioning { void clear(); }; +struct HighsLinearObjective { + double weight; + double offset; + std::vector coefficients; + double abs_tolerance; + double rel_tolerance; + HighsInt priority; + void clear(); +}; + #endif /* LP_DATA_HSTRUCT_H_ */ diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index 67c6d44e33..3e340907d9 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -59,6 +59,7 @@ HighsStatus Highs::clear() { HighsStatus Highs::clearModel() { model_.clear(); + multi_linear_objective_.clear(); return clearSolver(); } diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index a40813d1fe..0934129e62 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -3584,3 +3584,12 @@ bool Highs::infeasibleBoundsOk() { int(num_true_infeasible_bound)); return num_true_infeasible_bound == 0; } + +void HighsLinearObjective::clear() { + this->weight = 0.0; + this->offset = 0.0; + this->coefficients.clear(); + this->abs_tolerance = 0.0; + this->rel_tolerance = 0.0; + this->priority = 0; +} diff --git a/src/lp_data/HighsOptions.h b/src/lp_data/HighsOptions.h index cfb395a3fc..38df4e7617 100644 --- a/src/lp_data/HighsOptions.h +++ b/src/lp_data/HighsOptions.h @@ -348,6 +348,9 @@ struct HighsOptionsStruct { // Options for IIS calculation HighsInt iis_strategy; + // Option for multi-objective optimization + bool blend_multi_objectives; + // Advanced options HighsInt log_dev_level; bool log_githash; @@ -1120,6 +1123,12 @@ class HighsOptions : public HighsOptionsStruct { kIisStrategyMax); records.push_back(record_int); + record_bool = new OptionRecordBool( + "blend_multi_objectives", + "Blend multiple objectives or apply lexicographically: Default = true", advanced, + &blend_multi_objectives, true); + records.push_back(record_bool); + // Fix the number of user settable options num_user_settable_options_ = static_cast(records.size()); From 4fa6d2f5eb5a6a44ee6519863ddf8cca9db9a3bc Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 20 Nov 2024 11:40:42 +0000 Subject: [PATCH 02/21] Renamed Highs::run() to Highs::solve(), and created Highs::run() that currently just returns Highs::solve() --- src/Highs.h | 7 ++++++- src/lp_data/Highs.cpp | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Highs.h b/src/Highs.h index c7711a896b..4a21110273 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -183,10 +183,15 @@ class Highs { HighsStatus presolve(); /** - * @brief Solve the incumbent model according to the specified options + * @brief Run the solver, accounting for any multiple objective */ HighsStatus run(); + /** + * @brief Solve the incumbent model according to the specified options + */ + HighsStatus solve(); + /** * @brief Postsolve the incumbent model using a solution */ diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index 3e340907d9..e147eb85c4 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -877,9 +877,13 @@ HighsStatus Highs::presolve() { return returnFromHighs(return_status); } +HighsStatus Highs::run() { + return this->solve(); +} + // Checks the options calls presolve and postsolve if needed. Solvers are called // with callSolveLp(..) -HighsStatus Highs::run() { +HighsStatus Highs::solve() { HighsInt min_highs_debug_level = kHighsDebugLevelMin; // kHighsDebugLevelCostly; // kHighsDebugLevelMax; From 615663c85acf37f7bad21eaa1e9e5278598e6abe Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 20 Nov 2024 11:41:41 +0000 Subject: [PATCH 03/21] Formatted --- src/lp_data/Highs.cpp | 4 +--- src/lp_data/HighsOptions.h | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index e147eb85c4..b7c3881a0e 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -877,9 +877,7 @@ HighsStatus Highs::presolve() { return returnFromHighs(return_status); } -HighsStatus Highs::run() { - return this->solve(); -} +HighsStatus Highs::run() { return this->solve(); } // Checks the options calls presolve and postsolve if needed. Solvers are called // with callSolveLp(..) diff --git a/src/lp_data/HighsOptions.h b/src/lp_data/HighsOptions.h index 38df4e7617..abbf7624ae 100644 --- a/src/lp_data/HighsOptions.h +++ b/src/lp_data/HighsOptions.h @@ -1125,8 +1125,8 @@ class HighsOptions : public HighsOptionsStruct { record_bool = new OptionRecordBool( "blend_multi_objectives", - "Blend multiple objectives or apply lexicographically: Default = true", advanced, - &blend_multi_objectives, true); + "Blend multiple objectives or apply lexicographically: Default = true", + advanced, &blend_multi_objectives, true); records.push_back(record_bool); // Fix the number of user settable options From e063490148e31e9ed906dc3b8af7f5a0bb01b197 Mon Sep 17 00:00:00 2001 From: Julian Hall Date: Wed, 20 Nov 2024 17:34:00 +0000 Subject: [PATCH 04/21] Created Highs::passLinearObjectives Highs::addLinearObjective Highs::clearLinearObjectives, and unit test in TestMultipleObjective.cpp --- check/CMakeLists.txt | 2 + check/TestHighsIntegers.cpp | 108 +----------------------------------- src/Highs.h | 24 ++++++-- src/lp_data/Highs.cpp | 59 +++++++++++++++++++- 4 files changed, 81 insertions(+), 112 deletions(-) diff --git a/check/CMakeLists.txt b/check/CMakeLists.txt index f8bb62d133..700b5eee36 100644 --- a/check/CMakeLists.txt +++ b/check/CMakeLists.txt @@ -48,6 +48,7 @@ if ((NOT FAST_BUILD OR ALL_TESTS) AND NOT (BUILD_EXTRA_UNIT_ONLY)) TestBasis.cpp TestBasisSolves.cpp TestCrossover.cpp + TestHighsCDouble.cpp TestHighsHash.cpp TestHighsIntegers.cpp TestHighsParallel.cpp @@ -66,6 +67,7 @@ if ((NOT FAST_BUILD OR ALL_TESTS) AND NOT (BUILD_EXTRA_UNIT_ONLY)) TestLpModification.cpp TestLpOrientation.cpp TestModelProperties.cpp + TestMultiObjective.cpp TestPdlp.cpp TestPresolve.cpp TestQpSolver.cpp diff --git a/check/TestHighsIntegers.cpp b/check/TestHighsIntegers.cpp index b56ec69908..c08b288610 100644 --- a/check/TestHighsIntegers.cpp +++ b/check/TestHighsIntegers.cpp @@ -1,8 +1,8 @@ #include "HCheckConfig.h" #include "catch.hpp" -#include "util/HighsCDouble.h" +// #include "util/HighsCDouble.h" #include "util/HighsIntegers.h" -#include "util/HighsRandom.h" +// #include "util/HighsRandom.h" const bool dev_run = false; @@ -40,107 +40,3 @@ TEST_CASE("HighsIntegers", "[util]") { if (dev_run) printf("integral scalar is %g\n", integralscalar); } - -void testCeil(HighsCDouble x) { - double ceil_x; - double double_x; - ceil_x = double(ceil(x)); - double_x = double(x); - REQUIRE(ceil_x >= double_x); - REQUIRE(ceil(x) >= x); -} - -void testFloor(HighsCDouble x) { - double floor_x; - double double_x; - floor_x = double(floor(x)); - double_x = double(x); - REQUIRE(floor_x <= double_x); - REQUIRE(floor(x) <= x); -} -TEST_CASE("HighsCDouble-ceil", "[util]") { - HighsCDouble x; - x = -1e-34; - testCeil(x); - x = -1e-32; - testCeil(x); - x = -1e-30; - testCeil(x); - x = -1e-23; - testCeil(x); - x = -1e-12; - testCeil(x); - x = -1e-1; - testCeil(x); - x = -0.99; - testCeil(x); - - x = 0.99; - testCeil(x); - x = 1e-1; - testCeil(x); - x = 1e-12; - testCeil(x); - // This and rest failed in #2041 - x = 1e-23; - testCeil(x); - x = 1e-30; - testCeil(x); - x = 1e-32; - testCeil(x); - x = 1e-34; - testCeil(x); - - HighsRandom rand; - for (HighsInt k = 0; k < 1000; k++) { - double man = rand.fraction(); - HighsInt power = 2 - rand.integer(5); - double exp = std::pow(10, power); - x = man * exp; - testCeil(x); - } -} - -TEST_CASE("HighsCDouble-floor", "[util]") { - HighsCDouble x; - - x = 1e-34; - testFloor(x); - x = 1e-32; - testFloor(x); - x = 1e-30; - testFloor(x); - x = 1e-23; - testFloor(x); - x = 1e-12; - testFloor(x); - x = 1e-1; - testFloor(x); - x = 0.99; - testFloor(x); - - x = -0.99; - testFloor(x); - x = -1e-1; - testFloor(x); - x = -1e-12; - testFloor(x); - // This and rest failed in #2041 - x = -1e-23; - testFloor(x); - x = -1e-30; - testFloor(x); - x = -1e-32; - testFloor(x); - x = -1e-34; - testFloor(x); - - HighsRandom rand; - for (HighsInt k = 0; k < 1000; k++) { - double man = rand.fraction(); - HighsInt power = 2 - rand.integer(5); - double exp = std::pow(10, power); - x = man * exp; - testFloor(x); - } -} diff --git a/src/Highs.h b/src/Highs.h index 4a21110273..12fb05b3f0 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -150,6 +150,23 @@ class Highs { HighsStatus passHessian(const HighsInt dim, const HighsInt num_nz, const HighsInt format, const HighsInt* start, const HighsInt* index, const double* value); + /** + * @brief Pass multiple linear objectives for the incumbent model + */ + HighsStatus passLinearObjectives( + const HighsInt num_linear_objective, + const HighsLinearObjective* linear_objective); + + /** + * @brief Add a linear objective for the incumbent model + */ + HighsStatus addLinearObjective(const HighsLinearObjective& linear_objective); + + /** + * @brief Clear the multiple linear objective data + */ + HighsStatus clearLinearObjectives(); + /** * @brief Pass a column name to the incumbent model */ @@ -187,11 +204,6 @@ class Highs { */ HighsStatus run(); - /** - * @brief Solve the incumbent model according to the specified options - */ - HighsStatus solve(); - /** * @brief Postsolve the incumbent model using a solution */ @@ -1430,6 +1442,8 @@ class Highs { bool written_log_header = false; + HighsStatus solve(); + void exactResizeModel() { this->model_.lp_.exactResize(); this->model_.hessian_.exactResize(); diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index b7c3881a0e..cd4f4b0e59 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -578,6 +578,39 @@ HighsStatus Highs::passHessian(const HighsInt dim, const HighsInt num_nz, return passHessian(hessian); } +HighsStatus Highs::passLinearObjectives( + const HighsInt num_linear_objective, + const HighsLinearObjective* linear_objective) { + 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) + 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_) { + 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_)); + return HighsStatus::kError; + } + this->multi_linear_objective_.push_back(linear_objective); + return HighsStatus::kOk; +} + +HighsStatus Highs::clearLinearObjectives() { + this->multi_linear_objective_.clear(); + return HighsStatus::kOk; +} + HighsStatus Highs::passColName(const HighsInt col, const std::string& name) { const HighsInt num_col = this->model_.lp_.num_col_; if (col < 0 || col >= num_col) { @@ -877,7 +910,31 @@ HighsStatus Highs::presolve() { return returnFromHighs(return_status); } -HighsStatus Highs::run() { return this->solve(); } +HighsStatus Highs::run() { + HighsInt num_multi_linear_objective = this->multi_linear_objective_.size(); + printf("Has %d multiple linear objectives\n", + int(num_multi_linear_objective)); + if (!this->multi_linear_objective_.size()) return this->solve(); + HighsLp& lp = this->model_.lp_; + HighsLinearObjective& multi_linear_objective = + this->multi_linear_objective_[0]; + if (multi_linear_objective.coefficients.size() != + static_cast(lp.num_col_)) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Multiple linear objective coefficient vector %d has size " + "incompatible with model\n", + int(0)); + return HighsStatus::kError; + } + this->clearSolver(); + // Objective is multiplied by the weight and minimized + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + lp.col_cost_[iCol] = multi_linear_objective.weight * + multi_linear_objective.coefficients[iCol]; + lp.offset_ = multi_linear_objective.weight * multi_linear_objective.offset; + lp.sense_ = ObjSense::kMinimize; + return this->solve(); +} // Checks the options calls presolve and postsolve if needed. Solvers are called // with callSolveLp(..) From ac831eca476c9229b35962107f0fef9c61a4ecd2 Mon Sep 17 00:00:00 2001 From: Julian Hall Date: Wed, 20 Nov 2024 17:34:40 +0000 Subject: [PATCH 05/21] Added check/TestMultiObjective.cpp and check/TestHighsCDouble.cpp --- check/TestHighsCDouble.cpp | 109 +++++++++++++++++++++++++++++++++++ check/TestMultiObjective.cpp | 56 ++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 check/TestHighsCDouble.cpp create mode 100644 check/TestMultiObjective.cpp diff --git a/check/TestHighsCDouble.cpp b/check/TestHighsCDouble.cpp new file mode 100644 index 0000000000..2f17a40598 --- /dev/null +++ b/check/TestHighsCDouble.cpp @@ -0,0 +1,109 @@ +#include "HCheckConfig.h" +#include "catch.hpp" +#include "util/HighsCDouble.h" +#include "util/HighsRandom.h" + +void testCeil(HighsCDouble x) { + double ceil_x; + double double_x; + ceil_x = double(ceil(x)); + double_x = double(x); + REQUIRE(ceil_x >= double_x); + REQUIRE(ceil(x) >= x); +} + +void testFloor(HighsCDouble x) { + double floor_x; + double double_x; + floor_x = double(floor(x)); + double_x = double(x); + REQUIRE(floor_x <= double_x); + REQUIRE(floor(x) <= x); +} + +TEST_CASE("HighsCDouble-ceil", "[util]") { + HighsCDouble x; + x = -1e-34; + testCeil(x); + x = -1e-32; + testCeil(x); + x = -1e-30; + testCeil(x); + x = -1e-23; + testCeil(x); + x = -1e-12; + testCeil(x); + x = -1e-1; + testCeil(x); + x = -0.99; + testCeil(x); + + x = 0.99; + testCeil(x); + x = 1e-1; + testCeil(x); + x = 1e-12; + testCeil(x); + // This and rest failed in #2041 + x = 1e-23; + testCeil(x); + x = 1e-30; + testCeil(x); + x = 1e-32; + testCeil(x); + x = 1e-34; + testCeil(x); + + HighsRandom rand; + for (HighsInt k = 0; k < 1000; k++) { + double man = rand.fraction(); + HighsInt power = 2 - rand.integer(5); + double exp = std::pow(10, power); + x = man * exp; + testCeil(x); + } +} + +TEST_CASE("HighsCDouble-floor", "[util]") { + HighsCDouble x; + + x = 1e-34; + testFloor(x); + x = 1e-32; + testFloor(x); + x = 1e-30; + testFloor(x); + x = 1e-23; + testFloor(x); + x = 1e-12; + testFloor(x); + x = 1e-1; + testFloor(x); + x = 0.99; + testFloor(x); + + x = -0.99; + testFloor(x); + x = -1e-1; + testFloor(x); + x = -1e-12; + testFloor(x); + // This and rest failed in #2041 + x = -1e-23; + testFloor(x); + x = -1e-30; + testFloor(x); + x = -1e-32; + testFloor(x); + x = -1e-34; + testFloor(x); + + HighsRandom rand; + for (HighsInt k = 0; k < 1000; k++) { + double man = rand.fraction(); + HighsInt power = 2 - rand.integer(5); + double exp = std::pow(10, power); + x = man * exp; + testFloor(x); + } +} diff --git a/check/TestMultiObjective.cpp b/check/TestMultiObjective.cpp new file mode 100644 index 0000000000..013b8d3a8d --- /dev/null +++ b/check/TestMultiObjective.cpp @@ -0,0 +1,56 @@ +#include "Highs.h" +#include "catch.hpp" + +const bool dev_run = true; + +TEST_CASE("multi-objective", "[util]") { + HighsLp lp; + lp.num_col_ = 2; + lp.num_row_ = 3; + lp.col_cost_ = {0, 0}; + lp.col_lower_ = {0, 0}; + lp.col_upper_ = {kHighsInf, kHighsInf}; + lp.row_lower_ = {-kHighsInf, -kHighsInf, -kHighsInf}; + lp.row_upper_ = {18, 8, 14}; + lp.a_matrix_.start_ = {0, 3, 6}; + lp.a_matrix_.index_ = {0, 1, 2, 0, 1, 2}; + lp.a_matrix_.value_ = {3, 1, 1, 1, 1, 2}; + Highs h; + h.setOptionValue("output_flag", dev_run); + h.passModel(lp); + + HighsLinearObjective linear_objective; + std::vector linear_objectives; + + // Begin with an illegal linear objective + 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 = 0; + 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) + linear_objective.coefficients = {1, 1}; + REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kOk); + + h.run(); + h.writeSolution("", kSolutionStylePretty); + REQUIRE(h.getInfo().objective_function_value == -7); + // Save the linear objective for the next + linear_objectives.push_back(linear_objective); + + // Add a second linear objective with a very small minimization + // weight that should push the optimal solution to (2, 6) + linear_objective.weight = 1e-4; + linear_objective.offset = 0; + linear_objective.coefficients = {-1, 0}; + REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kOk); + + h.run(); + h.writeSolution("", kSolutionStylePretty); + // REQUIRE(h.getSolution().col_value[0] == 2); + // REQUIRE(h.getSolution().col_value[1] == 6); +} From 5db015b407c66b81d6f64295a205beda4197539b Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 21 Nov 2024 14:17:49 +0000 Subject: [PATCH 06/21] Need to define solution, info and model status after lexicographic optimization, and prevent equal priorities --- check/TestMultiObjective.cpp | 40 +++++++-- src/Highs.h | 2 + src/lp_data/Highs.cpp | 169 +++++++++++++++++++++++++++++++---- 3 files changed, 185 insertions(+), 26 deletions(-) diff --git a/check/TestMultiObjective.cpp b/check/TestMultiObjective.cpp index 013b8d3a8d..2ade329a7d 100644 --- a/check/TestMultiObjective.cpp +++ b/check/TestMultiObjective.cpp @@ -3,6 +3,10 @@ const bool dev_run = true; +bool smallDoubleDifference(double v0, double v1) { + return std::fabs(v0 - v1) < 1e-12; +} + TEST_CASE("multi-objective", "[util]") { HighsLp lp; lp.num_col_ = 2; @@ -23,34 +27,56 @@ TEST_CASE("multi-objective", "[util]") { std::vector linear_objectives; // Begin with an illegal linear objective + 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 = 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"); linear_objective.coefficients = {1, 1}; REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kOk); - h.run(); + REQUIRE(h.run() == HighsStatus::kOk); h.writeSolution("", kSolutionStylePretty); - REQUIRE(h.getInfo().objective_function_value == -7); + REQUIRE(smallDoubleDifference(h.getInfo().objective_function_value, -7)); // Save the linear objective for the next linear_objectives.push_back(linear_objective); // 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"); linear_objective.weight = 1e-4; linear_objective.offset = 0; - linear_objective.coefficients = {-1, 0}; + linear_objective.coefficients = {1, 0}; + linear_objective.priority = 0; REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kOk); - h.run(); + REQUIRE(h.run() == HighsStatus::kOk); + h.writeSolution("", kSolutionStylePretty); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 2)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); + linear_objectives.push_back(linear_objective); + + printf("\nClear and pass two linear objectives\n"); + REQUIRE(h.clearLinearObjectives() == HighsStatus::kOk); + REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == + HighsStatus::kOk); + REQUIRE(h.run() == HighsStatus::kOk); + h.writeSolution("", kSolutionStylePretty); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 2)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); + + // Now test lexicographic optimization + h.setOptionValue("blend_multi_objectives", false); + printf("\nLexicographic using existing multi objective data\n"); + REQUIRE(h.run() == HighsStatus::kOk); h.writeSolution("", kSolutionStylePretty); - // REQUIRE(h.getSolution().col_value[0] == 2); - // REQUIRE(h.getSolution().col_value[1] == 6); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 2)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); } diff --git a/src/Highs.h b/src/Highs.h index 12fb05b3f0..0bc6bb0239 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -1523,6 +1523,8 @@ 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 diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index cd4f4b0e59..ecb06af171 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -602,6 +602,18 @@ HighsStatus Highs::addLinearObjective( int(this->model_.lp_.num_col_)); 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); + return HighsStatus::kError; + } this->multi_linear_objective_.push_back(linear_objective); return HighsStatus::kOk; } @@ -910,30 +922,143 @@ HighsStatus Highs::presolve() { return returnFromHighs(return_status); } +bool comparison(std::pair x1, + std::pair x2) { + return x1.first >= x2.first; +} + HighsStatus Highs::run() { - HighsInt num_multi_linear_objective = this->multi_linear_objective_.size(); - printf("Has %d multiple linear objectives\n", - int(num_multi_linear_objective)); + 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(); HighsLp& lp = this->model_.lp_; - HighsLinearObjective& multi_linear_objective = - this->multi_linear_objective_[0]; - if (multi_linear_objective.coefficients.size() != - static_cast(lp.num_col_)) { - highsLogUser(options_.log_options, HighsLogType::kError, - "Multiple linear objective coefficient vector %d has size " - "incompatible with model\n", - int(0)); - return HighsStatus::kError; + for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++) { + HighsLinearObjective& multi_linear_objective = + this->multi_linear_objective_[iObj]; + if (multi_linear_objective.coefficients.size() != + static_cast(lp.num_col_)) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Multiple linear objective coefficient vector %d has size " + "incompatible with model\n", + int(iObj)); + return HighsStatus::kError; + } } + this->clearSolver(); - // Objective is multiplied by the weight and minimized - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) - lp.col_cost_[iCol] = multi_linear_objective.weight * - multi_linear_objective.coefficients[iCol]; - lp.offset_ = multi_linear_objective.weight * multi_linear_objective.offset; - lp.sense_ = ObjSense::kMinimize; - return this->solve(); + if (this->options_.blend_multi_objectives) { + // Objectives are blended by weight and minimized + lp.offset_ = 0; + lp.col_cost_.assign(lp.num_col_, 0); + for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++) { + HighsLinearObjective& multi_linear_objective = + this->multi_linear_objective_[iObj]; + lp.offset_ += + multi_linear_objective.weight * multi_linear_objective.offset; + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + lp.col_cost_[iCol] += multi_linear_objective.weight * + multi_linear_objective.coefficients[iCol]; + } + lp.sense_ = ObjSense::kMinimize; + return this->solve(); + } + + // Objectives are applied lexicographically + std::vector> priority_objective; + + for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++) + priority_objective.push_back( + std::make_pair(this->multi_linear_objective_[iObj].priority, iObj)); + std::sort(priority_objective.begin(), priority_objective.end(), comparison); + // Clear LP objective + lp.offset_ = 0; + lp.col_cost_.assign(lp.num_col_, 0); + const HighsInt original_lp_num_row = lp.num_row_; + std::vector index(lp.num_col_); + 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)); + // 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 ", + 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)); + printf("\n"); + HighsStatus solve_status = this->solve(); + if (solve_status == HighsStatus::kError) + return returnFromLexicographicOptimization(HighsStatus::kError, + original_lp_num_row); + if (model_status_ != HighsModelStatus::kOptimal) { + highsLogUser(options_.log_options, HighsLogType::kWarning, + "After priority %d solve, model status is %s\n", int(), + modelStatusToString(model_status_).c_str()); + return returnFromLexicographicOptimization(HighsStatus::kWarning, + original_lp_num_row); + } + this->writeSolution("", kSolutionStylePretty); + if (iIx == num_linear_objective - 1) break; + // Add the constraint + HighsInt nnz = 0; + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + if (lp.col_cost_[iCol]) { + index[nnz] = iCol; + value[nnz] = lp.col_cost_[iCol]; + nnz++; + } + } + double objective = info_.objective_function_value; + HighsStatus add_row_status; + if (lp.sense_ == ObjSense::kMinimize) { + // Minimizing, so set a greater upper bound than the objective + double upper_bound = objective + linear_objective.abs_tolerance; + if (objective >= 0) { + // Guarantees objective of at least t.f^* + // + // so (t.f^*-f^*)/f^* = t-1 + upper_bound = + std::min(objective * linear_objective.rel_tolerance, upper_bound); + } else if (objective < 0) { + // Guarantees objective of at least (2-t).f^* + // + // so ((2-t).f^*-f^*)/f^* = 1-t + upper_bound = std::min( + objective * (2.0 - linear_objective.rel_tolerance), upper_bound); + } + upper_bound -= lp.offset_; + add_row_status = this->addRow(-kHighsInf, upper_bound, nnz, index.data(), + value.data()); + } else { + // Maximizing, so set a lesser lower bound than the objective + double lower_bound = objective - linear_objective.abs_tolerance; + if (objective >= 0) { + // Guarantees objective of at most (2-t).f^* + // + // so ((2-t).f^*-f^*)/f^* = 1-t + lower_bound = std::max( + objective * (2.0 - linear_objective.rel_tolerance), lower_bound); + } else if (objective < 0) { + // Guarantees objective of at least t.f^* + // + // so (t.f^*-f^*)/f^* = t-1 + lower_bound = + std::max(objective * linear_objective.rel_tolerance, lower_bound); + } + lower_bound -= lp.offset_; + add_row_status = + this->addRow(lower_bound, kHighsInf, nnz, index.data(), value.data()); + } + assert(add_row_status == HighsStatus::kOk); + } + return returnFromLexicographicOptimization(HighsStatus::kOk, + original_lp_num_row); } // Checks the options calls presolve and postsolve if needed. Solvers are called @@ -4536,6 +4661,12 @@ HighsStatus Highs::returnFromRun(const HighsStatus run_return_status, return returnFromHighs(return_status); } +HighsStatus Highs::returnFromLexicographicOptimization( + HighsStatus return_status, HighsInt original_lp_num_row) { + this->deleteRows(original_lp_num_row, this->model_.lp_.num_row_ - 1); + return return_status; +} + HighsStatus Highs::returnFromHighs(HighsStatus highs_return_status) { // Applies checks before returning from HiGHS HighsStatus return_status = highs_return_status; From b5aac76597d9dae3a466348a7b190780618ad6bc Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 21 Nov 2024 17:37:35 +0000 Subject: [PATCH 07/21] Repeated priorities illegal; needs more testing --- check/TestMultiObjective.cpp | 42 +++++++++++++--- src/Highs.h | 7 ++- src/lp_data/Highs.cpp | 89 ++++++++++++++++++++++++---------- src/lp_data/HighsInfo.cpp | 6 +-- src/lp_data/HighsInterface.cpp | 62 +++++++++++++++++++++++ 5 files changed, 169 insertions(+), 37 deletions(-) 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; From 9f95d387ea6181ec03de6b3d76b9760c29c70292 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 21 Nov 2024 23:22:33 +0000 Subject: [PATCH 08/21] More unit tests of multi-objective optimization --- check/TestMultiObjective.cpp | 62 +++++++++- src/Highs.h | 2 + src/lp_data/Highs.cpp | 210 ++++++--------------------------- src/lp_data/HighsInterface.cpp | 165 ++++++++++++++++++++++++++ 4 files changed, 261 insertions(+), 178 deletions(-) diff --git a/check/TestMultiObjective.cpp b/check/TestMultiObjective.cpp index 6039026bc1..4cd0b729aa 100644 --- a/check/TestMultiObjective.cpp +++ b/check/TestMultiObjective.cpp @@ -4,7 +4,9 @@ const bool dev_run = false; bool smallDoubleDifference(double v0, double v1) { - return std::fabs(v0 - v1) < 1e-12; + double difference = std::fabs(v0 - v1); + // printf("smallDoubleDifference = %g\n", difference); + return difference < 1e-4; } TEST_CASE("multi-objective", "[util]") { @@ -105,4 +107,62 @@ TEST_CASE("multi-objective", "[util]") { h.writeSolution("", kSolutionStylePretty); REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 2)); REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); + + // Back to blending + h.setOptionValue("blend_multi_objectives", true); + // h.setOptionValue("output_flag", true); + REQUIRE(h.clearLinearObjectives() == HighsStatus::kOk); + linear_objectives[0].coefficients = {1.0001, 1}; + linear_objectives[0].abs_tolerance = -1e-5; + linear_objectives[0].rel_tolerance = 0.95; + + if (dev_run) printf("\nBlending: Illegal abs_tolerance \n"); + REQUIRE(h.passLinearObjectives(1, linear_objectives.data()) == + HighsStatus::kError); + linear_objectives[0].abs_tolerance = 1e-5; + + if (dev_run) printf("\nBlending: Illegal rel_tolerance \n"); + REQUIRE(h.passLinearObjectives(1, linear_objectives.data()) == + HighsStatus::kError); + linear_objectives[0].rel_tolerance = 1.05; + + linear_objectives[1].weight = 1e-3; + if (dev_run) + printf( + "\nBlending: first solve objective just giving unique optimal " + "solution\n"); + REQUIRE(h.passLinearObjectives(1, linear_objectives.data()) == + HighsStatus::kOk); + + REQUIRE(h.run() == HighsStatus::kOk); + h.writeSolution("", kSolutionStylePretty); + + REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == + HighsStatus::kOk); + + REQUIRE(h.run() == HighsStatus::kOk); + h.writeSolution("", kSolutionStylePretty); + + // Back to lexicographic optimization + h.setOptionValue("blend_multi_objectives", false); + + if (dev_run) printf("\nLexicographic using non-trivial tolerances\n"); + REQUIRE(h.run() == HighsStatus::kOk); + h.writeSolution("", kSolutionStylePretty); + + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 4.9)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 3.1)); + + linear_objectives[0].abs_tolerance = kHighsInf; + + REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == + HighsStatus::kOk); + + REQUIRE(h.run() == HighsStatus::kOk); + h.writeSolution("", kSolutionStylePretty); + + // printf("Solution = [%23.18g, %23.18g]\n", h.getSolution().col_value[0], + // h.getSolution().col_value[1]); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 1.30069)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6.34966)); } diff --git a/src/Highs.h b/src/Highs.h index 5f40efb732..bd7090f826 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -1623,6 +1623,8 @@ class Highs { HighsInt* iis_col_index, HighsInt* iis_row_index, HighsInt* iis_col_bound, HighsInt* iis_row_bound); + HighsStatus multiobjectiveSolve(); + bool aFormatOk(const HighsInt num_nz, const HighsInt format); bool qFormatOk(const HighsInt num_nz, const HighsInt format); void clearZeroHessian(); diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index 4ef0143b3b..9a3bd54c06 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -907,158 +907,10 @@ HighsStatus Highs::presolve() { return returnFromHighs(return_status); } -bool comparison(std::pair x1, - std::pair x2) { - return x1.first >= x2.first; -} - HighsStatus Highs::run() { HighsInt num_linear_objective = this->multi_linear_objective_.size(); - 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 = - this->multi_linear_objective_[iObj]; - if (multi_linear_objective.coefficients.size() != - static_cast(lp.num_col_)) { - highsLogUser(options_.log_options, HighsLogType::kError, - "Multiple linear objective coefficient vector %d has size " - "incompatible with model\n", - int(iObj)); - return HighsStatus::kError; - } - } - - this->clearSolver(); - if (this->options_.blend_multi_objectives) { - // Objectives are blended by weight and minimized - lp.offset_ = 0; - lp.col_cost_.assign(lp.num_col_, 0); - for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++) { - HighsLinearObjective& multi_linear_objective = - this->multi_linear_objective_[iObj]; - lp.offset_ += - multi_linear_objective.weight * multi_linear_objective.offset; - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) - lp.col_cost_[iCol] += multi_linear_objective.weight * - multi_linear_objective.coefficients[iCol]; - } - lp.sense_ = ObjSense::kMinimize; - return this->solve(); - } - - // 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++) - priority_objective.push_back( - std::make_pair(this->multi_linear_objective_[iObj].priority, iObj)); - std::sort(priority_objective.begin(), priority_objective.end(), comparison); - // Clear LP objective - lp.offset_ = 0; - lp.col_cost_.assign(lp.num_col_, 0); - const HighsInt original_lp_num_row = lp.num_row_; - std::vector index(lp.num_col_); - std::vector value(lp.num_col_); - for (HighsInt iIx = 0; iIx < num_linear_objective; iIx++) { - HighsInt iObj = priority_objective[iIx].second; - 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("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)); - printf("\n"); - HighsStatus solve_status = this->solve(); - if (solve_status == HighsStatus::kError) - return returnFromLexicographicOptimization(HighsStatus::kError, - original_lp_num_row); - if (model_status_ != HighsModelStatus::kOptimal) { - highsLogUser(options_.log_options, HighsLogType::kWarning, - "After priority %d solve, model status is %s\n", int(), - modelStatusToString(model_status_).c_str()); - return returnFromLexicographicOptimization(HighsStatus::kWarning, - original_lp_num_row); - } - this->writeSolution("", kSolutionStylePretty); - if (iIx == num_linear_objective - 1) break; - // Add the constraint - HighsInt nnz = 0; - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { - if (lp.col_cost_[iCol]) { - index[nnz] = iCol; - value[nnz] = lp.col_cost_[iCol]; - nnz++; - } - } - double objective = info_.objective_function_value; - HighsStatus add_row_status; - if (lp.sense_ == ObjSense::kMinimize) { - // Minimizing, so set a greater upper bound than the objective - double upper_bound = objective + linear_objective.abs_tolerance; - if (objective >= 0) { - // Guarantees objective of at least t.f^* - // - // so (t.f^*-f^*)/f^* = t-1 - upper_bound = - std::min(objective * linear_objective.rel_tolerance, upper_bound); - } else if (objective < 0) { - // Guarantees objective of at least (2-t).f^* - // - // so ((2-t).f^*-f^*)/f^* = 1-t - upper_bound = std::min( - objective * (2.0 - linear_objective.rel_tolerance), upper_bound); - } - upper_bound -= lp.offset_; - add_row_status = this->addRow(-kHighsInf, upper_bound, nnz, index.data(), - value.data()); - } else { - // Maximizing, so set a lesser lower bound than the objective - double lower_bound = objective - linear_objective.abs_tolerance; - if (objective >= 0) { - // Guarantees objective of at most (2-t).f^* - // - // so ((2-t).f^*-f^*)/f^* = 1-t - lower_bound = std::max( - objective * (2.0 - linear_objective.rel_tolerance), lower_bound); - } else if (objective < 0) { - // Guarantees objective of at least t.f^* - // - // so (t.f^*-f^*)/f^* = t-1 - lower_bound = - std::max(objective * linear_objective.rel_tolerance, lower_bound); - } - lower_bound -= lp.offset_; - add_row_status = - this->addRow(lower_bound, kHighsInf, nnz, index.data(), value.data()); - } - assert(add_row_status == HighsStatus::kOk); - } - return returnFromLexicographicOptimization(HighsStatus::kOk, - original_lp_num_row); + if (num_linear_objective == 0) return this->solve(); + return this->multiobjectiveSolve(); } // Checks the options calls presolve and postsolve if needed. Solvers are called @@ -4675,33 +4527,37 @@ HighsStatus Highs::returnFromLexicographicOptimization( 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]); + HighsInt num_linear_objective = this->multi_linear_objective_.size(); + if (num_linear_objective > 1) { + 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/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index ad37bdb923..361431bf04 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -3647,6 +3647,171 @@ bool Highs::hasRepeatedLinearObjectivePriorities( return false; } +bool comparison(std::pair x1, + std::pair x2) { + return x1.first >= x2.first; +} + +HighsStatus Highs::multiobjectiveSolve() { + 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++) { + HighsLinearObjective& multi_linear_objective = + this->multi_linear_objective_[iObj]; + if (multi_linear_objective.coefficients.size() != + static_cast(lp.num_col_)) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Multiple linear objective coefficient vector %d has size " + "incompatible with model\n", + int(iObj)); + return HighsStatus::kError; + } + } + + this->clearSolver(); + if (this->options_.blend_multi_objectives) { + // Objectives are blended by weight and minimized + lp.offset_ = 0; + lp.col_cost_.assign(lp.num_col_, 0); + for (HighsInt iObj = 0; iObj < num_linear_objective; iObj++) { + HighsLinearObjective& multi_linear_objective = + this->multi_linear_objective_[iObj]; + lp.offset_ += + multi_linear_objective.weight * multi_linear_objective.offset; + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + lp.col_cost_[iCol] += multi_linear_objective.weight * + multi_linear_objective.coefficients[iCol]; + } + lp.sense_ = ObjSense::kMinimize; + 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)); + printf("\n"); + return this->solve(); + } + + // 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++) + priority_objective.push_back( + std::make_pair(this->multi_linear_objective_[iObj].priority, iObj)); + std::sort(priority_objective.begin(), priority_objective.end(), comparison); + // Clear LP objective + lp.offset_ = 0; + lp.col_cost_.assign(lp.num_col_, 0); + const HighsInt original_lp_num_row = lp.num_row_; + std::vector index(lp.num_col_); + std::vector value(lp.num_col_); + for (HighsInt iIx = 0; iIx < num_linear_objective; iIx++) { + HighsInt iObj = priority_objective[iIx].second; + 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("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)); + printf("\n"); + HighsStatus solve_status = this->solve(); + if (solve_status == HighsStatus::kError) + return returnFromLexicographicOptimization(HighsStatus::kError, + original_lp_num_row); + if (model_status_ != HighsModelStatus::kOptimal) { + highsLogUser(options_.log_options, HighsLogType::kWarning, + "After priority %d solve, model status is %s\n", int(), + modelStatusToString(model_status_).c_str()); + return returnFromLexicographicOptimization(HighsStatus::kWarning, + original_lp_num_row); + } + this->writeSolution("", kSolutionStylePretty); + if (iIx == num_linear_objective - 1) break; + // Add the constraint + HighsInt nnz = 0; + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + if (lp.col_cost_[iCol]) { + index[nnz] = iCol; + value[nnz] = lp.col_cost_[iCol]; + nnz++; + } + } + double objective = info_.objective_function_value; + HighsStatus add_row_status; + double lower_bound = -kHighsInf; + double upper_bound = kHighsInf; + if (lp.sense_ == ObjSense::kMinimize) { + // Minimizing, so set a greater upper bound than the objective + upper_bound = objective + linear_objective.abs_tolerance; + if (objective >= 0) { + // Guarantees objective of at least t.f^* + // + // so (t.f^*-f^*)/f^* = t-1 + upper_bound = + std::min(objective * linear_objective.rel_tolerance, upper_bound); + } else if (objective < 0) { + // Guarantees objective of at least (2-t).f^* + // + // so ((2-t).f^*-f^*)/f^* = 1-t + upper_bound = std::min( + objective * (2.0 - linear_objective.rel_tolerance), upper_bound); + } + upper_bound -= lp.offset_; + } else { + // Maximizing, so set a lesser lower bound than the objective + lower_bound = objective - linear_objective.abs_tolerance; + if (objective >= 0) { + // Guarantees objective of at most (2-t).f^* + // + // so ((2-t).f^*-f^*)/f^* = 1-t + double lower_bound_from_rel_tolerance = + objective * (2.0 - linear_objective.rel_tolerance); + lower_bound = std::max( + objective * (2.0 - linear_objective.rel_tolerance), lower_bound); + } else if (objective < 0) { + // Guarantees objective of at least t.f^* + // + // so (t.f^*-f^*)/f^* = t-1 + lower_bound = + std::max(objective * linear_objective.rel_tolerance, lower_bound); + } + lower_bound -= lp.offset_; + } + printf("Highs::run() Add objective constraint %g <= ", lower_bound); + for (HighsInt iEl = 0; iEl < nnz; iEl++) + printf(" + (%g) x[%d]", value[iEl], int(index[iEl])); + printf(" <= %g\n", upper_bound); + + add_row_status = + this->addRow(lower_bound, upper_bound, nnz, index.data(), value.data()); + assert(add_row_status == HighsStatus::kOk); + } + return returnFromLexicographicOptimization(HighsStatus::kOk, + original_lp_num_row); +} + void HighsLinearObjective::clear() { this->weight = 0.0; this->offset = 0.0; From aa09efb69afd570eeb0a5d883a9c6d442ed41ae7 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 22 Nov 2024 10:05:28 +0000 Subject: [PATCH 09/21] Before removing dev printing --- check/TestMultiObjective.cpp | 302 ++++++++++++++++++--------------- src/lp_data/Highs.cpp | 49 ------ src/lp_data/HighsInterface.cpp | 134 ++++++++++----- 3 files changed, 250 insertions(+), 235 deletions(-) diff --git a/check/TestMultiObjective.cpp b/check/TestMultiObjective.cpp index 4cd0b729aa..580dcd50e2 100644 --- a/check/TestMultiObjective.cpp +++ b/check/TestMultiObjective.cpp @@ -23,146 +23,166 @@ TEST_CASE("multi-objective", "[util]") { lp.a_matrix_.value_ = {3, 1, 1, 1, 1, 2}; Highs h; h.setOptionValue("output_flag", dev_run); - h.passModel(lp); - HighsLinearObjective linear_objective; - std::vector linear_objectives; - - // Begin with an illegal linear objective - 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; - 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) - if (dev_run) printf("\nPass legal linear objective\n"); - linear_objective.coefficients = {1, 1}; - REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kOk); - - REQUIRE(h.run() == HighsStatus::kOk); - h.writeSolution("", kSolutionStylePretty); - REQUIRE(smallDoubleDifference(h.getInfo().objective_function_value, -7)); - // Save the linear objective for the next - linear_objectives.push_back(linear_objective); - - // Add a second linear objective with a very small minimization - // weight that should push the optimal solution to (2, 6) - if (dev_run) printf("\nPass second linear objective\n"); - linear_objective.weight = 1e-4; - linear_objective.offset = 0; - linear_objective.coefficients = {1, 0}; - REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kOk); - - REQUIRE(h.run() == HighsStatus::kOk); - h.writeSolution("", kSolutionStylePretty); - REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 2)); - REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); - linear_objectives.push_back(linear_objective); - - 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); - REQUIRE(h.run() == HighsStatus::kOk); - h.writeSolution("", kSolutionStylePretty); - 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); - - 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)); - REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); - - // Back to blending - h.setOptionValue("blend_multi_objectives", true); - // h.setOptionValue("output_flag", true); - REQUIRE(h.clearLinearObjectives() == HighsStatus::kOk); - linear_objectives[0].coefficients = {1.0001, 1}; - linear_objectives[0].abs_tolerance = -1e-5; - linear_objectives[0].rel_tolerance = 0.95; - - if (dev_run) printf("\nBlending: Illegal abs_tolerance \n"); - REQUIRE(h.passLinearObjectives(1, linear_objectives.data()) == - HighsStatus::kError); - linear_objectives[0].abs_tolerance = 1e-5; - - if (dev_run) printf("\nBlending: Illegal rel_tolerance \n"); - REQUIRE(h.passLinearObjectives(1, linear_objectives.data()) == - HighsStatus::kError); - linear_objectives[0].rel_tolerance = 1.05; - - linear_objectives[1].weight = 1e-3; - if (dev_run) - printf( - "\nBlending: first solve objective just giving unique optimal " - "solution\n"); - REQUIRE(h.passLinearObjectives(1, linear_objectives.data()) == - HighsStatus::kOk); - - REQUIRE(h.run() == HighsStatus::kOk); - h.writeSolution("", kSolutionStylePretty); - - REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == - HighsStatus::kOk); - - REQUIRE(h.run() == HighsStatus::kOk); - h.writeSolution("", kSolutionStylePretty); - - // Back to lexicographic optimization - h.setOptionValue("blend_multi_objectives", false); - - if (dev_run) printf("\nLexicographic using non-trivial tolerances\n"); - REQUIRE(h.run() == HighsStatus::kOk); - h.writeSolution("", kSolutionStylePretty); - - REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 4.9)); - REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 3.1)); - - linear_objectives[0].abs_tolerance = kHighsInf; - - REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == - HighsStatus::kOk); - - REQUIRE(h.run() == HighsStatus::kOk); - h.writeSolution("", kSolutionStylePretty); - - // printf("Solution = [%23.18g, %23.18g]\n", h.getSolution().col_value[0], - // h.getSolution().col_value[1]); - REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 1.30069)); - REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6.34966)); + for (HighsInt k = 0; k < 2; k++) { + // Pass 0 is continuous; pass 1 integer + if (dev_run) + printf("\n******************\nPass %d: var type is %s\n******************\n", int(k), k==0 ? "continuous" : "integer"); + for (HighsInt l = 0; l < 2; l++) { + // Pass 0 is with unsigned weights and coefficients + double obj_mu = l == 0 ? 1 : -1; + if (dev_run) + printf("\n******************\nPass %d: objective multiplier is %g\n******************\n", int(l), obj_mu); + + if (k == 0) { + lp.integrality_.clear(); + } else if (k == 1) { + lp.integrality_ = {HighsVarType::kInteger, HighsVarType::kInteger}; + } + h.passModel(lp); + + h.setOptionValue("blend_multi_objectives", true); + + HighsLinearObjective linear_objective; + std::vector linear_objectives; + REQUIRE(h.clearLinearObjectives() == HighsStatus::kOk); + + // Begin with an illegal linear objective + 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.abs_tolerance = 0.0; + linear_objective.rel_tolerance = 0.0; + 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) + if (dev_run) printf("\nPass legal linear objective\n"); + linear_objective.coefficients = {obj_mu*1, obj_mu*1}; + REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kOk); + + REQUIRE(h.run() == HighsStatus::kOk); + h.writeSolution("", kSolutionStylePretty); + REQUIRE(smallDoubleDifference(h.getInfo().objective_function_value, -7)); + // Save the linear objective for the next + linear_objectives.push_back(linear_objective); + + // Add a second linear objective with a very small minimization + // weight that should push the optimal solution to (2, 6) + if (dev_run) printf("\nPass second linear objective\n"); + linear_objective.weight = obj_mu*1e-4; + linear_objective.offset = 0; + linear_objective.coefficients = {obj_mu*1, obj_mu*0}; + REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kOk); + + REQUIRE(h.run() == HighsStatus::kOk); + h.writeSolution("", kSolutionStylePretty); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 2)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); + linear_objectives.push_back(linear_objective); + + 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); + REQUIRE(h.run() == HighsStatus::kOk); + h.writeSolution("", kSolutionStylePretty); + 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); + + 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)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); + + // Back to blending + h.setOptionValue("blend_multi_objectives", true); + // h.setOptionValue("output_flag", true); + REQUIRE(h.clearLinearObjectives() == HighsStatus::kOk); + linear_objectives[0].coefficients = {obj_mu*1.0001, obj_mu*1}; + linear_objectives[0].abs_tolerance = 1e-5; + linear_objectives[0].rel_tolerance = 0.05; + linear_objectives[1].weight = obj_mu*1e-3; + if (dev_run) + printf( + "\nBlending: first solve objective just giving unique optimal " + "solution\n"); + REQUIRE(h.passLinearObjectives(1, linear_objectives.data()) == + HighsStatus::kOk); + + REQUIRE(h.run() == HighsStatus::kOk); + h.writeSolution("", kSolutionStylePretty); + + REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == + HighsStatus::kOk); + + REQUIRE(h.run() == HighsStatus::kOk); + h.writeSolution("", kSolutionStylePretty); + + // Back to lexicographic optimization + h.setOptionValue("blend_multi_objectives", false); + + if (dev_run) printf("\nLexicographic using non-trivial tolerances\n"); + REQUIRE(h.run() == HighsStatus::kOk); + h.writeSolution("", kSolutionStylePretty); + + if (k == 0) { + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 4.9)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 3.1)); + } else { + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 5)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 3)); + } + + linear_objectives[0].abs_tolerance = kHighsInf; + + REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == + HighsStatus::kOk); + + REQUIRE(h.run() == HighsStatus::kOk); + h.writeSolution("", kSolutionStylePretty); + + // printf("Solution = [%23.18g, %23.18g]\n", h.getSolution().col_value[0], + // h.getSolution().col_value[1]); + if (k == 0) { + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 1.30069)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6.34966)); + } else { + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 2)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); + } + } + } } + diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index 9a3bd54c06..6352a68a09 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -4513,55 +4513,6 @@ HighsStatus Highs::returnFromRun(const HighsStatus run_return_status, return returnFromHighs(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); - - HighsInt num_linear_objective = this->multi_linear_objective_.size(); - if (num_linear_objective > 1) { - 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; -} - HighsStatus Highs::returnFromHighs(HighsStatus highs_return_status) { // Applies checks before returning from HiGHS HighsStatus return_status = highs_return_status; diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 361431bf04..562b66918a 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -3599,22 +3599,6 @@ bool Highs::validLinearObjective(const HighsLinearObjective& linear_objective, 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( @@ -3652,6 +3636,56 @@ bool comparison(std::pair x1, return x1.first >= x2.first; } +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); + + HighsInt num_linear_objective = this->multi_linear_objective_.size(); + if (num_linear_objective > 1) { + 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]); + } + this->model_.lp_.col_cost_.assign(this->model_.lp_.num_col_, 0); + } + return return_status; +} + HighsStatus Highs::multiobjectiveSolve() { HighsInt num_linear_objective = this->multi_linear_objective_.size(); assert(num_linear_objective > 0); @@ -3721,6 +3755,7 @@ HighsStatus Highs::multiobjectiveSolve() { std::vector index(lp.num_col_); std::vector value(lp.num_col_); for (HighsInt iIx = 0; iIx < num_linear_objective; iIx++) { + HighsInt priority = priority_objective[iIx].first; HighsInt iObj = priority_objective[iIx].second; printf("\nHighs::run() Entry %d is objective %d with priority %d\n", int(iIx), int(iObj), int(priority_objective[iIx].first)); @@ -3742,7 +3777,7 @@ HighsStatus Highs::multiobjectiveSolve() { original_lp_num_row); if (model_status_ != HighsModelStatus::kOptimal) { highsLogUser(options_.log_options, HighsLogType::kWarning, - "After priority %d solve, model status is %s\n", int(), + "After priority %d solve, model status is %s\n", int(priority), modelStatusToString(model_status_).c_str()); return returnFromLexicographicOptimization(HighsStatus::kWarning, original_lp_num_row); @@ -3764,41 +3799,50 @@ HighsStatus Highs::multiobjectiveSolve() { double upper_bound = kHighsInf; if (lp.sense_ == ObjSense::kMinimize) { // Minimizing, so set a greater upper bound than the objective - upper_bound = objective + linear_objective.abs_tolerance; - if (objective >= 0) { - // Guarantees objective of at least t.f^* - // - // so (t.f^*-f^*)/f^* = t-1 - upper_bound = - std::min(objective * linear_objective.rel_tolerance, upper_bound); - } else if (objective < 0) { - // Guarantees objective of at least (2-t).f^* - // - // so ((2-t).f^*-f^*)/f^* = 1-t - upper_bound = std::min( - objective * (2.0 - linear_objective.rel_tolerance), upper_bound); + if (linear_objective.abs_tolerance >= 0) + upper_bound = objective + linear_objective.abs_tolerance; + if (linear_objective.rel_tolerance >= 0) { + if (objective >= 0) { + // Guarantees objective of at least (1+t).f^* + // + // so ((1+t).f^*-f^*)/f^* = t + upper_bound = std::min( + objective * (1.0 + linear_objective.rel_tolerance), upper_bound); + } else if (objective < 0) { + // Guarantees objective of at least (1-t).f^* + // + // so ((1-t).f^*-f^*)/f^* = -t + upper_bound = std::min( + objective * (1.0 - linear_objective.rel_tolerance), upper_bound); + } } upper_bound -= lp.offset_; } else { // Maximizing, so set a lesser lower bound than the objective - lower_bound = objective - linear_objective.abs_tolerance; - if (objective >= 0) { - // Guarantees objective of at most (2-t).f^* - // - // so ((2-t).f^*-f^*)/f^* = 1-t - double lower_bound_from_rel_tolerance = - objective * (2.0 - linear_objective.rel_tolerance); - lower_bound = std::max( - objective * (2.0 - linear_objective.rel_tolerance), lower_bound); - } else if (objective < 0) { - // Guarantees objective of at least t.f^* - // - // so (t.f^*-f^*)/f^* = t-1 - lower_bound = - std::max(objective * linear_objective.rel_tolerance, lower_bound); + if (linear_objective.abs_tolerance >= 0) + lower_bound = objective - linear_objective.abs_tolerance; + if (linear_objective.rel_tolerance >= 0) { + if (objective >= 0) { + // Guarantees objective of at most (1-t).f^* + // + // so ((1-t).f^*-f^*)/f^* = -t + lower_bound = std::max( + objective * (1.0 - linear_objective.rel_tolerance), lower_bound); + } else if (objective < 0) { + // Guarantees objective of at least (1+t).f^* + // + // so ((1+t).f^*-f^*)/f^* = t + lower_bound = std::max( + objective * (1.0 + linear_objective.rel_tolerance), lower_bound); + } } lower_bound -= lp.offset_; } + if (lower_bound == -kHighsInf && upper_bound == kHighsInf) + highsLogUser(options_.log_options, HighsLogType::kWarning, + "After priority %d solve, no objective constraint due to absolute tolerance being %g < 0," + " and relative tolerance being %g < 0\n", + int(priority), linear_objective.abs_tolerance, linear_objective.rel_tolerance); printf("Highs::run() Add objective constraint %g <= ", lower_bound); for (HighsInt iEl = 0; iEl < nnz; iEl++) printf(" + (%g) x[%d]", value[iEl], int(index[iEl])); From 714ce34828ebb48de57b31ca544a532b2b1ac969 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 22 Nov 2024 10:08:27 +0000 Subject: [PATCH 10/21] Removed dev logging; formatted --- check/TestMultiObjective.cpp | 118 ++++++++++++++++++--------------- src/lp_data/HighsInterface.cpp | 111 +++++++++++-------------------- 2 files changed, 100 insertions(+), 129 deletions(-) diff --git a/check/TestMultiObjective.cpp b/check/TestMultiObjective.cpp index 580dcd50e2..9761c3045b 100644 --- a/check/TestMultiObjective.cpp +++ b/check/TestMultiObjective.cpp @@ -26,18 +26,23 @@ TEST_CASE("multi-objective", "[util]") { for (HighsInt k = 0; k < 2; k++) { // Pass 0 is continuous; pass 1 integer - if (dev_run) - printf("\n******************\nPass %d: var type is %s\n******************\n", int(k), k==0 ? "continuous" : "integer"); + if (dev_run) + printf( + "\n******************\nPass %d: var type is %s\n******************\n", + int(k), k == 0 ? "continuous" : "integer"); for (HighsInt l = 0; l < 2; l++) { // Pass 0 is with unsigned weights and coefficients double obj_mu = l == 0 ? 1 : -1; - if (dev_run) - printf("\n******************\nPass %d: objective multiplier is %g\n******************\n", int(l), obj_mu); + if (dev_run) + printf( + "\n******************\nPass %d: objective multiplier is " + "%g\n******************\n", + int(l), obj_mu); if (k == 0) { - lp.integrality_.clear(); + lp.integrality_.clear(); } else if (k == 1) { - lp.integrality_ = {HighsVarType::kInteger, HighsVarType::kInteger}; + lp.integrality_ = {HighsVarType::kInteger, HighsVarType::kInteger}; } h.passModel(lp); @@ -51,14 +56,14 @@ 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 * 2, 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); // Now legalise the linear objective so LP has nonunique optimal // solutions on the line joining (2, 6) and (5, 3) if (dev_run) printf("\nPass legal linear objective\n"); - linear_objective.coefficients = {obj_mu*1, obj_mu*1}; + linear_objective.coefficients = {obj_mu * 1, obj_mu * 1}; REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kOk); REQUIRE(h.run() == HighsStatus::kOk); @@ -70,9 +75,9 @@ 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) if (dev_run) printf("\nPass second linear objective\n"); - linear_objective.weight = obj_mu*1e-4; + linear_objective.weight = obj_mu * 1e-4; linear_objective.offset = 0; - linear_objective.coefficients = {obj_mu*1, obj_mu*0}; + linear_objective.coefficients = {obj_mu * 1, obj_mu * 0}; REQUIRE(h.addLinearObjective(linear_objective) == HighsStatus::kOk); REQUIRE(h.run() == HighsStatus::kOk); @@ -84,7 +89,7 @@ TEST_CASE("multi-objective", "[util]") { 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); + HighsStatus::kOk); REQUIRE(h.run() == HighsStatus::kOk); h.writeSolution("", kSolutionStylePretty); REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 2)); @@ -93,96 +98,99 @@ TEST_CASE("multi-objective", "[util]") { // 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"); + 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); + HighsStatus::kOk); // Now test lexicographic optimization h.setOptionValue("blend_multi_objectives", false); 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"); + printf( + "\nSetting priorities that are illegal now blend_multi_objectives " + "= " + "false\n"); REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == - HighsStatus::kError); - + HighsStatus::kError); + if (dev_run) - printf("\nSetting legal priorities for blend_multi_objectives = false\n"); + 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"); + 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)); REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); - + // Back to blending h.setOptionValue("blend_multi_objectives", true); // h.setOptionValue("output_flag", true); REQUIRE(h.clearLinearObjectives() == HighsStatus::kOk); - linear_objectives[0].coefficients = {obj_mu*1.0001, obj_mu*1}; + linear_objectives[0].coefficients = {obj_mu * 1.0001, obj_mu * 1}; linear_objectives[0].abs_tolerance = 1e-5; linear_objectives[0].rel_tolerance = 0.05; - linear_objectives[1].weight = obj_mu*1e-3; + linear_objectives[1].weight = obj_mu * 1e-3; if (dev_run) - printf( - "\nBlending: first solve objective just giving unique optimal " - "solution\n"); + printf( + "\nBlending: first solve objective just giving unique optimal " + "solution\n"); REQUIRE(h.passLinearObjectives(1, linear_objectives.data()) == - HighsStatus::kOk); - + HighsStatus::kOk); + REQUIRE(h.run() == HighsStatus::kOk); h.writeSolution("", kSolutionStylePretty); - + REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == - HighsStatus::kOk); - + HighsStatus::kOk); + REQUIRE(h.run() == HighsStatus::kOk); h.writeSolution("", kSolutionStylePretty); - + // Back to lexicographic optimization h.setOptionValue("blend_multi_objectives", false); - + if (dev_run) printf("\nLexicographic using non-trivial tolerances\n"); REQUIRE(h.run() == HighsStatus::kOk); h.writeSolution("", kSolutionStylePretty); - + if (k == 0) { - REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 4.9)); - REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 3.1)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 4.9)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 3.1)); } else { - REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 5)); - REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 3)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 5)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 3)); } - + linear_objectives[0].abs_tolerance = kHighsInf; - + REQUIRE(h.passLinearObjectives(2, linear_objectives.data()) == - HighsStatus::kOk); - + HighsStatus::kOk); + REQUIRE(h.run() == HighsStatus::kOk); h.writeSolution("", kSolutionStylePretty); - - // printf("Solution = [%23.18g, %23.18g]\n", h.getSolution().col_value[0], - // h.getSolution().col_value[1]); + + // printf("Solution = [%23.18g, %23.18g]\n", + // h.getSolution().col_value[0], h.getSolution().col_value[1]); if (k == 0) { - REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 1.30069)); - REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6.34966)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 1.30069)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6.34966)); } else { - REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 2)); - REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[0], 2)); + REQUIRE(smallDoubleDifference(h.getSolution().col_value[1], 6)); } } } } - diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 562b66918a..7f472a55fe 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -3638,25 +3638,13 @@ bool comparison(std::pair x1, 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); - HighsInt num_linear_objective = this->multi_linear_objective_.size(); if (num_linear_objective > 1) { 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; @@ -3671,16 +3659,6 @@ HighsStatus Highs::returnFromLexicographicOptimization( 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]); - } this->model_.lp_.col_cost_.assign(this->model_.lp_.num_col_, 0); } return return_status; @@ -3718,11 +3696,6 @@ HighsStatus Highs::multiobjectiveSolve() { multi_linear_objective.coefficients[iCol]; } lp.sense_ = ObjSense::kMinimize; - 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)); - printf("\n"); return this->solve(); } @@ -3757,8 +3730,6 @@ HighsStatus Highs::multiobjectiveSolve() { for (HighsInt iIx = 0; iIx < num_linear_objective; iIx++) { HighsInt priority = priority_objective[iIx].first; HighsInt iObj = priority_objective[iIx].second; - 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]; @@ -3766,19 +3737,14 @@ HighsStatus Highs::multiobjectiveSolve() { lp.col_cost_ = linear_objective.coefficients; lp.sense_ = linear_objective.weight > 0 ? ObjSense::kMinimize : ObjSense::kMaximize; - 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)); - printf("\n"); HighsStatus solve_status = this->solve(); if (solve_status == HighsStatus::kError) return returnFromLexicographicOptimization(HighsStatus::kError, original_lp_num_row); if (model_status_ != HighsModelStatus::kOptimal) { highsLogUser(options_.log_options, HighsLogType::kWarning, - "After priority %d solve, model status is %s\n", int(priority), - modelStatusToString(model_status_).c_str()); + "After priority %d solve, model status is %s\n", + int(priority), modelStatusToString(model_status_).c_str()); return returnFromLexicographicOptimization(HighsStatus::kWarning, original_lp_num_row); } @@ -3800,54 +3766,51 @@ HighsStatus Highs::multiobjectiveSolve() { if (lp.sense_ == ObjSense::kMinimize) { // Minimizing, so set a greater upper bound than the objective if (linear_objective.abs_tolerance >= 0) - upper_bound = objective + linear_objective.abs_tolerance; + upper_bound = objective + linear_objective.abs_tolerance; if (linear_objective.rel_tolerance >= 0) { - if (objective >= 0) { - // Guarantees objective of at least (1+t).f^* - // - // so ((1+t).f^*-f^*)/f^* = t - upper_bound = std::min( - objective * (1.0 + linear_objective.rel_tolerance), upper_bound); - } else if (objective < 0) { - // Guarantees objective of at least (1-t).f^* - // - // so ((1-t).f^*-f^*)/f^* = -t - upper_bound = std::min( - objective * (1.0 - linear_objective.rel_tolerance), upper_bound); - } + if (objective >= 0) { + // Guarantees objective of at least (1+t).f^* + // + // so ((1+t).f^*-f^*)/f^* = t + upper_bound = std::min( + objective * (1.0 + linear_objective.rel_tolerance), upper_bound); + } else if (objective < 0) { + // Guarantees objective of at least (1-t).f^* + // + // so ((1-t).f^*-f^*)/f^* = -t + upper_bound = std::min( + objective * (1.0 - linear_objective.rel_tolerance), upper_bound); + } } upper_bound -= lp.offset_; } else { // Maximizing, so set a lesser lower bound than the objective if (linear_objective.abs_tolerance >= 0) - lower_bound = objective - linear_objective.abs_tolerance; + lower_bound = objective - linear_objective.abs_tolerance; if (linear_objective.rel_tolerance >= 0) { - if (objective >= 0) { - // Guarantees objective of at most (1-t).f^* - // - // so ((1-t).f^*-f^*)/f^* = -t - lower_bound = std::max( - objective * (1.0 - linear_objective.rel_tolerance), lower_bound); - } else if (objective < 0) { - // Guarantees objective of at least (1+t).f^* - // - // so ((1+t).f^*-f^*)/f^* = t - lower_bound = std::max( - objective * (1.0 + linear_objective.rel_tolerance), lower_bound); - } + if (objective >= 0) { + // Guarantees objective of at most (1-t).f^* + // + // so ((1-t).f^*-f^*)/f^* = -t + lower_bound = std::max( + objective * (1.0 - linear_objective.rel_tolerance), lower_bound); + } else if (objective < 0) { + // Guarantees objective of at least (1+t).f^* + // + // so ((1+t).f^*-f^*)/f^* = t + lower_bound = std::max( + objective * (1.0 + linear_objective.rel_tolerance), lower_bound); + } } lower_bound -= lp.offset_; } - if (lower_bound == -kHighsInf && upper_bound == kHighsInf) - highsLogUser(options_.log_options, HighsLogType::kWarning, - "After priority %d solve, no objective constraint due to absolute tolerance being %g < 0," - " and relative tolerance being %g < 0\n", - int(priority), linear_objective.abs_tolerance, linear_objective.rel_tolerance); - printf("Highs::run() Add objective constraint %g <= ", lower_bound); - for (HighsInt iEl = 0; iEl < nnz; iEl++) - printf(" + (%g) x[%d]", value[iEl], int(index[iEl])); - printf(" <= %g\n", upper_bound); - + if (lower_bound == -kHighsInf && upper_bound == kHighsInf) + highsLogUser(options_.log_options, HighsLogType::kWarning, + "After priority %d solve, no objective constraint due to " + "absolute tolerance being %g < 0," + " and relative tolerance being %g < 0\n", + int(priority), linear_objective.abs_tolerance, + linear_objective.rel_tolerance); add_row_status = this->addRow(lower_bound, upper_bound, nnz, index.data(), value.data()); assert(add_row_status == HighsStatus::kOk); From e38350721bb2687201a709c69c0b42ce75d5d6f3 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 22 Nov 2024 10:58:27 +0000 Subject: [PATCH 11/21] Started modifying documentation --- FEATURES.md | 34 +-------- docs/make.jl | 9 ++- docs/src/guide/further.md | 17 +++++ docs/src/interfaces/python/example-py.md | 11 ++- docs/src/options/definitions.md | 5 ++ docs/src/structures/classes/HighsBasis.md | 8 -- docs/src/structures/classes/HighsInfo.md | 80 -------------------- docs/src/structures/classes/HighsSolution.md | 10 --- src/lp_data/HighsOptions.h | 2 + 9 files changed, 43 insertions(+), 133 deletions(-) delete mode 100644 docs/src/structures/classes/HighsBasis.md delete mode 100644 docs/src/structures/classes/HighsInfo.md delete mode 100644 docs/src/structures/classes/HighsSolution.md diff --git a/FEATURES.md b/FEATURES.md index 034eca4232..76f4c4997d 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -2,39 +2,13 @@ ## Code changes -When primal infeasiblity is detected in presolve, no dual ray is available so, previously, the `has_dual_ray` parameter of `Highs::getDualRay` returned false and that was it. Now, if a null pointer is not passed for `dual_ray_value`, `Highs::getDualRay` will compute a dual ray - at the cost of solving the feasiblility LP without presolve. The same is now true for `Highs::getPrimalRay`. `Highs::getDualUnboundednessDirection` has been introduced to determine the product between the constraint matrix and the dual ray, forcing the calculation of the latter if necessary. Once a dual ray is known for the incumbent model in HiGHS, subsequent calls to `Highs::getDualRay` and `Highs::getDualUnboundednessDirection` will be vastly cheaper - -The method `Highs::getDualObjectiveValue` now exitsts to compute the dual objective value, returning `HighsStatus::kError` if it is not possible. - -The method `Highs::getStandardFormLp` now exists to return the incumbent LP in standard form - overlooking any integrality or Hessian. To determine the sizes of the vectors of data, the method is called without specifying pointers to the data arrays. - -Added documentation on the use of presolve when solving an incumbent model, and clarifying the use of the method `Highs::presolve`. - -HiGHS will now read a `MIPLIB` solution file - -Added time limit check to `HPresolve::strengthenInequalities` - -Added `getColIntegrality` to `highspy` - -Now computing the primal-dual integral, reporting it, and making it available as `HighsInfo::primal_dual_integral` - -Trivial primal heuristics "all zero", "all lower bound", "all upper bound", and "lock point" added to the MIP solver - -# Build changes - -Added wheels for Python 3.13 - -Updated command line options and moved them out of the library and into the executable - - - - - - - +HiGHS now handles multiple linear objectives by either blending using weights, or performing lexicographic optimization: see https://ergo-code.github.io/HiGHS/stable/guide/further/#guide-multi-objective-optimization +Fixed minor bug in bound checking in presolve +Fixed bug in `floor(HighsCDouble x)` and `ceil(HighsCDouble x)` when argument is small +Added some sanity checks to Highs::writeLocalModel to prevent segfaults if called directly by a user diff --git a/docs/make.jl b/docs/make.jl index c1f6bc70b9..ab42d07be3 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -64,9 +64,12 @@ Documenter.makedocs( "structures/classes/HighsLp.md", "structures/classes/HighsHessian.md", "structures/classes/HighsModel.md", - "structures/classes/HighsSolution.md", - "structures/classes/HighsBasis.md", - "structures/classes/HighsInfo.md", + ], + "Structures" => Any[, + "structures/structs/HighsSolution.md", + "structures/structs/HighsBasis.md", + "structures/structs/HighsInfo.md", + "structures/structs/HighsLinearObjective.md", ], ], "Callbacks" => "callbacks.md", diff --git a/docs/src/guide/further.md b/docs/src/guide/further.md index 3597928f5b..cd7f6cf730 100644 --- a/docs/src/guide/further.md +++ b/docs/src/guide/further.md @@ -104,3 +104,20 @@ HiGHS. Note that this does not affect how the incumbent model is solved. There are two corresponding [postsolve](@ref Presolve/postsolve) methods, according to whether there are just solution values, or also a basis. + +## [Multi-objective optimization](@id guide-multi-objective-optimization) + +Users can specify multiple linear objectives with respect to which +HiGHS will optimize by either blending them, or by performing +lexicographic optimization according to the truth of the +[blend_multi_objectives](@ref blend_multi_objectives) option. Each +linear objective is represented by the following data, held in the +[HighsLinearObjective](@ref HighsLinearObjective) structure + +- weight: Scalar of type double - The weight of this objective when blending +- offset: Scalar of type double - The offset of this objective +- coefficients: Vector of type double - The coefficients of this objective +- abs\_tolerance: Scalar of type double - The absolute tolerance on this objective when performing lexicographic optimization +- rel\_tolerance: Scalar of type double - The relative tolerance on this objective when performing lexicographic optimization +- priority: Scalar of type HighsInt - The priority of this objective when performing lexicographic optimization + diff --git a/docs/src/interfaces/python/example-py.md b/docs/src/interfaces/python/example-py.md index 50d40c59e7..6fce25e870 100644 --- a/docs/src/interfaces/python/example-py.md +++ b/docs/src/interfaces/python/example-py.md @@ -237,5 +237,12 @@ print('Basis validity = ', h.basisValidityToString(info.basis_validity)) * `presolveRuleTypeToString` * `postsolve` - - +## Multi-objective optimization + +* `passLinearObjectives` +* `addLinearObjective` +* `clearLinearObjectives` + + + + diff --git a/docs/src/options/definitions.md b/docs/src/options/definitions.md index 8055067601..f4e932523c 100644 --- a/docs/src/options/definitions.md +++ b/docs/src/options/definitions.md @@ -353,3 +353,8 @@ - Range: {0, 2147483647} - Default: 4000 +## blend\_multi\_objectives +- Blend multiple objectives or apply lexicographically +- Type: boolean +- Default: "true" + diff --git a/docs/src/structures/classes/HighsBasis.md b/docs/src/structures/classes/HighsBasis.md deleted file mode 100644 index bfc71c7efd..0000000000 --- a/docs/src/structures/classes/HighsBasis.md +++ /dev/null @@ -1,8 +0,0 @@ -# HighsBasis - -The basis of a model is communicated via an instance of the HighsBasis class - -- valid: Scalar of type bool - Indicates whether the basis is valid -- col\_status: Vector of type [HighsBasisStatus](@ref) - Comparison with [HighsBasisStatus](@ref) gives the basis status of a column -- row\_status: Vector of type [HighsBasisStatus](@ref) - Comparison with [HighsBasisStatus](@ref) gives the basis status of a row - diff --git a/docs/src/structures/classes/HighsInfo.md b/docs/src/structures/classes/HighsInfo.md deleted file mode 100644 index f0ddebee4f..0000000000 --- a/docs/src/structures/classes/HighsInfo.md +++ /dev/null @@ -1,80 +0,0 @@ -# HighsInfo - -Scalar information about a solved model is communicated via an instance of the HighsInfo class - -## valid -- Indicates whether the values in a HighsInfo instance are valid -- Type: bool - -## simplex\_iteration\_count -- The number of simplex iterations performed -- Type: integer - -## ipm\_iteration\_count -- The number of interior point iterations performed -- Type: integer - -## crossover\_iteration\_count -- The number of crossover iterations performed -- Type: integer - -## qp\_iteration\_count -- The number of QP iterations performed -- Type: integer - -## primal\_solution\_status -- Comparison with [SolutionStatus](@ref) gives the status of the [primal](@ref Primal-values) solution -- Type: integer - -## dual\_solution\_status -- Comparison with [SolutionStatus](@ref) gives the status of the [dual](@ref Dual-values) solution -- Type: integer - -## basis\_validity -- Comparison with [BasisValidity](@ref) gives the status of any basis information -- Type: integer - -## objective\_function\_value -- The optimal value of the objective function -- Type: double - -## mip\_node\_count -- The number of nodes generated by the MIP solver -- Type: long integer - -## mip\_dual\_bound -- The [dual bound](@ref terminology-mip) for the MIP solver -- Type: double - -## mip\_gap -- The absolute value of the gap between the primal and bounds, relative to the primal bound. -- Type: double - -## max\_integrality\_violation -- The maximum deviation from an integer value over all the discrete variables -- Type: double - -## num\_primal\_infeasibilities -- The number of variables violating a bound by more than the [primal feasibility tolerance](@ref primal_feasibility_tolerance). -- Type: integer - -## max\_primal\_infeasibility -- The maximum violation of a bound on a variable -- Type: double - -## sum\_primal\_infeasibilities -- The sum of violations of bounds by variables -- Type: double - -## num\_dual\_infeasibilities -- The number of variables violating dual feasibility by more than the [dual feasibility tolerance](@ref dual_feasibility_tolerance). -- Type: integer - -## max\_dual\_infeasibility -- The maximum dual feasibility violation -- Type: double - -## sum\_dual\_infeasibilities -- The sum of dual feasibility violations -- Type: double - diff --git a/docs/src/structures/classes/HighsSolution.md b/docs/src/structures/classes/HighsSolution.md deleted file mode 100644 index 78fc48a959..0000000000 --- a/docs/src/structures/classes/HighsSolution.md +++ /dev/null @@ -1,10 +0,0 @@ -# HighsSolution - -The solution of a model is communicated via an instance of the HighsSolution class - -- value\_valid: Scalar of type bool - Indicates whether the column and row values are valid -- dual\_valid: Scalar of type bool - Indicates whether the column and row [duals](@ref Dual-values) are valid -- col\_value: Vector of type double - Values of the columns (variables) -- col\_dual: Vector of type double - Duals of the columns (variables) -- row\_value: Vector of type double - Values of the rows (constraints) -- row\_dual: Vector of type double - Duals of the rows (constraints) diff --git a/src/lp_data/HighsOptions.h b/src/lp_data/HighsOptions.h index abbf7624ae..f7e7142e57 100644 --- a/src/lp_data/HighsOptions.h +++ b/src/lp_data/HighsOptions.h @@ -488,6 +488,8 @@ struct HighsOptionsStruct { pdlp_d_gap_tol(0.0), qp_iteration_limit(0), qp_nullspace_limit(0), + iis_strategy(0), + blend_multi_objectives(false), log_dev_level(0), log_githash(false), solve_relaxation(false), From 423032284cf431b280136cb027184e1b1568b63b Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 22 Nov 2024 12:18:14 +0000 Subject: [PATCH 12/21] Corrected make.jl and format in HighsOptions.h --- docs/make.jl | 2 +- src/lp_data/HighsOptions.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index ab42d07be3..c2d48c170b 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -65,7 +65,7 @@ Documenter.makedocs( "structures/classes/HighsHessian.md", "structures/classes/HighsModel.md", ], - "Structures" => Any[, + "Structures" => Any[ "structures/structs/HighsSolution.md", "structures/structs/HighsBasis.md", "structures/structs/HighsInfo.md", diff --git a/src/lp_data/HighsOptions.h b/src/lp_data/HighsOptions.h index f7e7142e57..82424a0adc 100644 --- a/src/lp_data/HighsOptions.h +++ b/src/lp_data/HighsOptions.h @@ -488,8 +488,8 @@ struct HighsOptionsStruct { pdlp_d_gap_tol(0.0), qp_iteration_limit(0), qp_nullspace_limit(0), - iis_strategy(0), - blend_multi_objectives(false), + iis_strategy(0), + blend_multi_objectives(false), log_dev_level(0), log_githash(false), solve_relaxation(false), From 857ee9b34d57576a1dee75c642a7b3a6194bcd5e Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 22 Nov 2024 12:41:05 +0000 Subject: [PATCH 13/21] Added docs/src/structures/structs --- docs/make.jl | 12 +-- docs/src/guide/further.md | 29 +++++++ docs/src/structures/structs/HighsBasis.md | 8 ++ docs/src/structures/structs/HighsInfo.md | 80 +++++++++++++++++++ .../structs/HighsLinearObjective.md | 11 +++ docs/src/structures/structs/HighsSolution.md | 10 +++ 6 files changed, 144 insertions(+), 6 deletions(-) create mode 100644 docs/src/structures/structs/HighsBasis.md create mode 100644 docs/src/structures/structs/HighsInfo.md create mode 100644 docs/src/structures/structs/HighsLinearObjective.md create mode 100644 docs/src/structures/structs/HighsSolution.md diff --git a/docs/make.jl b/docs/make.jl index c2d48c170b..f598979b78 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -65,12 +65,12 @@ Documenter.makedocs( "structures/classes/HighsHessian.md", "structures/classes/HighsModel.md", ], - "Structures" => Any[ - "structures/structs/HighsSolution.md", - "structures/structs/HighsBasis.md", - "structures/structs/HighsInfo.md", - "structures/structs/HighsLinearObjective.md", - ], +# "Structures" => Any[ +# "structures/structs/HighsSolution.md", +# "structures/structs/HighsBasis.md", +# "structures/structs/HighsInfo.md", +# "structures/structs/HighsLinearObjective.md", +# ], ], "Callbacks" => "callbacks.md", "Interfaces" => Any[ diff --git a/docs/src/guide/further.md b/docs/src/guide/further.md index cd7f6cf730..6e51fbb9f1 100644 --- a/docs/src/guide/further.md +++ b/docs/src/guide/further.md @@ -121,3 +121,32 @@ linear objective is represented by the following data, held in the - rel\_tolerance: Scalar of type double - The relative tolerance on this objective when performing lexicographic optimization - priority: Scalar of type HighsInt - The priority of this objective when performing lexicographic optimization +### Methods + +Multi-objective optimization in HiGHS is defined by the following methods + +- passLinearObjectives - Pass multiple linear objectives as their number `num_linear_objective` and pointer to a vector of `HighsLinearObjective` instances, overwriting any previous linear objectives +- addLinearObjective - Add a single `HighsLinearObjective` instance to any already stored in HiGHS +- clearLinearObjectives - Clears any linear objectives stored in HiGHS + +When there is at least one `HighsLinearObjective` instance in HiGHS, +the `col_cost_` data in the incumbent model is ignored. + +### Blending multiple linear objectives + +When [blend_multi_objectives](@ref blend_multi_objectives) is `true`, +as it is by default, any `HighsLinearObjective` instances will be +combined according to the `weight` values, and the resulting objective +will be minimized. Hence, any objectives that should be maximized +within the combination must have a negative `weight` value. + +### Lexicographic optimization of multiple linear objectives + +When [blend_multi_objectives](@ref blend_multi_objectives) is `false`, +HiGHS will optimize lexicographically with respect to any +`HighsLinearObjective` instances. This is carried out according to the +`priority` values in `HighsLinearObjective` instances. Note that all +priority values must be distinct. + + + diff --git a/docs/src/structures/structs/HighsBasis.md b/docs/src/structures/structs/HighsBasis.md new file mode 100644 index 0000000000..6bac592bd5 --- /dev/null +++ b/docs/src/structures/structs/HighsBasis.md @@ -0,0 +1,8 @@ +# HighsBasis + +The basis of a model is communicated via an instance of the HighsBasis structure + +- valid: Scalar of type bool - Indicates whether the basis is valid +- col\_status: Vector of type [HighsBasisStatus](@ref) - Comparison with [HighsBasisStatus](@ref) gives the basis status of a column +- row\_status: Vector of type [HighsBasisStatus](@ref) - Comparison with [HighsBasisStatus](@ref) gives the basis status of a row + diff --git a/docs/src/structures/structs/HighsInfo.md b/docs/src/structures/structs/HighsInfo.md new file mode 100644 index 0000000000..9a3217adc3 --- /dev/null +++ b/docs/src/structures/structs/HighsInfo.md @@ -0,0 +1,80 @@ +# HighsInfo + +Scalar information about a solved model is communicated via an instance of the HighsInfo structure + +## valid +- Indicates whether the values in a HighsInfo instance are valid +- Type: bool + +## simplex\_iteration\_count +- The number of simplex iterations performed +- Type: integer + +## ipm\_iteration\_count +- The number of interior point iterations performed +- Type: integer + +## crossover\_iteration\_count +- The number of crossover iterations performed +- Type: integer + +## qp\_iteration\_count +- The number of QP iterations performed +- Type: integer + +## primal\_solution\_status +- Comparison with [SolutionStatus](@ref) gives the status of the [primal](@ref Primal-values) solution +- Type: integer + +## dual\_solution\_status +- Comparison with [SolutionStatus](@ref) gives the status of the [dual](@ref Dual-values) solution +- Type: integer + +## basis\_validity +- Comparison with [BasisValidity](@ref) gives the status of any basis information +- Type: integer + +## objective\_function\_value +- The optimal value of the objective function +- Type: double + +## mip\_node\_count +- The number of nodes generated by the MIP solver +- Type: long integer + +## mip\_dual\_bound +- The [dual bound](@ref terminology-mip) for the MIP solver +- Type: double + +## mip\_gap +- The absolute value of the gap between the primal and bounds, relative to the primal bound. +- Type: double + +## max\_integrality\_violation +- The maximum deviation from an integer value over all the discrete variables +- Type: double + +## num\_primal\_infeasibilities +- The number of variables violating a bound by more than the [primal feasibility tolerance](@ref primal_feasibility_tolerance). +- Type: integer + +## max\_primal\_infeasibility +- The maximum violation of a bound on a variable +- Type: double + +## sum\_primal\_infeasibilities +- The sum of violations of bounds by variables +- Type: double + +## num\_dual\_infeasibilities +- The number of variables violating dual feasibility by more than the [dual feasibility tolerance](@ref dual_feasibility_tolerance). +- Type: integer + +## max\_dual\_infeasibility +- The maximum dual feasibility violation +- Type: double + +## sum\_dual\_infeasibilities +- The sum of dual feasibility violations +- Type: double + diff --git a/docs/src/structures/structs/HighsLinearObjective.md b/docs/src/structures/structs/HighsLinearObjective.md new file mode 100644 index 0000000000..9cc9f09d12 --- /dev/null +++ b/docs/src/structures/structs/HighsLinearObjective.md @@ -0,0 +1,11 @@ +# HighsLinearObjective + +A linear objective for a model is communicated via an instance of the HighsLinearObjective structure + +- weight: Scalar of type double - The weight of this objective when blending +- offset: Scalar of type double - The offset of this objective +- coefficients: Vector of type double - The coefficients of this objective +- abs\_tolerance: Scalar of type double - The absolute tolerance on this objective when performing lexicographic optimization +- rel\_tolerance: Scalar of type double - The relative tolerance on this objective when performing lexicographic optimization +- priority: Scalar of type HighsInt - The priority of this objective when performing lexicographic optimization + diff --git a/docs/src/structures/structs/HighsSolution.md b/docs/src/structures/structs/HighsSolution.md new file mode 100644 index 0000000000..5b9dbf6a73 --- /dev/null +++ b/docs/src/structures/structs/HighsSolution.md @@ -0,0 +1,10 @@ +# HighsSolution + +The solution of a model is communicated via an instance of the HighsSolution structure + +- value\_valid: Scalar of type bool - Indicates whether the column and row values are valid +- dual\_valid: Scalar of type bool - Indicates whether the column and row [duals](@ref Dual-values) are valid +- col\_value: Vector of type double - Values of the columns (variables) +- col\_dual: Vector of type double - Duals of the columns (variables) +- row\_value: Vector of type double - Values of the rows (constraints) +- row\_dual: Vector of type double - Duals of the rows (constraints) From 93762ae1869b3f07f106b5008e0f7827d7a7d853 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 22 Nov 2024 12:53:36 +0000 Subject: [PATCH 14/21] Added Structures to make.jl --- docs/make.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index f598979b78..c2d48c170b 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -65,12 +65,12 @@ Documenter.makedocs( "structures/classes/HighsHessian.md", "structures/classes/HighsModel.md", ], -# "Structures" => Any[ -# "structures/structs/HighsSolution.md", -# "structures/structs/HighsBasis.md", -# "structures/structs/HighsInfo.md", -# "structures/structs/HighsLinearObjective.md", -# ], + "Structures" => Any[ + "structures/structs/HighsSolution.md", + "structures/structs/HighsBasis.md", + "structures/structs/HighsInfo.md", + "structures/structs/HighsLinearObjective.md", + ], ], "Callbacks" => "callbacks.md", "Interfaces" => Any[ From 4c1fa212cfe3bb721e6094b1803049516f7f515e Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 22 Nov 2024 13:08:36 +0000 Subject: [PATCH 15/21] Added some maths --- docs/src/guide/further.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/src/guide/further.md b/docs/src/guide/further.md index 6e51fbb9f1..9c929eb115 100644 --- a/docs/src/guide/further.md +++ b/docs/src/guide/further.md @@ -110,7 +110,7 @@ a basis. Users can specify multiple linear objectives with respect to which HiGHS will optimize by either blending them, or by performing lexicographic optimization according to the truth of the -[blend_multi_objectives](@ref blend_multi_objectives) option. Each +[blend\_multi\_objectives](@ref blend_multi_objectives) option. Each linear objective is represented by the following data, held in the [HighsLinearObjective](@ref HighsLinearObjective) structure @@ -134,7 +134,7 @@ the `col_cost_` data in the incumbent model is ignored. ### Blending multiple linear objectives -When [blend_multi_objectives](@ref blend_multi_objectives) is `true`, +When [blend\_multi\_objectives](@ref blend_multi_objectives) is `true`, as it is by default, any `HighsLinearObjective` instances will be combined according to the `weight` values, and the resulting objective will be minimized. Hence, any objectives that should be maximized @@ -142,11 +142,18 @@ within the combination must have a negative `weight` value. ### Lexicographic optimization of multiple linear objectives -When [blend_multi_objectives](@ref blend_multi_objectives) is `false`, +When [blend\_multi\_objectives](@ref blend_multi_objectives) is `false`, HiGHS will optimize lexicographically with respect to any -`HighsLinearObjective` instances. This is carried out according to the -`priority` values in `HighsLinearObjective` instances. Note that all -priority values must be distinct. +`HighsLinearObjective` instances. This is carried out as follows, according to the +`priority` values in `HighsLinearObjective` instances. Note that _all +priority values must be distinct_. + +- Minimize/maximize with respect to the linear objective of highest priority value, according to whether its `weight` is positive/negative + +- Add a constraint to the model so that the value of the linear objective of highest priority satsifies a bound given by the values of `abs\_tolerance` and/or `rel\_tolerance`. + +-- If the objective was minimized to a value ``f^*``, then the constraint ensures that the this objective value is no creater than ``f^*+```abs\_tolerance` + From 9e7ac0a4a10307b951c15db18ac6f6b2ff4e212e Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 22 Nov 2024 13:53:56 +0000 Subject: [PATCH 16/21] Removed some unnecessary commas? --- docs/make.jl | 5 +++-- docs/src/structures/classes/index.md | 3 --- docs/src/structures/structs/index.md | 10 ++++++++++ 3 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 docs/src/structures/structs/index.md diff --git a/docs/make.jl b/docs/make.jl index c2d48c170b..93947c773c 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -63,13 +63,14 @@ Documenter.makedocs( "structures/classes/HighsSparseMatrix.md", "structures/classes/HighsLp.md", "structures/classes/HighsHessian.md", - "structures/classes/HighsModel.md", + "structures/classes/HighsModel.md" ], "Structures" => Any[ + "structures/structs/index.md", "structures/structs/HighsSolution.md", "structures/structs/HighsBasis.md", "structures/structs/HighsInfo.md", - "structures/structs/HighsLinearObjective.md", + "structures/structs/HighsLinearObjective.md" ], ], "Callbacks" => "callbacks.md", diff --git a/docs/src/structures/classes/index.md b/docs/src/structures/classes/index.md index f2f5ed4326..9277bc5ef8 100644 --- a/docs/src/structures/classes/index.md +++ b/docs/src/structures/classes/index.md @@ -6,8 +6,5 @@ The data members of fundamental classes in HiGHS are defined in this section. * [HighsLp](@ref) * [HighsHessian](@ref) * [HighsModel](@ref) - * [HighsSolution](@ref) - * [HighsBasis](@ref) - * [HighsInfo](@ref) Class data members for internal use only are not documented. diff --git a/docs/src/structures/structs/index.md b/docs/src/structures/structs/index.md new file mode 100644 index 0000000000..d8c763c2b3 --- /dev/null +++ b/docs/src/structures/structs/index.md @@ -0,0 +1,10 @@ +# [Overview](@id structs-overview) + +The data members of fundamental structs in HiGHS are defined in this section. + + * [HighsSolution](@ref) + * [HighsBasis](@ref) + * [HighsInfo](@ref) + * [HighsLinearObjective](@ref) + +Structure data members for internal use only are not documented. From 7dc5a14b9821aaeed87a7eead6d6bd01e39c370a Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 22 Nov 2024 14:52:02 +0000 Subject: [PATCH 17/21] Use the solution of one MIP to provide an integer feasible solution of the next in lexicographic optimization --- docs/src/guide/further.md | 15 +++++++++++++-- src/lp_data/HighsInterface.cpp | 33 +++++++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/docs/src/guide/further.md b/docs/src/guide/further.md index 9c929eb115..a01e29e37b 100644 --- a/docs/src/guide/further.md +++ b/docs/src/guide/further.md @@ -150,10 +150,21 @@ priority values must be distinct_. - Minimize/maximize with respect to the linear objective of highest priority value, according to whether its `weight` is positive/negative -- Add a constraint to the model so that the value of the linear objective of highest priority satsifies a bound given by the values of `abs\_tolerance` and/or `rel\_tolerance`. +- Add a constraint to the model so that the value of the linear objective of highest priority satsifies a bound given by the values of `abs_tolerance` and/or `rel_tolerance`. --- If the objective was minimized to a value ``f^*``, then the constraint ensures that the this objective value is no creater than ``f^*+```abs\_tolerance` +-- If the objective was minimized to a value ``f^*>=0``, then the constraint ensures that the this objective value is no greater than ``\min(f^*+```abs_tolerance```,~f^*(1+```rel_tolerance```))`. +-- If the objective was minimized to a value ``f^*<0``, then the constraint ensures that the this objective value is no greater than ``\min(f^*+```abs_tolerance```,~f^*(1-```rel_tolerance```))`. +-- If the objective was maximized to a value ``f^*>=0``, then the constraint ensures that the this objective value is no less than ``\max(f^*-```abs_tolerance```,~f^*(1-```rel_tolerance```))`. + +-- If the objective was maximized to a value ``f^*<0``, then the constraint ensures that the this objective value is no less than ``\max(f^*-```abs_tolerance```,~f^*(1+```rel_tolerance```))`. + +- Minimize/maximize with respect to the linear objective of next highest priority, and then add a corresponding objective constraint to the model, repeating until optimization with respect to the linear objective of lowest priority has taken place. + +Note + +- Negative values of `abs_tolerance` and `rel_tolerance` will be ignored. This is a convenient way of "switching off" a bounding technique that is not of interest. +- When the model is continuous, no dual information will be returned if there is more than one linear objective. diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 7f472a55fe..3e2676b74a 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -1755,7 +1755,6 @@ HighsStatus Highs::getDualRayInterface(bool& has_dual_ray, this->setOptionValue("presolve", kHighsOffString); this->setOptionValue("solve_relaxation", true); HighsStatus call_status = this->run(); - this->writeSolution("", kSolutionStylePretty); if (call_status != HighsStatus::kOk) return_status = call_status; has_dual_ray = ekk_instance_.status_.has_dual_ray; has_invert = ekk_instance_.status_.has_invert; @@ -3727,6 +3726,9 @@ HighsStatus Highs::multiobjectiveSolve() { const HighsInt original_lp_num_row = lp.num_row_; std::vector index(lp.num_col_); std::vector value(lp.num_col_); + // Use the solution of one MIP to provide an integer feasible + // solution of the next + HighsSolution solution; for (HighsInt iIx = 0; iIx < num_linear_objective; iIx++) { HighsInt priority = priority_objective[iIx].first; HighsInt iObj = priority_objective[iIx].second; @@ -3737,6 +3739,28 @@ HighsStatus Highs::multiobjectiveSolve() { lp.col_cost_ = linear_objective.coefficients; lp.sense_ = linear_objective.weight > 0 ? ObjSense::kMinimize : ObjSense::kMaximize; + if (lp.isMip() && solution.value_valid) { + HighsStatus set_solution_status = this->setSolution(solution); + if (set_solution_status == HighsStatus::kError) { + highsLogUser(options_.log_options, HighsLogType::kError, + "Failure to use one MIP to provide an integer feasible " + "solution of the next\n"); + return returnFromLexicographicOptimization(HighsStatus::kError, + original_lp_num_row); + } + bool valid, integral, feasible; + HighsStatus assess_primal_solution = + assessPrimalSolution(valid, integral, feasible); + if (!valid || !integral || !feasible) { + highsLogUser(options_.log_options, HighsLogType::kWarning, + "Failure to use one MIP to provide an integer feasible " + "solution of the next: " + "status is valid = %s, integral = %s, feasible = %s\n", + highsBoolToString(valid).c_str(), + highsBoolToString(integral).c_str(), + highsBoolToString(feasible).c_str()); + } + } HighsStatus solve_status = this->solve(); if (solve_status == HighsStatus::kError) return returnFromLexicographicOptimization(HighsStatus::kError, @@ -3748,8 +3772,13 @@ 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 + // the next MIP + solution.col_value = this->solution_.col_value; + solution.value_valid = true; + } // Add the constraint HighsInt nnz = 0; for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { From 23102c67bc8f88cf982bed3e9f44af09b3d9a98d Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 22 Nov 2024 17:36:01 +0000 Subject: [PATCH 18/21] Finished first draft of documentation; add C and Python API --- docs/src/guide/further.md | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/docs/src/guide/further.md b/docs/src/guide/further.md index a01e29e37b..91a4dc75ca 100644 --- a/docs/src/guide/further.md +++ b/docs/src/guide/further.md @@ -125,9 +125,9 @@ linear objective is represented by the following data, held in the Multi-objective optimization in HiGHS is defined by the following methods -- passLinearObjectives - Pass multiple linear objectives as their number `num_linear_objective` and pointer to a vector of `HighsLinearObjective` instances, overwriting any previous linear objectives -- addLinearObjective - Add a single `HighsLinearObjective` instance to any already stored in HiGHS -- clearLinearObjectives - Clears any linear objectives stored in HiGHS +- [passLinearObjectives](@ref Multi-objective-optimization] - Pass multiple linear objectives as their number `num_linear_objective` and pointer to a vector of `HighsLinearObjective` instances, overwriting any previous linear objectives +- [addLinearObjective](@ref Multi-objective-optimization] - Add a single `HighsLinearObjective` instance to any already stored in HiGHS +- [clearLinearObjectives](@ref Multi-objective-optimization] - Clears any linear objectives stored in HiGHS When there is at least one `HighsLinearObjective` instance in HiGHS, the `col_cost_` data in the incumbent model is ignored. @@ -148,23 +148,36 @@ HiGHS will optimize lexicographically with respect to any `priority` values in `HighsLinearObjective` instances. Note that _all priority values must be distinct_. -- Minimize/maximize with respect to the linear objective of highest priority value, according to whether its `weight` is positive/negative +* Minimize/maximize with respect to the linear objective of highest priority value, according to whether its `weight` is positive/negative -- Add a constraint to the model so that the value of the linear objective of highest priority satsifies a bound given by the values of `abs_tolerance` and/or `rel_tolerance`. +* Add a constraint to the model so that the value of the linear objective of highest priority satsifies a bound given by the values of `abs_tolerance` and/or `rel_tolerance`. --- If the objective was minimized to a value ``f^*>=0``, then the constraint ensures that the this objective value is no greater than ``\min(f^*+```abs_tolerance```,~f^*(1+```rel_tolerance```))`. + + If the objective was minimized to a value ``f^*>=0``, then the constraint ensures that the this objective value is no greater than +```math --- If the objective was minimized to a value ``f^*<0``, then the constraint ensures that the this objective value is no greater than ``\min(f^*+```abs_tolerance```,~f^*(1-```rel_tolerance```))`. +\min(f^*+`abs_tolerance`,~f^*[1+`rel_tolerance`]). +``` --- If the objective was maximized to a value ``f^*>=0``, then the constraint ensures that the this objective value is no less than ``\max(f^*-```abs_tolerance```,~f^*(1-```rel_tolerance```))`. + + If the objective was minimized to a value ``f^*<0``, then the constraint ensures that the this objective value is no greater than +```math +\min(f^*+`abs_tolerance`,~f^*[1-`rel_tolerance`]). +``` --- If the objective was maximized to a value ``f^*<0``, then the constraint ensures that the this objective value is no less than ``\max(f^*-```abs_tolerance```,~f^*(1+```rel_tolerance```))`. + + If the objective was maximized to a value ``f^*>=0``, then the constraint ensures that the this objective value is no less than +```math +\max(f^*-`abs_tolerance`,~f^*[1-`rel_tolerance`]). +``` -- Minimize/maximize with respect to the linear objective of next highest priority, and then add a corresponding objective constraint to the model, repeating until optimization with respect to the linear objective of lowest priority has taken place. + + If the objective was maximized to a value ``f^*<0``, then the constraint ensures that the this objective value is no less than +```math +\max(f^*-`abs_tolerance`,~f^*[1+`rel_tolerance`]). +``` + +* Minimize/maximize with respect to the linear objective of next highest priority, and then add a corresponding objective constraint to the model, repeating until optimization with respect to the linear objective of lowest priority has taken place. Note -- Negative values of `abs_tolerance` and `rel_tolerance` will be ignored. This is a convenient way of "switching off" a bounding technique that is not of interest. -- When the model is continuous, no dual information will be returned if there is more than one linear objective. +* Negative values of `abs_tolerance` and `rel_tolerance` will be ignored. This is a convenient way of "switching off" a bounding technique that is not of interest. +* When the model is continuous, no dual information will be returned if there is more than one linear objective. From cda67caa36783147a0f8b9d656910b7435fac7a9 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 22 Nov 2024 18:46:40 +0000 Subject: [PATCH 19/21] Debugging the C API for multi-objective optimization --- check/TestCAPI.c | 114 +++++++++++++++++++++++++++------ src/interfaces/highs_c_api.cpp | 40 ++++++++++++ src/interfaces/highs_c_api.h | 16 ++++- 3 files changed, 151 insertions(+), 19 deletions(-) diff --git a/check/TestCAPI.c b/check/TestCAPI.c index e5848f67a1..9b94ccc126 100644 --- a/check/TestCAPI.c +++ b/check/TestCAPI.c @@ -1855,6 +1855,83 @@ void test_getModel() { Highs_destroy(highs); } +void test_multiObjective() { + void* highs; + highs = Highs_create(); + const double inf = Highs_getInfinity(highs); + + HighsInt num_col = 2; + HighsInt num_row = 3; + HighsInt num_nz = num_col * num_row; + HighsInt a_format = kHighsMatrixFormatColwise; + HighsInt sense = kHighsObjSenseMaximize; + double offset = -1; + double col_cost[2] = {1, 1}; + double col_lower[2] = {0, 0}; + double col_upper[2] = {inf, inf}; + double row_lower[3] = {-inf, -inf, -inf}; + double row_upper[3] = {18, 8, 14}; + HighsInt a_start[3] = {0, 3, 6}; + HighsInt a_index[6] = {0, 1, 2, 0, 1, 2}; + double a_value[6] = {3, 1, 1, 1, 1, 2}; + HighsInt integrality[2] = {kHighsVarTypeInteger, kHighsVarTypeInteger}; + + // 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 abs_tolerance = 0; + double rel_tolerance = 0; + HighsInt priority = 10; + return_status = Highs_addLinearObjective(highs, weight, linear_objective_offset, coefficients, abs_tolerance, rel_tolerance, priority); + assert(return_status == kHighsStatusOk); + + weight = 1e-4; + linear_objective_offset = 0; + coefficients[0] = 1; + coefficients[1] = 0; + priority = 0; + return_status = Highs_addLinearObjective(highs, weight, linear_objective_offset, coefficients, abs_tolerance, rel_tolerance, priority); + assert(return_status == kHighsStatusOk); + + return_status = Highs_run(highs); + assert(return_status == kHighsStatusOk); + model_status = Highs_getModelStatus(highs); + assert(model_status == kHighsModelStatusOptimal); + + Highs_writeSolutionPretty(highs, ""); + double* col_value = (double*)malloc(sizeof(double) * num_col); + 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); + + Highs_setBoolOptionValue(highs, "blend_multi_objectives", 0); + + // double weight = {}; + // double offset = {}; + // double coefficients = {}; + // double abs_tolerance = {}; + // double rel_tolerance = {}; + // HighsInt priority = {}; + + Highs_destroy(highs); + free(col_value); +} + /* The horrible C in this causes problems in some of the CI tests, so suppress thius test until the C has been improved @@ -1901,24 +1978,25 @@ iteration_count1); assertLogical("Dual", logic); } */ int main() { - minimal_api_illegal_lp(); - test_callback(); - version_api(); - full_api(); - minimal_api_lp(); - minimal_api_mip(); - minimal_api_qp(); - full_api_options(); - full_api_lp(); - full_api_mip(); - full_api_qp(); - pass_presolve_get_lp(); - options(); - test_getColsByRange(); - test_passHessian(); - test_ranging(); - test_feasibilityRelaxation(); - test_getModel(); + // minimal_api_illegal_lp(); + // test_callback(); + // version_api(); + // full_api(); + // minimal_api_lp(); + // minimal_api_mip(); + // minimal_api_qp(); + // full_api_options(); + // full_api_lp(); + // full_api_mip(); + // full_api_qp(); + // pass_presolve_get_lp(); + // options(); + // test_getColsByRange(); + // test_passHessian(); + // test_ranging(); + // test_feasibilityRelaxation(); + // test_getModel(); + test_multiObjective(); return 0; } // test_setSolution(); diff --git a/src/interfaces/highs_c_api.cpp b/src/interfaces/highs_c_api.cpp index 8dad0e5216..54ca0ff74b 100644 --- a/src/interfaces/highs_c_api.cpp +++ b/src/interfaces/highs_c_api.cpp @@ -291,6 +291,46 @@ 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 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); + 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_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++) + linear_objective.coefficients.push_back(coefficients[iCol]); + linear_objective.abs_tolerance = abs_tolerance; + linear_objective.rel_tolerance = rel_tolerance; + linear_objective.priority = priority; + linear_objective.weight = weight; + return HighsInt(((Highs*)highs)->addLinearObjective(linear_objective)); +} + +HighsInt Highs_clearLinearObjectives(const void* highs) { + return HighsInt(((Highs*)highs)->clearLinearObjectives()); +} + HighsInt Highs_passRowName(const void* highs, const HighsInt row, const char* name) { return (HighsInt)((Highs*)highs)->passRowName(row, std::string(name)); diff --git a/src/interfaces/highs_c_api.h b/src/interfaces/highs_c_api.h index a59ec64a17..ba5255d98f 100644 --- a/src/interfaces/highs_c_api.h +++ b/src/interfaces/highs_c_api.h @@ -557,6 +557,20 @@ 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_clearLinearObjectives(const void* highs); /** * Pass the name of a row. * @@ -945,7 +959,7 @@ HighsInt Highs_getDualRay(const void* highs, HighsInt* has_dual_ray, * filled with the unboundedness * direction. */ -HighsInt getDualUnboundednessDirection( +HighsInt Highs_getDualUnboundednessDirection( const void* highs, HighsInt* has_dual_unboundedness_direction, double* dual_unboundedness_direction_value); From 3c71c597666134e46d74435d61baf29cd6b4fbe5 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sat, 23 Nov 2024 14:11:39 +0000 Subject: [PATCH 20/21] Added C API and logging during multi-objective optimization --- check/TestCAPI.c | 65 ++++++++++++++++++++------- check/TestMultiObjective.cpp | 2 +- src/Highs.h | 4 +- src/interfaces/highs_c_api.cpp | 37 ++++++++++------ src/interfaces/highs_c_api.h | 31 ++++++++----- src/lp_data/HighsInterface.cpp | 81 ++++++++++++++++++++++++++++++++++ 6 files changed, 177 insertions(+), 43 deletions(-) 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); From 0493f8c274be1b9204a156f2920fbef0522973c1 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sat, 23 Nov 2024 17:27:10 +0000 Subject: [PATCH 21/21] highspy API for multi-objective optimization is done and checked; formatted --- check/TestCAPI.c | 36 ++++++++++++------------ docs/src/interfaces/python/example-py.md | 1 - examples/call_highs_from_python.py | 6 +++- src/highs_bindings.cpp | 14 +++++++++ src/highspy/__init__.py | 1 + src/lp_data/HighsInterface.cpp | 1 - 6 files changed, 38 insertions(+), 21 deletions(-) diff --git a/check/TestCAPI.c b/check/TestCAPI.c index e16fd5b0aa..d75cc71305 100644 --- a/check/TestCAPI.c +++ b/check/TestCAPI.c @@ -2013,24 +2013,24 @@ iteration_count1); assertLogical("Dual", logic); } */ int main() { - // minimal_api_illegal_lp(); - // test_callback(); - // version_api(); - // full_api(); - // minimal_api_lp(); - // minimal_api_mip(); - // minimal_api_qp(); - // full_api_options(); - // full_api_lp(); - // full_api_mip(); - // full_api_qp(); - // pass_presolve_get_lp(); - // options(); - // test_getColsByRange(); - // test_passHessian(); - // test_ranging(); - // test_feasibilityRelaxation(); - // test_getModel(); + minimal_api_illegal_lp(); + test_callback(); + version_api(); + full_api(); + minimal_api_lp(); + minimal_api_mip(); + minimal_api_qp(); + full_api_options(); + full_api_lp(); + full_api_mip(); + full_api_qp(); + pass_presolve_get_lp(); + options(); + test_getColsByRange(); + test_passHessian(); + test_ranging(); + test_feasibilityRelaxation(); + test_getModel(); test_multiObjective(); return 0; } diff --git a/docs/src/interfaces/python/example-py.md b/docs/src/interfaces/python/example-py.md index 6fce25e870..fd4eef038b 100644 --- a/docs/src/interfaces/python/example-py.md +++ b/docs/src/interfaces/python/example-py.md @@ -239,7 +239,6 @@ print('Basis validity = ', h.basisValidityToString(info.basis_validity)) ## Multi-objective optimization -* `passLinearObjectives` * `addLinearObjective` * `clearLinearObjectives` diff --git a/examples/call_highs_from_python.py b/examples/call_highs_from_python.py index 2ff88f281b..61e6356f3e 100644 --- a/examples/call_highs_from_python.py +++ b/examples/call_highs_from_python.py @@ -493,4 +493,8 @@ def user_interrupt_callback( print("row_bound:", iis.row_bound) print("col_index:", iis.col_index) -print("col_bound:", iis.col_bound) \ No newline at end of file +print("col_bound:", iis.col_bound) + +# ~~~ +# Clear so that incumbent model is empty +h.clear() diff --git a/src/highs_bindings.cpp b/src/highs_bindings.cpp index b8f7e249cf..64db9172d3 100644 --- a/src/highs_bindings.cpp +++ b/src/highs_bindings.cpp @@ -159,6 +159,10 @@ HighsStatus highs_passHessianPointers(Highs* h, const HighsInt dim, q_value_ptr); } +HighsStatus highs_addLinearObjective(Highs* h, const HighsLinearObjective& linear_objective) { + return h->addLinearObjective(linear_objective, -1); +} + HighsStatus highs_postsolve(Highs* h, const HighsSolution& solution, const HighsBasis& basis) { return h->postsolve(solution, basis); @@ -939,6 +943,8 @@ PYBIND11_MODULE(_core, m) { .def("passModel", &highs_passLpPointers) .def("passHessian", &highs_passHessian) .def("passHessian", &highs_passHessianPointers) + .def("addLinearObjective", &highs_addLinearObjective) + .def("clearLinearObjectives", &Highs::clearLinearObjectives) .def("passColName", &Highs::passColName) .def("passRowName", &Highs::passRowName) .def("readModel", &Highs::readModel) @@ -1148,6 +1154,14 @@ PYBIND11_MODULE(_core, m) { .def(py::init<>()) .def_readwrite("simplex_time", &HighsIisInfo::simplex_time) .def_readwrite("simplex_iterations", &HighsIisInfo::simplex_iterations); + py::class_(m, "HighsLinearObjective") + .def(py::init<>()) + .def_readwrite("weight", &HighsLinearObjective::weight) + .def_readwrite("offset", &HighsLinearObjective::offset) + .def_readwrite("coefficients", &HighsLinearObjective::coefficients) + .def_readwrite("abs_tolerance", &HighsLinearObjective::abs_tolerance) + .def_readwrite("rel_tolerance", &HighsLinearObjective::rel_tolerance) + .def_readwrite("priority", &HighsLinearObjective::priority); // constants m.attr("kHighsInf") = kHighsInf; m.attr("kHighsIInf") = kHighsIInf; diff --git a/src/highspy/__init__.py b/src/highspy/__init__.py index 9e3c03a0e1..e3044e8ccb 100644 --- a/src/highspy/__init__.py +++ b/src/highspy/__init__.py @@ -29,6 +29,7 @@ HighsRanging, kHighsInf, kHighsIInf, + HighsLinearObjective, HIGHS_VERSION_MAJOR, HIGHS_VERSION_MINOR, HIGHS_VERSION_PATCH, diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 3669f69f78..1c7ad16b9f 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -3836,7 +3836,6 @@ 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