From d4a4a305c96cc1d366fa233e5e109adea471202a Mon Sep 17 00:00:00 2001 From: Julian Hall Date: Tue, 19 Nov 2024 16:07:06 +0000 Subject: [PATCH 01/59] Created writeLocalModel unit test, assessHessianDimensions, and fixed bug in lpDimensionsOk --- check/TestFilereader.cpp | 43 +++++++++++++++++++++++++++++++++ src/lp_data/Highs.cpp | 11 ++++++++- src/lp_data/HighsLpUtils.cpp | 2 +- src/model/HighsHessianUtils.cpp | 21 ++++++++++------ 4 files changed, 67 insertions(+), 10 deletions(-) diff --git a/check/TestFilereader.cpp b/check/TestFilereader.cpp index 915b73a25c..551718ab79 100644 --- a/check/TestFilereader.cpp +++ b/check/TestFilereader.cpp @@ -11,6 +11,7 @@ #include "lp_data/HighsLpUtils.h" const bool dev_run = false; +const double inf = kHighsInf; TEST_CASE("filereader-edge-cases", "[highs_filereader]") { std::string model = ""; @@ -357,3 +358,45 @@ TEST_CASE("filereader-dD2e", "[highs_filereader]") { // double objective_value = highs.getInfo().objective_function_value; // REQUIRE(objective_value == optimal_objective_value); // } + +TEST_CASE("writeLocalModel", "[highs_filereader]") { + Highs h; + // h.setOptionValue("output_flag", dev_run); + std::string write_model_file = "foo.mps"; + HighsModel model; + HighsLp& lp = model.lp_;; + lp.num_col_ = 2; + lp.num_row_ = 3; + lp.col_cost_ = {8, 10}; + lp.row_lower_ = {7, 12, 6}; + lp.row_upper_ = {inf, inf, inf}; + lp.a_matrix_.start_ = {0, 3, 6}; + lp.a_matrix_.index_ = {0, 1, 2, 0, 1, 2}; + lp.a_matrix_.value_ = {2, 3, 2, 2, 4, 1}; + + // if (dev_run) + printf("\nModel with no column lower or upper bounds\n"); + REQUIRE(h.writeLocalModel(model, write_model_file) == HighsStatus::kError); + lp.col_lower_ = {0, 0}; + + // if (dev_run) + printf("\nModel with no column upper bounds\n"); + REQUIRE(h.writeLocalModel(model, write_model_file) == HighsStatus::kError); + lp.col_upper_ = {inf, inf}; + + // Model has no dimensions for a_matrix_, but these are set in + // writeLocalModel. + printf("\nModel with no column or row names\n"); + REQUIRE(h.writeLocalModel(model, write_model_file) == HighsStatus::kWarning); + lp.col_names_ = {"C0", "C1"}; + lp.row_names_ = {"R0", "R1", "R2"}; + + printf("\nModel with column and row names\n"); + REQUIRE(h.writeLocalModel(model, write_model_file) == HighsStatus::kOk); + + // Introduce illegal index + lp.a_matrix_.index_ = {0, -1, 2, 0, 1, 2}; + REQUIRE(h.writeLocalModel(model, write_model_file) == HighsStatus::kError); + + std::remove(write_model_file.c_str()); +} diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index c1c32546a6..dd5049b876 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -706,8 +706,17 @@ HighsStatus Highs::writeLocalModel(HighsModel& model, const std::string& filename) { HighsStatus return_status = HighsStatus::kOk; + HighsLp& lp = model.lp_; + // Dimensions in a_matrix_ may not be set, so take them from lp + lp.setMatrixDimensions(); // Ensure that the LP is column-wise - model.lp_.ensureColwise(); + lp.ensureColwise(); + // Ensure that the dimensions are OK + if (!lpDimensionsOk("writeLocalModel", lp, options_.log_options)) return HighsStatus::kError; + if (model.hessian_.dim_ > 0) { + HighsStatus call_status = assessHessianDimensions(options_, model.hessian_); + if (call_status == HighsStatus::kError) return call_status; + } // Check for repeated column or row names that would corrupt the file if (model.lp_.col_hash_.hasDuplicate(model.lp_.col_names_)) { highsLogUser(options_.log_options, HighsLogType::kError, diff --git a/src/lp_data/HighsLpUtils.cpp b/src/lp_data/HighsLpUtils.cpp index 89b31e04aa..af8c39c822 100644 --- a/src/lp_data/HighsLpUtils.cpp +++ b/src/lp_data/HighsLpUtils.cpp @@ -130,7 +130,7 @@ bool lpDimensionsOk(std::string message, const HighsLp& lp, HighsInt col_upper_size = lp.col_upper_.size(); bool legal_col_cost_size = col_cost_size >= num_col; bool legal_col_lower_size = col_lower_size >= num_col; - bool legal_col_upper_size = col_lower_size >= num_col; + bool legal_col_upper_size = col_upper_size >= num_col; if (!legal_col_cost_size) highsLogUser(log_options, HighsLogType::kError, "LP dimension validation (%s) fails on col_cost.size() = %d < " diff --git a/src/model/HighsHessianUtils.cpp b/src/model/HighsHessianUtils.cpp index 6e809cc759..aad2a8c900 100644 --- a/src/model/HighsHessianUtils.cpp +++ b/src/model/HighsHessianUtils.cpp @@ -28,14 +28,8 @@ HighsStatus assessHessian(HighsHessian& hessian, const HighsOptions& options) { HighsStatus return_status = HighsStatus::kOk; HighsStatus call_status; - // Assess the Hessian dimensions and vector sizes, returning on error - vector hessian_p_end; - const bool partitioned = false; - call_status = assessMatrixDimensions( - options.log_options, hessian.dim_, partitioned, hessian.start_, - hessian_p_end, hessian.index_, hessian.value_); - return_status = interpretCallStatus(options.log_options, call_status, - return_status, "assessMatrixDimensions"); + return_status = interpretCallStatus(options.log_options, assessHessianDimensions(options, hessian), + return_status, "assessHessianDimensions"); if (return_status == HighsStatus::kError) return return_status; // If the Hessian has no columns there is nothing left to test @@ -110,6 +104,17 @@ HighsStatus assessHessian(HighsHessian& hessian, const HighsOptions& options) { return return_status; } +HighsStatus assessHessianDimensions(const HighsOptions& options, + HighsHessian& hessian) { + if (hessian.dim_ == 0) return HighsStatus::kOk; + + // Assess the Hessian dimensions and vector sizes + vector hessian_p_end; + const bool partitioned = false; + return assessMatrixDimensions(options.log_options, hessian.dim_, partitioned, hessian.start_, + hessian_p_end, hessian.index_, hessian.value_); +} + void completeHessianDiagonal(const HighsOptions& options, HighsHessian& hessian) { // Count the number of missing diagonal entries From f94a2c7dfe21bf01878051ff7f854b975101587e Mon Sep 17 00:00:00 2001 From: Julian Hall Date: Tue, 19 Nov 2024 16:43:41 +0000 Subject: [PATCH 02/59] Added tests for starts and indices to HighsSparseMatrix, and using them in writeLocalModel --- check/TestFilereader.cpp | 36 ++++++++++++++------ src/lp_data/Highs.cpp | 18 ++++++++-- src/model/HighsHessianUtils.cpp | 8 +++-- src/util/HighsMatrixUtils.cpp | 2 +- src/util/HighsSparseMatrix.cpp | 59 +++++++++++++++++++++++++++++++++ src/util/HighsSparseMatrix.h | 3 ++ 6 files changed, 110 insertions(+), 16 deletions(-) diff --git a/check/TestFilereader.cpp b/check/TestFilereader.cpp index 551718ab79..63fe50e8a2 100644 --- a/check/TestFilereader.cpp +++ b/check/TestFilereader.cpp @@ -361,10 +361,11 @@ TEST_CASE("filereader-dD2e", "[highs_filereader]") { TEST_CASE("writeLocalModel", "[highs_filereader]") { Highs h; - // h.setOptionValue("output_flag", dev_run); + h.setOptionValue("output_flag", dev_run); std::string write_model_file = "foo.mps"; HighsModel model; - HighsLp& lp = model.lp_;; + HighsLp& lp = model.lp_; + ; lp.num_col_ = 2; lp.num_row_ = 3; lp.col_cost_ = {8, 10}; @@ -374,29 +375,44 @@ TEST_CASE("writeLocalModel", "[highs_filereader]") { lp.a_matrix_.index_ = {0, 1, 2, 0, 1, 2}; lp.a_matrix_.value_ = {2, 3, 2, 2, 4, 1}; - // if (dev_run) - printf("\nModel with no column lower or upper bounds\n"); + if (dev_run) printf("\nModel with no column lower or upper bounds\n"); REQUIRE(h.writeLocalModel(model, write_model_file) == HighsStatus::kError); lp.col_lower_ = {0, 0}; - // if (dev_run) - printf("\nModel with no column upper bounds\n"); + if (dev_run) printf("\nModel with no column upper bounds\n"); REQUIRE(h.writeLocalModel(model, write_model_file) == HighsStatus::kError); lp.col_upper_ = {inf, inf}; // Model has no dimensions for a_matrix_, but these are set in - // writeLocalModel. - printf("\nModel with no column or row names\n"); + // writeLocalModel. + if (dev_run) printf("\nModel with no column or row names\n"); REQUIRE(h.writeLocalModel(model, write_model_file) == HighsStatus::kWarning); lp.col_names_ = {"C0", "C1"}; lp.row_names_ = {"R0", "R1", "R2"}; - printf("\nModel with column and row names\n"); + if (dev_run) printf("\nModel with column and row names\n"); REQUIRE(h.writeLocalModel(model, write_model_file) == HighsStatus::kOk); + // Introduce illegal start + if (dev_run) printf("\nModel with start entry > num_nz\n"); + lp.a_matrix_.start_ = {0, 7, 6}; + REQUIRE(h.writeLocalModel(model, write_model_file) == HighsStatus::kError); + + // Introduce illegal start + if (dev_run) printf("\nModel with start entry -1\n"); + lp.a_matrix_.start_ = {0, -1, 6}; + REQUIRE(h.writeLocalModel(model, write_model_file) == HighsStatus::kError); + lp.a_matrix_.start_ = {0, 3, 6}; + // Introduce illegal index + if (dev_run) printf("\nModel with index entry -1\n"); lp.a_matrix_.index_ = {0, -1, 2, 0, 1, 2}; REQUIRE(h.writeLocalModel(model, write_model_file) == HighsStatus::kError); - + + // Introduce illegal index + if (dev_run) printf("\nModel with index entry 3 >= num_row\n"); + lp.a_matrix_.index_ = {0, 1, 3, 0, 1, 2}; + REQUIRE(h.writeLocalModel(model, write_model_file) == HighsStatus::kError); + std::remove(write_model_file.c_str()); } diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index dd5049b876..67c6d44e33 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -705,18 +705,32 @@ HighsStatus Highs::writePresolvedModel(const std::string& filename) { HighsStatus Highs::writeLocalModel(HighsModel& model, const std::string& filename) { HighsStatus return_status = HighsStatus::kOk; + HighsStatus call_status; HighsLp& lp = model.lp_; // Dimensions in a_matrix_ may not be set, so take them from lp lp.setMatrixDimensions(); + // Ensure that the LP is column-wise lp.ensureColwise(); + // Ensure that the dimensions are OK - if (!lpDimensionsOk("writeLocalModel", lp, options_.log_options)) return HighsStatus::kError; + if (!lpDimensionsOk("writeLocalModel", lp, options_.log_options)) + return HighsStatus::kError; + if (model.hessian_.dim_ > 0) { - HighsStatus call_status = assessHessianDimensions(options_, model.hessian_); + call_status = assessHessianDimensions(options_, model.hessian_); if (call_status == HighsStatus::kError) return call_status; } + + // Check that the matrix starts are OK + call_status = lp.a_matrix_.assessStart(options_.log_options); + if (call_status == HighsStatus::kError) return call_status; + + // Check that the matrix indices are within bounds + call_status = lp.a_matrix_.assessIndexBounds(options_.log_options); + if (call_status == HighsStatus::kError) return call_status; + // Check for repeated column or row names that would corrupt the file if (model.lp_.col_hash_.hasDuplicate(model.lp_.col_names_)) { highsLogUser(options_.log_options, HighsLogType::kError, diff --git a/src/model/HighsHessianUtils.cpp b/src/model/HighsHessianUtils.cpp index aad2a8c900..56a68f1543 100644 --- a/src/model/HighsHessianUtils.cpp +++ b/src/model/HighsHessianUtils.cpp @@ -28,7 +28,8 @@ HighsStatus assessHessian(HighsHessian& hessian, const HighsOptions& options) { HighsStatus return_status = HighsStatus::kOk; HighsStatus call_status; - return_status = interpretCallStatus(options.log_options, assessHessianDimensions(options, hessian), + return_status = interpretCallStatus(options.log_options, + assessHessianDimensions(options, hessian), return_status, "assessHessianDimensions"); if (return_status == HighsStatus::kError) return return_status; @@ -111,8 +112,9 @@ HighsStatus assessHessianDimensions(const HighsOptions& options, // Assess the Hessian dimensions and vector sizes vector hessian_p_end; const bool partitioned = false; - return assessMatrixDimensions(options.log_options, hessian.dim_, partitioned, hessian.start_, - hessian_p_end, hessian.index_, hessian.value_); + return assessMatrixDimensions(options.log_options, hessian.dim_, partitioned, + hessian.start_, hessian_p_end, hessian.index_, + hessian.value_); } void completeHessianDiagonal(const HighsOptions& options, diff --git a/src/util/HighsMatrixUtils.cpp b/src/util/HighsMatrixUtils.cpp index b55f4df79a..7b70695065 100644 --- a/src/util/HighsMatrixUtils.cpp +++ b/src/util/HighsMatrixUtils.cpp @@ -68,7 +68,7 @@ HighsStatus assessMatrix( // // Check whether the first start is zero if (matrix_start[0]) { - highsLogUser(log_options, HighsLogType::kWarning, + highsLogUser(log_options, HighsLogType::kError, "%s matrix start vector begins with %" HIGHSINT_FORMAT " rather than 0\n", matrix_name.c_str(), matrix_start[0]); diff --git a/src/util/HighsSparseMatrix.cpp b/src/util/HighsSparseMatrix.cpp index a493d2e7ad..d063f0468e 100644 --- a/src/util/HighsSparseMatrix.cpp +++ b/src/util/HighsSparseMatrix.cpp @@ -722,6 +722,65 @@ void HighsSparseMatrix::deleteRows( this->num_row_ = new_num_row; } +HighsStatus HighsSparseMatrix::assessStart(const HighsLogOptions& log_options) { + // Identify main dimensions + HighsInt vec_dim; + HighsInt num_vec; + if (this->isColwise()) { + vec_dim = this->num_row_; + num_vec = this->num_col_; + } else { + vec_dim = this->num_col_; + num_vec = this->num_row_; + } + if (this->start_[0]) { + highsLogUser(log_options, HighsLogType::kError, + "Matrix start[0] = %d, not 0\n", int(this->start_[0])); + return HighsStatus::kError; + } + HighsInt num_nz = this->numNz(); + for (HighsInt iVec = 1; iVec < num_vec; iVec++) { + if (this->start_[iVec] < this->start_[iVec - 1]) { + highsLogUser(log_options, HighsLogType::kError, + "Matrix start[%d] = %d > %d = start[%d]\n", int(iVec), + int(this->start_[iVec]), int(this->start_[iVec - 1]), + int(iVec - 1)); + return HighsStatus::kError; + } + if (this->start_[iVec] > num_nz) { + highsLogUser(log_options, HighsLogType::kError, + "Matrix start[%d] = %d > %d = number of nonzeros\n", + int(iVec), int(this->start_[iVec]), int(num_nz)); + return HighsStatus::kError; + } + } + return HighsStatus::kOk; +} + +HighsStatus HighsSparseMatrix::assessIndexBounds( + const HighsLogOptions& log_options) { + // Identify main dimensions + HighsInt vec_dim; + HighsInt num_vec; + if (this->isColwise()) { + vec_dim = this->num_row_; + // num_vec = this->num_col_; + } else { + vec_dim = this->num_col_; + // num_vec = this->num_row_; + } + HighsInt num_nz = this->numNz(); + for (HighsInt iEl = 1; iEl < num_nz; iEl++) { + if (this->index_[iEl] < 0 || this->index_[iEl] >= vec_dim) { + highsLogUser(log_options, HighsLogType::kError, + "Matrix index[%d] = %d is not in legal range of [0, %d)\n", + int(iEl), int(this->index_[iEl]), vec_dim); + return HighsStatus::kError; + } + } + return HighsStatus::kOk; +} + HighsStatus HighsSparseMatrix::assess(const HighsLogOptions& log_options, const std::string matrix_name, const double small_matrix_value, diff --git a/src/util/HighsSparseMatrix.h b/src/util/HighsSparseMatrix.h index 7333a37e64..9694183191 100644 --- a/src/util/HighsSparseMatrix.h +++ b/src/util/HighsSparseMatrix.h @@ -66,6 +66,9 @@ class HighsSparseMatrix { void deleteRows(const HighsIndexCollection& index_collection); HighsStatus assessDimensions(const HighsLogOptions& log_options, const std::string matrix_name); + HighsStatus assessStart(const HighsLogOptions& log_options); + HighsStatus assessIndexBounds(const HighsLogOptions& log_options); + HighsStatus assess(const HighsLogOptions& log_options, const std::string matrix_name, const double small_matrix_value, From b3d1b61362ac014533ddc1c91745e53adeb2e30c Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 20 Nov 2024 11:36:24 +0000 Subject: [PATCH 03/59] 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 04/59] 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 05/59] 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 06/59] 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 07/59] 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 08/59] 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 09/59] 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 10/59] 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 11/59] 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 12/59] 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 8b7f707829bec5ecb383ee12f097a97e9e0cc17b Mon Sep 17 00:00:00 2001 From: fwesselm Date: Fri, 22 Nov 2024 11:15:35 +0100 Subject: [PATCH 13/59] WIP --- src/mip/HighsCutGeneration.cpp | 7 +++ src/mip/HighsPathSeparator.cpp | 5 +- src/mip/HighsTransformedLp.cpp | 96 +++++++++++++++++----------------- 3 files changed, 59 insertions(+), 49 deletions(-) diff --git a/src/mip/HighsCutGeneration.cpp b/src/mip/HighsCutGeneration.cpp index 1417c777b7..0db97b9e52 100644 --- a/src/mip/HighsCutGeneration.cpp +++ b/src/mip/HighsCutGeneration.cpp @@ -1184,6 +1184,11 @@ bool HighsCutGeneration::generateCut(HighsTransformedLp& transLp, } } while (false); + // save data that might otherwise be overwritten when calling the cmir + // separator + bool saveIntegalSupport = integralSupport; + bool saveIntegralCoefficients = integralCoefficients; + double minMirEfficacy = minEfficacy; if (success) { double violation = -double(rhs); @@ -1239,6 +1244,8 @@ bool HighsCutGeneration::generateCut(HighsTransformedLp& transLp, complementation.clear(); inds = inds_.data(); vals = vals_.data(); + integralSupport = saveIntegalSupport; + integralCoefficients = saveIntegralCoefficients; } else // neither cmir nor lifted cut successful return false; diff --git a/src/mip/HighsPathSeparator.cpp b/src/mip/HighsPathSeparator.cpp index 0989bd098c..4bbc0e94f8 100644 --- a/src/mip/HighsPathSeparator.cpp +++ b/src/mip/HighsPathSeparator.cpp @@ -351,16 +351,17 @@ void HighsPathSeparator::separateLpSolution(HighsLpRelaxation& lpRelaxation, if (addedSubstitutionRows) continue; + // generate cut double rhs = 0; - success = cutGen.generateCut(transLp, baseRowInds, baseRowVals, rhs); lpAggregator.getCurrentAggregation(baseRowInds, baseRowVals, true); if (!aggregatedPath.empty() || bestOutArcCol != -1 || bestInArcCol != -1) aggregatedPath.emplace_back(baseRowInds, baseRowVals); - rhs = 0; + // generate reverse cut + rhs = 0; success |= cutGen.generateCut(transLp, baseRowInds, baseRowVals, rhs); if (success || (bestOutArcCol == -1 && bestInArcCol == -1)) break; diff --git a/src/mip/HighsTransformedLp.cpp b/src/mip/HighsTransformedLp.cpp index 16b529a4cb..284a13069a 100644 --- a/src/mip/HighsTransformedLp.cpp +++ b/src/mip/HighsTransformedLp.cpp @@ -180,31 +180,24 @@ bool HighsTransformedLp::transform(std::vector& vals, if (lprelaxation.isColIntegral(col)) { if (lb == -kHighsInf || ub == kHighsInf) integersPositive = false; - bool useVbd = false; - if (ub - lb > 1.5 && boundDist[col] == 0.0 && simpleLbDist[col] != 0 && - simpleUbDist[col] != 0) { - if (bestVlb[col].first == -1 || - ubDist[col] < lbDist[col] - mip.mipdata_->feastol) { - assert(bestVub[col].first != -1); - boundTypes[col] = BoundType::kVariableUb; - useVbd = true; - } else if (bestVub[col].first == -1 || - lbDist[col] < ubDist[col] - mip.mipdata_->feastol) { - assert(bestVlb[col].first != -1); - boundTypes[col] = BoundType::kVariableLb; - useVbd = true; - } else if (vals[i] > 0) { - assert(bestVub[col].first != -1); - boundTypes[col] = BoundType::kVariableUb; - useVbd = true; - } else { - assert(bestVlb[col].first != -1); - boundTypes[col] = BoundType::kVariableLb; - useVbd = true; - } + if (ub - lb <= 1.5 || boundDist[col] != 0.0 || simpleLbDist[col] == 0 || + simpleUbDist[col] == 0) + continue; + if (bestVlb[col].first == -1 || + ubDist[col] < lbDist[col] - mip.mipdata_->feastol) { + assert(bestVub[col].first != -1); + boundTypes[col] = BoundType::kVariableUb; + } else if (bestVub[col].first == -1 || + lbDist[col] < ubDist[col] - mip.mipdata_->feastol) { + assert(bestVlb[col].first != -1); + boundTypes[col] = BoundType::kVariableLb; + } else if (vals[i] > 0) { + assert(bestVub[col].first != -1); + boundTypes[col] = BoundType::kVariableUb; + } else { + assert(bestVlb[col].first != -1); + boundTypes[col] = BoundType::kVariableLb; } - - if (!useVbd) continue; } else { if (lbDist[col] < ubDist[col] - mip.mipdata_->feastol) { if (bestVlb[col].first == -1) @@ -242,6 +235,7 @@ bool HighsTransformedLp::transform(std::vector& vals, switch (boundTypes[col]) { case BoundType::kSimpleLb: if (vals[i] > 0) { + // relax away using lower bound tmpRhs -= lb * vals[i]; vals[i] = 0.0; removeZeros = true; @@ -250,6 +244,7 @@ bool HighsTransformedLp::transform(std::vector& vals, break; case BoundType::kSimpleUb: if (vals[i] < 0) { + // relax away using upper bound tmpRhs -= ub * vals[i]; vals[i] = 0.0; removeZeros = true; @@ -259,6 +254,10 @@ bool HighsTransformedLp::transform(std::vector& vals, case BoundType::kVariableLb: tmpRhs -= bestVlb[col].second.constant * vals[i]; vectorsum.add(bestVlb[col].first, vals[i] * bestVlb[col].second.coef); + // arbitrarily initialize bound type for vlb variable in order to + // distinguish from variable 'col'. the bound type will be set properly + // in subsequently. + boundTypes[bestVlb[col].first] = BoundType::kSimpleLb; if (vals[i] > 0) { boundTypes[col] = oldBoundType; vals[i] = 0; @@ -267,8 +266,11 @@ bool HighsTransformedLp::transform(std::vector& vals, case BoundType::kVariableUb: tmpRhs -= bestVub[col].second.constant * vals[i]; vectorsum.add(bestVub[col].first, vals[i] * bestVub[col].second.coef); - vals[i] = -vals[i]; - if (vals[i] > 0) { + // arbitrarily initialize bound type for vub variable in order to + // distinguish from variable 'col'. the bound type will be set properly + // in subsequently. + boundTypes[bestVub[col].first] = BoundType::kSimpleLb; + if (vals[i] < 0) { boundTypes[col] = oldBoundType; vals[i] = 0; } @@ -316,28 +318,25 @@ bool HighsTransformedLp::transform(std::vector& vals, inds.resize(numNz); } - if (integersPositive) { - // complement integers to make coefficients positive - for (HighsInt j = 0; j != numNz; ++j) { - HighsInt col = inds[j]; - if (!lprelaxation.isColIntegral(inds[j])) continue; + // integersPositive == true: complement integers to make coefficients positive + // integersPositive == false: complement integers with closest bound + for (HighsInt j = 0; j != numNz; ++j) { + HighsInt col = inds[j]; - if (vals[j] > 0) - boundTypes[col] = BoundType::kSimpleLb; - else - boundTypes[col] = BoundType::kSimpleUb; - } - } else { - // complement integers with closest bound - for (HighsInt j = 0; j != numNz; ++j) { - HighsInt col = inds[j]; - if (!lprelaxation.isColIntegral(inds[j])) continue; - - if (lbDist[col] < ubDist[col]) - boundTypes[col] = BoundType::kSimpleLb; - else - boundTypes[col] = BoundType::kSimpleUb; - } + // skip non-integer variables as their bound types were set above + if (!lprelaxation.isColIntegral(col)) continue; + + // skip integral vlb / vub variables since their status was also already set + // (and it would otherwise be overwritten) + if (boundTypes[col] == BoundType::kVariableLb || + boundTypes[col] == BoundType::kVariableUb) + continue; + + if ((integersPositive && vals[j] > 0) || + (!integersPositive && lbDist[col] < ubDist[col])) + boundTypes[col] = BoundType::kSimpleLb; + else + boundTypes[col] = BoundType::kSimpleUb; } upper.resize(numNz); @@ -362,12 +361,14 @@ bool HighsTransformedLp::transform(std::vector& vals, switch (boundTypes[col]) { case BoundType::kSimpleLb: { + // shift (lower bound) assert(lb != -kHighsInf); tmpRhs -= lb * vals[j]; solval[j] = lbDist[col]; break; } case BoundType::kSimpleUb: { + // complement (upper bound) assert(ub != kHighsInf); tmpRhs -= ub * vals[j]; vals[j] = -vals[j]; @@ -379,6 +380,7 @@ bool HighsTransformedLp::transform(std::vector& vals, break; } case BoundType::kVariableUb: { + vals[j] = -vals[j]; solval[j] = ubDist[col]; continue; } From e38350721bb2687201a709c69c0b42ce75d5d6f3 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 22 Nov 2024 10:58:27 +0000 Subject: [PATCH 14/59] 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 15/59] 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 16/59] 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 17/59] 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 18/59] 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 19/59] 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 20/59] 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 21/59] 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 22/59] 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 23/59] 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 24/59] 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 From 60aec89147018ad5d57c3141d117c0b0f676bbf7 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Mon, 25 Nov 2024 10:08:06 +0100 Subject: [PATCH 25/59] Fix transformation --- src/mip/HighsTransformedLp.cpp | 74 ++++++++++++++++------------------ 1 file changed, 35 insertions(+), 39 deletions(-) diff --git a/src/mip/HighsTransformedLp.cpp b/src/mip/HighsTransformedLp.cpp index 284a13069a..a6b06fc9af 100644 --- a/src/mip/HighsTransformedLp.cpp +++ b/src/mip/HighsTransformedLp.cpp @@ -140,20 +140,21 @@ bool HighsTransformedLp::transform(std::vector& vals, HighsInt numNz = inds.size(); bool removeZeros = false; + auto getLb = [&](HighsInt col) { + return (col < slackOffset ? mip.mipdata_->domain.col_lower_[col] + : lprelaxation.slackLower(col - slackOffset)); + }; + + auto getUb = [&](HighsInt col) { + return (col < slackOffset ? mip.mipdata_->domain.col_upper_[col] + : lprelaxation.slackUpper(col - slackOffset)); + }; + for (HighsInt i = 0; i != numNz; ++i) { HighsInt col = inds[i]; - double lb; - double ub; - - if (col < slackOffset) { - lb = mip.mipdata_->domain.col_lower_[col]; - ub = mip.mipdata_->domain.col_upper_[col]; - } else { - HighsInt row = col - slackOffset; - lb = lprelaxation.slackLower(row); - ub = lprelaxation.slackUpper(row); - } + double lb = getLb(col); + double ub = getUb(col); if (ub - lb < mip.options_mip_->small_matrix_value) { tmpRhs -= std::min(lb, ub) * vals[i]; @@ -179,7 +180,6 @@ bool HighsTransformedLp::transform(std::vector& vals, BoundType oldBoundType = boundTypes[col]; if (lprelaxation.isColIntegral(col)) { - if (lb == -kHighsInf || ub == kHighsInf) integersPositive = false; if (ub - lb <= 1.5 || boundDist[col] != 0.0 || simpleLbDist[col] == 0 || simpleUbDist[col] == 0) continue; @@ -255,8 +255,8 @@ bool HighsTransformedLp::transform(std::vector& vals, tmpRhs -= bestVlb[col].second.constant * vals[i]; vectorsum.add(bestVlb[col].first, vals[i] * bestVlb[col].second.coef); // arbitrarily initialize bound type for vlb variable in order to - // distinguish from variable 'col'. the bound type will be set properly - // in subsequently. + // distinguish from already processed integer-constrained variables. the + // bound type will be set properly in subsequently. boundTypes[bestVlb[col].first] = BoundType::kSimpleLb; if (vals[i] > 0) { boundTypes[col] = oldBoundType; @@ -266,11 +266,12 @@ bool HighsTransformedLp::transform(std::vector& vals, case BoundType::kVariableUb: tmpRhs -= bestVub[col].second.constant * vals[i]; vectorsum.add(bestVub[col].first, vals[i] * bestVub[col].second.coef); + vals[i] = -vals[i]; // arbitrarily initialize bound type for vub variable in order to - // distinguish from variable 'col'. the bound type will be set properly - // in subsequently. + // distinguish from already processed integer-constrained variables. the + // bound type will be set properly in subsequently. boundTypes[bestVub[col].first] = BoundType::kSimpleLb; - if (vals[i] < 0) { + if (vals[i] > 0) { boundTypes[col] = oldBoundType; vals[i] = 0; } @@ -318,21 +319,22 @@ bool HighsTransformedLp::transform(std::vector& vals, inds.resize(numNz); } - // integersPositive == true: complement integers to make coefficients positive - // integersPositive == false: complement integers with closest bound for (HighsInt j = 0; j != numNz; ++j) { HighsInt col = inds[j]; - // skip non-integer variables as their bound types were set above - if (!lprelaxation.isColIntegral(col)) continue; - - // skip integral vlb / vub variables since their status was also already set - // (and it would otherwise be overwritten) - if (boundTypes[col] == BoundType::kVariableLb || + // set bound type for previously unprocessed integer-constrained variables. + // do not overwrite bound type for integral slacks from vlb / vub + // constraints. + if (!lprelaxation.isColIntegral(col) || + boundTypes[col] == BoundType::kVariableLb || boundTypes[col] == BoundType::kVariableUb) continue; - if ((integersPositive && vals[j] > 0) || + // complement integers to make coefficients positive if both bounds are + // finite; otherwise, complement integers with closest bound. + // take into account 'integersPositive' provided by caller. + if ((integersPositive && getLb(col) != -kHighsInf && + getUb(col) != kHighsInf && vals[j] > 0) || (!integersPositive && lbDist[col] < ubDist[col])) boundTypes[col] = BoundType::kSimpleLb; else @@ -345,17 +347,8 @@ bool HighsTransformedLp::transform(std::vector& vals, for (HighsInt j = 0; j != numNz; ++j) { HighsInt col = inds[j]; - double lb; - double ub; - - if (col < slackOffset) { - lb = mip.mipdata_->domain.col_lower_[col]; - ub = mip.mipdata_->domain.col_upper_[col]; - } else { - HighsInt row = col - slackOffset; - lb = lprelaxation.slackLower(row); - ub = lprelaxation.slackUpper(row); - } + double lb = getLb(col); + double ub = getUb(col); upper[j] = ub - lb; @@ -380,11 +373,14 @@ bool HighsTransformedLp::transform(std::vector& vals, break; } case BoundType::kVariableUb: { - vals[j] = -vals[j]; solval[j] = ubDist[col]; - continue; + break; } } + + // check if all integer-constrained variables have positive coefficients + if (lprelaxation.isColIntegral(col)) + integersPositive = integersPositive && vals[j] > 0; } rhs = double(tmpRhs); From f85262adf7f57dfef45f298430ad624d903511f1 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 25 Nov 2024 09:30:19 +0000 Subject: [PATCH 26/59] Removed Highs_addLinearObjectiveWithIndex from C API, and added docstrings for other multiple linear objective methods --- docs/src/guide/further.md | 1 - src/interfaces/highs_c_api.cpp | 27 ++++++++--------- src/interfaces/highs_c_api.h | 53 +++++++++++++++++++++++++++++++--- 3 files changed, 63 insertions(+), 18 deletions(-) diff --git a/docs/src/guide/further.md b/docs/src/guide/further.md index 91a4dc75ca..b5fb80ec2d 100644 --- a/docs/src/guide/further.md +++ b/docs/src/guide/further.md @@ -125,7 +125,6 @@ linear objective is represented by the following data, held in the Multi-objective optimization in HiGHS is defined by the following methods -- [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 diff --git a/src/interfaces/highs_c_api.cpp b/src/interfaces/highs_c_api.cpp index 655c0fce30..3c464796e2 100644 --- a/src/interfaces/highs_c_api.cpp +++ b/src/interfaces/highs_c_api.cpp @@ -300,12 +300,22 @@ HighsInt Highs_passLinearObjectives(const void* highs, const HighsInt* priority) { HighsInt status = Highs_clearLinearObjectives(highs); if (status != kHighsStatusOk) return status; - HighsInt num_col = Highs_getNumCol(highs); + HighsLinearObjective linear_objective; 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); + HighsInt num_col = Highs_getNumCol(highs); + linear_objective.weight = weight[iObj]; + linear_objective.offset = offset[iObj]; + for (HighsInt iCol = 0; iCol < num_col; iCol++) + linear_objective.coefficients.push_back( + coefficients[iObj * num_col + iCol]); + linear_objective.abs_tolerance = abs_tolerance[iObj]; + linear_objective.rel_tolerance = rel_tolerance[iObj]; + linear_objective.priority = priority[iObj]; + linear_objective.weight = weight[iObj]; + status = + HighsInt(((Highs*)highs)->addLinearObjective(linear_objective, iObj)); if (status != kHighsStatusOk) return status; + linear_objective.coefficients.clear(); } return kHighsStatusOk; } @@ -316,15 +326,6 @@ HighsInt Highs_addLinearObjective(const void* highs, const double weight, 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; diff --git a/src/interfaces/highs_c_api.h b/src/interfaces/highs_c_api.h index db8764fae6..0feb39bf35 100644 --- a/src/interfaces/highs_c_api.h +++ b/src/interfaces/highs_c_api.h @@ -557,6 +557,28 @@ HighsInt Highs_passHessian(void* highs, const HighsInt dim, const HighsInt* start, const HighsInt* index, const double* value); +/** + * Passes multiple linear objective data to HiGHS, clearing any such + * data already in HiGHS + * + * @param highs A pointer to the Highs instance. + * @param weight A pointer to the weights of the linear objective, with + * its positive/negative sign determining whether it is + * minimized or maximized during lexicographic optimization + * @param offset A pointer to the objective offsets + * @param coefficients A pointer to the objective coefficients + * @param abs_tolerance A pointer to the absolute tolerances used when + * constructing objective constraints during lexicographic + * optimization + * @param rel_tolerance A pointer to the relative tolerances used when + * constructing objective constraints during lexicographic + * optimization + * @param priority A pointer to the priorities of the objectives during + * lexicographic optimization + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ + HighsInt Highs_passLinearObjectives(const void* highs, const HighsInt num_linear_objective, const double* weight, const double* offset, @@ -565,6 +587,26 @@ HighsInt Highs_passLinearObjectives(const void* highs, const double* rel_tolerance, const HighsInt* priority); +/** + * Adds linear objective data to HiGHS + * + * @param highs A pointer to the Highs instance. + * @param weight The weight of the linear objective, with its + * positive/negative sign determining whether it is + * minimized or maximized during lexicographic + * optimization + * @param offset The objective offset + * @param coefficients A pointer to the objective coefficients + * @param abs_tolerance The absolute tolerance used when constructing an + * objective constraint during lexicographic optimization + * @param rel_tolerance The relative tolerance used when constructing an + * objective constraint during lexicographic optimization + * @param priority The priority of this objective during lexicographic + * optimization + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ + HighsInt Highs_addLinearObjective(const void* highs, const double weight, const double offset, const double* coefficients, @@ -572,10 +614,13 @@ HighsInt Highs_addLinearObjective(const void* highs, const double weight, 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); +/** + * Clears any multiple linear objective data in HiGHS + * + * @param highs A pointer to the Highs instance. + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ HighsInt Highs_clearLinearObjectives(const void* highs); /** From 9726a7f23d9650840cbd0f1e946192c1bbe8e5f2 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Mon, 25 Nov 2024 10:33:58 +0100 Subject: [PATCH 27/59] Fix if-block --- src/mip/HighsTransformedLp.cpp | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/mip/HighsTransformedLp.cpp b/src/mip/HighsTransformedLp.cpp index a6b06fc9af..47d3bf05e9 100644 --- a/src/mip/HighsTransformedLp.cpp +++ b/src/mip/HighsTransformedLp.cpp @@ -330,15 +330,30 @@ bool HighsTransformedLp::transform(std::vector& vals, boundTypes[col] == BoundType::kVariableUb) continue; + // get bounds + double lb = getLb(col); + double ub = getUb(col); + + // make sure that variable is bounded + if (lb == -kHighsInf && ub == kHighsInf) { + vectorsum.clear(); + return false; + } + // complement integers to make coefficients positive if both bounds are // finite; otherwise, complement integers with closest bound. // take into account 'integersPositive' provided by caller. - if ((integersPositive && getLb(col) != -kHighsInf && - getUb(col) != kHighsInf && vals[j] > 0) || - (!integersPositive && lbDist[col] < ubDist[col])) - boundTypes[col] = BoundType::kSimpleLb; - else - boundTypes[col] = BoundType::kSimpleUb; + if (integersPositive) { + if ((lb != -kHighsInf && vals[j] > 0) || ub == kHighsInf) + boundTypes[col] = BoundType::kSimpleLb; + else + boundTypes[col] = BoundType::kSimpleUb; + } else { + if (lbDist[col] < ubDist[col]) + boundTypes[col] = BoundType::kSimpleLb; + else + boundTypes[col] = BoundType::kSimpleUb; + } } upper.resize(numNz); From 6b8382127531235b7d7486a367de3d396a2ee946 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Mon, 25 Nov 2024 10:37:40 +0100 Subject: [PATCH 28/59] Fix typo --- src/mip/HighsTransformedLp.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mip/HighsTransformedLp.cpp b/src/mip/HighsTransformedLp.cpp index 47d3bf05e9..2db2a14987 100644 --- a/src/mip/HighsTransformedLp.cpp +++ b/src/mip/HighsTransformedLp.cpp @@ -256,7 +256,7 @@ bool HighsTransformedLp::transform(std::vector& vals, vectorsum.add(bestVlb[col].first, vals[i] * bestVlb[col].second.coef); // arbitrarily initialize bound type for vlb variable in order to // distinguish from already processed integer-constrained variables. the - // bound type will be set properly in subsequently. + // bound type will be set properly subsequently. boundTypes[bestVlb[col].first] = BoundType::kSimpleLb; if (vals[i] > 0) { boundTypes[col] = oldBoundType; @@ -269,7 +269,7 @@ bool HighsTransformedLp::transform(std::vector& vals, vals[i] = -vals[i]; // arbitrarily initialize bound type for vub variable in order to // distinguish from already processed integer-constrained variables. the - // bound type will be set properly in subsequently. + // bound type will be set properly subsequently. boundTypes[bestVub[col].first] = BoundType::kSimpleLb; if (vals[i] > 0) { boundTypes[col] = oldBoundType; From 901e5d5af58d49b69c404c7dcba8d54212944ec8 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 25 Nov 2024 10:08:30 +0000 Subject: [PATCH 29/59] Hopefully corrected further.md --- docs/src/guide/further.md | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/docs/src/guide/further.md b/docs/src/guide/further.md index b5fb80ec2d..0bfa519867 100644 --- a/docs/src/guide/further.md +++ b/docs/src/guide/further.md @@ -150,28 +150,22 @@ 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^*>=0``, then the constraint ensures that the this objective value is no greater than + + If the objective was minimized to a value ``f^*\ge0``, then the constraint ensures that the this objective value is no greater than ```math - -\min(f^*+`abs_tolerance`,~f^*[1+`rel_tolerance`]). +\min(f^*+`abs\_tolerance`,~f^*\times[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 + + If the objective was minimized to a value ``f^*\lt0``, then the constraint ensures that the this objective value is no greater than ```math -\min(f^*+`abs_tolerance`,~f^*[1-`rel_tolerance`]). +\min(f^*+`abs\_tolerance`,~f^*\times[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 + + If the objective was maximized to a value ``f^*\ge0``, then the constraint ensures that the this objective value is no less than ```math -\max(f^*-`abs_tolerance`,~f^*[1-`rel_tolerance`]). +\max(f^*-`abs\_tolerance`,~f^*\times[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 + + If the objective was maximized to a value ``f^*\lt0``, then the constraint ensures that the this objective value is no less than ```math -\max(f^*-`abs_tolerance`,~f^*[1+`rel_tolerance`]). +\max(f^*-`abs\_tolerance`,~f^*\times[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 From 0778fae054f47b7509864ce84879681e1cb29f33 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 25 Nov 2024 10:34:57 +0000 Subject: [PATCH 30/59] New hope to correct further.md --- docs/src/guide/further.md | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/docs/src/guide/further.md b/docs/src/guide/further.md index 0bfa519867..65d25ff658 100644 --- a/docs/src/guide/further.md +++ b/docs/src/guide/further.md @@ -151,21 +151,13 @@ priority values must be distinct_. * 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^*\ge0``, then the constraint ensures that the this objective value is no greater than -```math -\min(f^*+`abs\_tolerance`,~f^*\times[1+`rel\_tolerance`]). -``` - + If the objective was minimized to a value ``f^*\lt0``, then the constraint ensures that the this objective value is no greater than -```math -\min(f^*+`abs\_tolerance`,~f^*\times[1-`rel\_tolerance`]). -``` +``\min(f^*+abs\_tolerance,~f^*\times[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^*\times[1-rel\_tolerance]).`` + If the objective was maximized to a value ``f^*\ge0``, then the constraint ensures that the this objective value is no less than -```math -\max(f^*-`abs\_tolerance`,~f^*\times[1-`rel\_tolerance`]). -``` - + If the objective was maximized to a value ``f^*\lt0``, then the constraint ensures that the this objective value is no less than -```math -\max(f^*-`abs\_tolerance`,~f^*\times[1+`rel\_tolerance`]). -``` +``\max(f^*-abs\_tolerance,~f^*\times[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^*\times[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 From 1d25e744436c42dd933b784d61661b513e36ae3c Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 25 Nov 2024 11:20:10 +0000 Subject: [PATCH 31/59] Again... new hope to correct further.md --- docs/src/guide/further.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/src/guide/further.md b/docs/src/guide/further.md index 65d25ff658..4d828f7055 100644 --- a/docs/src/guide/further.md +++ b/docs/src/guide/further.md @@ -152,12 +152,16 @@ priority values must be distinct_. * 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^*\ge0``, then the constraint ensures that the this objective value is no greater than ``\min(f^*+abs\_tolerance,~f^*\times[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^*\times[1-rel\_tolerance]).`` + + If the objective was maximized to a value ``f^*\ge0``, then the constraint ensures that the this objective value is no less than ``\max(f^*-abs\_tolerance,~f^*\times[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^*\times[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 From bf5c7db411750955ecbf953331d896ac0fc7d56a Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 25 Nov 2024 11:32:21 +0000 Subject: [PATCH 32/59] More... again... new hope to correct further.md --- docs/src/guide/further.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/docs/src/guide/further.md b/docs/src/guide/further.md index 4d828f7055..ed111f6682 100644 --- a/docs/src/guide/further.md +++ b/docs/src/guide/further.md @@ -150,17 +150,13 @@ 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^*\ge0``, then the constraint ensures that the this objective value is no greater than -``\min(f^*+abs\_tolerance,~f^*\times[1+rel\_tolerance]).`` + + If the objective was minimized to a value ``f^*\ge0``, then the constraint ensures that the this objective value is no greater than ``\min(f^*+abs\_tolerance,~f^*\times[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^*\times[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^*\times[1-rel\_tolerance]).`` - + If the objective was maximized to a value ``f^*\ge0``, then the constraint ensures that the this objective value is no less than -``\max(f^*-abs\_tolerance,~f^*\times[1-rel\_tolerance]).`` + + If the objective was maximized to a value ``f^*\ge0``, then the constraint ensures that the this objective value is no less than ``\max(f^*-abs\_tolerance,~f^*\times[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^*\times[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^*\times[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. From 52d1b58dde02eb17bd08160becfcfa278b20e84a Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 25 Nov 2024 12:03:45 +0000 Subject: [PATCH 33/59] Added examples/knapsack.py to illustrate setSolution in highspy --- examples/knapsack.py | 55 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 examples/knapsack.py diff --git a/examples/knapsack.py b/examples/knapsack.py new file mode 100644 index 0000000000..992cac793d --- /dev/null +++ b/examples/knapsack.py @@ -0,0 +1,55 @@ +import numpy as np +import highspy + +h = highspy.Highs() +h.setOptionValue("output_flag", False); +h.setOptionValue("presolve", "off"); +inf = highspy.kHighsInf +lp = highspy.HighsLp() +lp.sense_ = highspy.ObjSense.kMaximize +lp.num_col_ = 5 +lp.num_row_ = 1 +lp.col_cost_ = np.array([8, 5, 3, 11, 7], dtype=np.double) +lp.col_lower_ = np.array([0, 0, 0, 0, 0], dtype=np.double) +lp.col_upper_ = np.array([1, 1, 1, 1, 1], dtype=np.double) +lp.row_lower_ = np.array([-inf], dtype=np.double) +lp.row_upper_ = np.array([11], dtype=np.double) +lp.a_matrix_.format_ = highspy.MatrixFormat.kRowwise +lp.a_matrix_.start_ = np.array([0, 5]) +lp.a_matrix_.index_ = np.array([0, 1, 2, 3, 4]) +lp.a_matrix_.value_ = np.array([4, 3, 1, 5, 4], dtype=np.double) +lp.integrality_ = np.array([highspy.HighsVarType.kInteger, highspy.HighsVarType.kInteger, highspy.HighsVarType.kInteger, highspy.HighsVarType.kInteger, highspy.HighsVarType.kInteger]) +h.passModel(lp) + +h.run() +solution = h.getSolution() +print(f"Solution: ({solution.col_value[0]}, {solution.col_value[1]}, {solution.col_value[2]}, {solution.col_value[3]}, {solution.col_value[4]})") + +# Solution is [1, 0, 1, 1, 0] + +# Illustrate setSolution + +# First by passing back the optimal solution + +h.clearSolver() +h.setSolution(solution) +h.run() +solution = h.getSolution() +print(f"Solution: ({solution.col_value[0]}, {solution.col_value[1]}, {solution.col_value[2]}, {solution.col_value[3]}, {solution.col_value[4]})") + +# Now passing back the optimal values of two variables as a sparse solution +h.clearSolver() +index = np.array([0, 3]) +value = np.array([1, 1], dtype=np.double) +h.setSolution(2, index, value) +h.run() +solution = h.getSolution() +print(f"Solution: ({solution.col_value[0]}, {solution.col_value[1]}, {solution.col_value[2]}, {solution.col_value[3]}, {solution.col_value[4]})") + +# Test passing back the optimal value of one variable, and a non-optimal value of another, as a sparse solution in untyped array +h.clearSolver() +h.setSolution(2, np.array([0, 4]), np.array([1, 1])) +h.run() +solution = h.getSolution() +print(f"Solution: ({solution.col_value[0]}, {solution.col_value[1]}, {solution.col_value[2]}, {solution.col_value[3]}, {solution.col_value[4]})") + From 92852f64317cb79a7353c5e4905ef958b5f36b05 Mon Sep 17 00:00:00 2001 From: Stefan Vigerske Date: Mon, 25 Nov 2024 17:42:45 +0100 Subject: [PATCH 34/59] fix constructor initialization order --- src/mip/HighsPrimalHeuristics.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mip/HighsPrimalHeuristics.cpp b/src/mip/HighsPrimalHeuristics.cpp index 5908f4373c..1c4aff5daf 100644 --- a/src/mip/HighsPrimalHeuristics.cpp +++ b/src/mip/HighsPrimalHeuristics.cpp @@ -37,10 +37,10 @@ HighsPrimalHeuristics::HighsPrimalHeuristics(HighsMipSolver& mipsolver) : mipsolver(mipsolver), - lp_iterations(0), total_repair_lp(0), total_repair_lp_feasible(0), total_repair_lp_iterations(0), + lp_iterations(0), randgen(mipsolver.options_mip_->random_seed) { successObservations = 0; numSuccessObservations = 0; From 9fe14972efda787a01fb862abb7c86f81fbaac19 Mon Sep 17 00:00:00 2001 From: Stefan Vigerske Date: Mon, 25 Nov 2024 17:44:06 +0100 Subject: [PATCH 35/59] fix missing prior declartion of public function - make function local --- src/mip/HighsMipSolverData.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mip/HighsMipSolverData.cpp b/src/mip/HighsMipSolverData.cpp index e84f9b5396..522804d18b 100644 --- a/src/mip/HighsMipSolverData.cpp +++ b/src/mip/HighsMipSolverData.cpp @@ -2501,7 +2501,8 @@ bool HighsMipSolverData::interruptFromCallbackWithData( return mipsolver.callback_->callbackAction(callback_type, message); } -double possInfRelDiff(const double v0, const double v1, const double den) { +static double possInfRelDiff(const double v0, const double v1, + const double den) { double rel_diff; if (std::fabs(v0) == kHighsInf) { if (std::fabs(v1) == kHighsInf) { From 16bc17cc7f528fc65f4ea616ae35b1fc077ed5ef Mon Sep 17 00:00:00 2001 From: fwesselm Date: Tue, 26 Nov 2024 09:40:30 +0100 Subject: [PATCH 36/59] Add an assertion --- src/mip/HighsTransformedLp.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mip/HighsTransformedLp.cpp b/src/mip/HighsTransformedLp.cpp index 2db2a14987..56361885c1 100644 --- a/src/mip/HighsTransformedLp.cpp +++ b/src/mip/HighsTransformedLp.cpp @@ -132,6 +132,9 @@ bool HighsTransformedLp::transform(std::vector& vals, std::vector& solval, std::vector& inds, double& rhs, bool& integersPositive, bool preferVbds) { + // vector sum should be empty + assert(vectorsum.getNonzeros().empty()); + HighsCDouble tmpRhs = rhs; const HighsMipSolver& mip = lprelaxation.getMipSolver(); @@ -335,10 +338,7 @@ bool HighsTransformedLp::transform(std::vector& vals, double ub = getUb(col); // make sure that variable is bounded - if (lb == -kHighsInf && ub == kHighsInf) { - vectorsum.clear(); - return false; - } + if (lb == -kHighsInf && ub == kHighsInf) return false; // complement integers to make coefficients positive if both bounds are // finite; otherwise, complement integers with closest bound. From 8bddf0b5f9e3e8e5f262b96b6de8f3adfce19227 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Tue, 26 Nov 2024 14:29:24 +0100 Subject: [PATCH 37/59] Store indices in set instead of manipulating bound type --- src/mip/HighsTransformedLp.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/mip/HighsTransformedLp.cpp b/src/mip/HighsTransformedLp.cpp index 56361885c1..491dcd96e2 100644 --- a/src/mip/HighsTransformedLp.cpp +++ b/src/mip/HighsTransformedLp.cpp @@ -135,6 +135,9 @@ bool HighsTransformedLp::transform(std::vector& vals, // vector sum should be empty assert(vectorsum.getNonzeros().empty()); + // set for storing indices of integral slacks from variable bound constraints + std::set intVariableBndSlacks; + HighsCDouble tmpRhs = rhs; const HighsMipSolver& mip = lprelaxation.getMipSolver(); @@ -257,10 +260,9 @@ bool HighsTransformedLp::transform(std::vector& vals, case BoundType::kVariableLb: tmpRhs -= bestVlb[col].second.constant * vals[i]; vectorsum.add(bestVlb[col].first, vals[i] * bestVlb[col].second.coef); - // arbitrarily initialize bound type for vlb variable in order to - // distinguish from already processed integer-constrained variables. the - // bound type will be set properly subsequently. - boundTypes[bestVlb[col].first] = BoundType::kSimpleLb; + // store integral slack in set + if (lprelaxation.isColIntegral(bestVlb[col].first)) + intVariableBndSlacks.insert(bestVlb[col].first); if (vals[i] > 0) { boundTypes[col] = oldBoundType; vals[i] = 0; @@ -270,10 +272,9 @@ bool HighsTransformedLp::transform(std::vector& vals, tmpRhs -= bestVub[col].second.constant * vals[i]; vectorsum.add(bestVub[col].first, vals[i] * bestVub[col].second.coef); vals[i] = -vals[i]; - // arbitrarily initialize bound type for vub variable in order to - // distinguish from already processed integer-constrained variables. the - // bound type will be set properly subsequently. - boundTypes[bestVub[col].first] = BoundType::kSimpleLb; + // store integral slack in set + if (lprelaxation.isColIntegral(bestVub[col].first)) + intVariableBndSlacks.insert(bestVub[col].first); if (vals[i] > 0) { boundTypes[col] = oldBoundType; vals[i] = 0; @@ -325,13 +326,8 @@ bool HighsTransformedLp::transform(std::vector& vals, for (HighsInt j = 0; j != numNz; ++j) { HighsInt col = inds[j]; - // set bound type for previously unprocessed integer-constrained variables. - // do not overwrite bound type for integral slacks from vlb / vub - // constraints. - if (!lprelaxation.isColIntegral(col) || - boundTypes[col] == BoundType::kVariableLb || - boundTypes[col] == BoundType::kVariableUb) - continue; + // set bound type for previously unprocessed integer-constrained variables + if (!lprelaxation.isColIntegral(col)) continue; // get bounds double lb = getLb(col); @@ -340,6 +336,10 @@ bool HighsTransformedLp::transform(std::vector& vals, // make sure that variable is bounded if (lb == -kHighsInf && ub == kHighsInf) return false; + // do not overwrite bound type for integral slacks from vlb / vub + // constraints + if (intVariableBndSlacks.find(col) != intVariableBndSlacks.end()) continue; + // complement integers to make coefficients positive if both bounds are // finite; otherwise, complement integers with closest bound. // take into account 'integersPositive' provided by caller. From 083f617b08ed80825a543472597a8499b8ec9363 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Tue, 26 Nov 2024 14:53:00 +0100 Subject: [PATCH 38/59] Fix index error --- src/mip/HighsTransformedLp.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/mip/HighsTransformedLp.cpp b/src/mip/HighsTransformedLp.cpp index 491dcd96e2..9c06d96328 100644 --- a/src/mip/HighsTransformedLp.cpp +++ b/src/mip/HighsTransformedLp.cpp @@ -261,8 +261,7 @@ bool HighsTransformedLp::transform(std::vector& vals, tmpRhs -= bestVlb[col].second.constant * vals[i]; vectorsum.add(bestVlb[col].first, vals[i] * bestVlb[col].second.coef); // store integral slack in set - if (lprelaxation.isColIntegral(bestVlb[col].first)) - intVariableBndSlacks.insert(bestVlb[col].first); + if (lprelaxation.isColIntegral(col)) intVariableBndSlacks.insert(col); if (vals[i] > 0) { boundTypes[col] = oldBoundType; vals[i] = 0; @@ -273,8 +272,7 @@ bool HighsTransformedLp::transform(std::vector& vals, vectorsum.add(bestVub[col].first, vals[i] * bestVub[col].second.coef); vals[i] = -vals[i]; // store integral slack in set - if (lprelaxation.isColIntegral(bestVub[col].first)) - intVariableBndSlacks.insert(bestVub[col].first); + if (lprelaxation.isColIntegral(col)) intVariableBndSlacks.insert(col); if (vals[i] > 0) { boundTypes[col] = oldBoundType; vals[i] = 0; From 0052f8f7701c4f3090460ee625ca27635bf41ccd Mon Sep 17 00:00:00 2001 From: fwesselm Date: Tue, 26 Nov 2024 14:56:10 +0100 Subject: [PATCH 39/59] Restructure a little bit --- src/mip/HighsTransformedLp.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/mip/HighsTransformedLp.cpp b/src/mip/HighsTransformedLp.cpp index 9c06d96328..66caf9e485 100644 --- a/src/mip/HighsTransformedLp.cpp +++ b/src/mip/HighsTransformedLp.cpp @@ -260,22 +260,24 @@ bool HighsTransformedLp::transform(std::vector& vals, case BoundType::kVariableLb: tmpRhs -= bestVlb[col].second.constant * vals[i]; vectorsum.add(bestVlb[col].first, vals[i] * bestVlb[col].second.coef); - // store integral slack in set - if (lprelaxation.isColIntegral(col)) intVariableBndSlacks.insert(col); if (vals[i] > 0) { boundTypes[col] = oldBoundType; vals[i] = 0; + } else if (lprelaxation.isColIntegral(col)) { + // store integral slack in set + intVariableBndSlacks.insert(col); } break; case BoundType::kVariableUb: tmpRhs -= bestVub[col].second.constant * vals[i]; vectorsum.add(bestVub[col].first, vals[i] * bestVub[col].second.coef); vals[i] = -vals[i]; - // store integral slack in set - if (lprelaxation.isColIntegral(col)) intVariableBndSlacks.insert(col); if (vals[i] > 0) { boundTypes[col] = oldBoundType; vals[i] = 0; + } else if (lprelaxation.isColIntegral(col)) { + // store integral slack in set + intVariableBndSlacks.insert(col); } } } From 0373ffdb8792916d9ec67b2a800a961ab561033c Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 27 Nov 2024 10:14:22 +0100 Subject: [PATCH 40/59] Minor changes to loop --- src/mip/HighsTransformedLp.cpp | 53 +++++++++++++++++----------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/src/mip/HighsTransformedLp.cpp b/src/mip/HighsTransformedLp.cpp index 66caf9e485..8900cd6121 100644 --- a/src/mip/HighsTransformedLp.cpp +++ b/src/mip/HighsTransformedLp.cpp @@ -144,7 +144,6 @@ bool HighsTransformedLp::transform(std::vector& vals, const HighsInt slackOffset = lprelaxation.numCols(); HighsInt numNz = inds.size(); - bool removeZeros = false; auto getLb = [&](HighsInt col) { return (col < slackOffset ? mip.mipdata_->domain.col_lower_[col] @@ -156,7 +155,16 @@ bool HighsTransformedLp::transform(std::vector& vals, : lprelaxation.slackUpper(col - slackOffset)); }; - for (HighsInt i = 0; i != numNz; ++i) { + auto remove = [&](HighsInt position) { + numNz--; + inds[position] = inds[numNz]; + vals[position] = vals[numNz]; + inds[numNz] = 0; + vals[numNz] = 0; + }; + + HighsInt i = 0; + while (i < numNz) { HighsInt col = inds[i]; double lb = getLb(col); @@ -164,8 +172,7 @@ bool HighsTransformedLp::transform(std::vector& vals, if (ub - lb < mip.options_mip_->small_matrix_value) { tmpRhs -= std::min(lb, ub) * vals[i]; - vals[i] = 0.0; - removeZeros = true; + remove(i); continue; } @@ -187,8 +194,10 @@ bool HighsTransformedLp::transform(std::vector& vals, if (lprelaxation.isColIntegral(col)) { if (ub - lb <= 1.5 || boundDist[col] != 0.0 || simpleLbDist[col] == 0 || - simpleUbDist[col] == 0) + simpleUbDist[col] == 0) { + i++; continue; + } if (bestVlb[col].first == -1 || ubDist[col] < lbDist[col] - mip.mipdata_->feastol) { assert(bestVub[col].first != -1); @@ -243,18 +252,18 @@ bool HighsTransformedLp::transform(std::vector& vals, if (vals[i] > 0) { // relax away using lower bound tmpRhs -= lb * vals[i]; - vals[i] = 0.0; - removeZeros = true; boundTypes[col] = oldBoundType; + remove(i); + continue; } break; case BoundType::kSimpleUb: if (vals[i] < 0) { // relax away using upper bound tmpRhs -= ub * vals[i]; - vals[i] = 0.0; - removeZeros = true; boundTypes[col] = oldBoundType; + remove(i); + continue; } break; case BoundType::kVariableLb: @@ -262,7 +271,8 @@ bool HighsTransformedLp::transform(std::vector& vals, vectorsum.add(bestVlb[col].first, vals[i] * bestVlb[col].second.coef); if (vals[i] > 0) { boundTypes[col] = oldBoundType; - vals[i] = 0; + remove(i); + continue; } else if (lprelaxation.isColIntegral(col)) { // store integral slack in set intVariableBndSlacks.insert(col); @@ -274,12 +284,15 @@ bool HighsTransformedLp::transform(std::vector& vals, vals[i] = -vals[i]; if (vals[i] > 0) { boundTypes[col] = oldBoundType; - vals[i] = 0; + remove(i); + continue; } else if (lprelaxation.isColIntegral(col)) { // store integral slack in set intVariableBndSlacks.insert(col); } } + // move to next element + i++; } if (!vectorsum.getNonzeros().empty()) { @@ -289,9 +302,7 @@ bool HighsTransformedLp::transform(std::vector& vals, double maxError = 0.0; auto IsZero = [&](HighsInt col, double val) { - double absval = std::abs(val); - if (absval <= mip.options_mip_->small_matrix_value) return true; - + if (std::abs(val) <= mip.options_mip_->small_matrix_value) return true; return false; }; @@ -308,17 +319,7 @@ bool HighsTransformedLp::transform(std::vector& vals, for (HighsInt j = 0; j != numNz; ++j) vals[j] = vectorsum.getValue(inds[j]); vectorsum.clear(); - } else if (removeZeros) { - for (HighsInt i = numNz - 1; i >= 0; --i) { - if (vals[i] == 0) { - --numNz; - vals[i] = vals[numNz]; - inds[i] = inds[numNz]; - std::swap(vals[i], vals[numNz]); - std::swap(inds[i], inds[numNz]); - } - } - + } else { vals.resize(numNz); inds.resize(numNz); } @@ -333,7 +334,7 @@ bool HighsTransformedLp::transform(std::vector& vals, double lb = getLb(col); double ub = getUb(col); - // make sure that variable is bounded + // make sure that variable is not free if (lb == -kHighsInf && ub == kHighsInf) return false; // do not overwrite bound type for integral slacks from vlb / vub From 8465762ee48a806033f75aa9866cf942f1ad160c Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 27 Nov 2024 13:58:43 +0100 Subject: [PATCH 41/59] Simplify --- src/mip/HighsCutGeneration.cpp | 21 +++++++++++++++------ src/mip/HighsTransformedLp.cpp | 23 ++++++++--------------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/mip/HighsCutGeneration.cpp b/src/mip/HighsCutGeneration.cpp index 0db97b9e52..d265c24b78 100644 --- a/src/mip/HighsCutGeneration.cpp +++ b/src/mip/HighsCutGeneration.cpp @@ -1184,11 +1184,6 @@ bool HighsCutGeneration::generateCut(HighsTransformedLp& transLp, } } while (false); - // save data that might otherwise be overwritten when calling the cmir - // separator - bool saveIntegalSupport = integralSupport; - bool saveIntegralCoefficients = integralCoefficients; - double minMirEfficacy = minEfficacy; if (success) { double violation = -double(rhs); @@ -1210,7 +1205,7 @@ bool HighsCutGeneration::generateCut(HighsTransformedLp& transLp, minMirEfficacy += efficacy; if (!complementation.empty()) { // remove the complementation if it exists, so that the values stored - // values are uncomplemented + // are uncomplemented for (HighsInt i = 0; i != rowlen; ++i) { if (complementation[i]) { rhs -= upper[i] * vals[i]; @@ -1226,6 +1221,11 @@ bool HighsCutGeneration::generateCut(HighsTransformedLp& transLp, inds = tmpInds.data(); vals = tmpVals.data(); + // save data that might otherwise be overwritten when calling the cmir + // separator + bool saveIntegalSupport = integralSupport; + bool saveIntegralCoefficients = integralCoefficients; + bool cmirSuccess = cmirCutGenerationHeuristic(minMirEfficacy, onlyInitialCMIRScale); @@ -1244,6 +1244,7 @@ bool HighsCutGeneration::generateCut(HighsTransformedLp& transLp, complementation.clear(); inds = inds_.data(); vals = vals_.data(); + // restore indicators integralSupport = saveIntegalSupport; integralCoefficients = saveIntegralCoefficients; } else @@ -1423,6 +1424,11 @@ bool HighsCutGeneration::generateConflict(HighsDomain& localdomain, inds = tmpInds.data(); vals = tmpVals.data(); + // save data that might otherwise be overwritten when calling the cmir + // separator + bool saveIntegalSupport = integralSupport; + bool saveIntegralCoefficients = integralCoefficients; + bool cmirSuccess = cmirCutGenerationHeuristic(minEfficacy); if (cmirSuccess) { @@ -1438,6 +1444,9 @@ bool HighsCutGeneration::generateConflict(HighsDomain& localdomain, complementation.swap(tmpComplementation); inds = proofinds.data(); vals = proofvals.data(); + // restore indicators + integralSupport = saveIntegalSupport; + integralCoefficients = saveIntegralCoefficients; } else // neither cmir nor lifted cut successful return false; diff --git a/src/mip/HighsTransformedLp.cpp b/src/mip/HighsTransformedLp.cpp index 8900cd6121..68be21844b 100644 --- a/src/mip/HighsTransformedLp.cpp +++ b/src/mip/HighsTransformedLp.cpp @@ -135,9 +135,6 @@ bool HighsTransformedLp::transform(std::vector& vals, // vector sum should be empty assert(vectorsum.getNonzeros().empty()); - // set for storing indices of integral slacks from variable bound constraints - std::set intVariableBndSlacks; - HighsCDouble tmpRhs = rhs; const HighsMipSolver& mip = lprelaxation.getMipSolver(); @@ -273,9 +270,6 @@ bool HighsTransformedLp::transform(std::vector& vals, boundTypes[col] = oldBoundType; remove(i); continue; - } else if (lprelaxation.isColIntegral(col)) { - // store integral slack in set - intVariableBndSlacks.insert(col); } break; case BoundType::kVariableUb: @@ -286,9 +280,6 @@ bool HighsTransformedLp::transform(std::vector& vals, boundTypes[col] = oldBoundType; remove(i); continue; - } else if (lprelaxation.isColIntegral(col)) { - // store integral slack in set - intVariableBndSlacks.insert(col); } } // move to next element @@ -327,19 +318,21 @@ bool HighsTransformedLp::transform(std::vector& vals, for (HighsInt j = 0; j != numNz; ++j) { HighsInt col = inds[j]; - // set bound type for previously unprocessed integer-constrained variables - if (!lprelaxation.isColIntegral(col)) continue; - // get bounds double lb = getLb(col); double ub = getUb(col); - // make sure that variable is not free - if (lb == -kHighsInf && ub == kHighsInf) return false; + // variable should not be free + assert(lb != -kHighsInf || ub != kHighsInf); + + // set bound type for previously unprocessed integer-constrained variables + if (!lprelaxation.isColIntegral(col)) continue; // do not overwrite bound type for integral slacks from vlb / vub // constraints - if (intVariableBndSlacks.find(col) != intVariableBndSlacks.end()) continue; + if (boundTypes[col] == BoundType::kVariableLb || + boundTypes[col] == BoundType::kVariableUb) + continue; // complement integers to make coefficients positive if both bounds are // finite; otherwise, complement integers with closest bound. From 2c9e304a2f48d8f3fddbfc9ebfc4d9916c0eeeb7 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Wed, 27 Nov 2024 14:20:52 +0100 Subject: [PATCH 42/59] MAINT: declare that `highspy._core` supports free-threaded CPython This allows `highspy` to be imported in a free-threaded CPython (see PEP 703) interpreter without raising a warning and re-enabling the GIL. We've tested thread-safety of this extension module reasonably well in SciPy now, and everything seems fine on all tested platforms (Linux, macOS, Windows and x86-64/arm64). --- src/highs_bindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/highs_bindings.cpp b/src/highs_bindings.cpp index 64db9172d3..5b4069a96c 100644 --- a/src/highs_bindings.cpp +++ b/src/highs_bindings.cpp @@ -642,7 +642,7 @@ HighsStatus highs_setCallback( data.ptr()); } -PYBIND11_MODULE(_core, m) { +PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { // To keep a smaller diff, for reviewers, the declarations are not moved, but // keep in mind: // C++ enum classes :: don't need .export_values() From 9fdc6115adc47c340f5f9c5fb3f73f5398661a26 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 28 Nov 2024 16:09:37 +0000 Subject: [PATCH 43/59] First draft of HighsSimplexStats --- check/TestLpSolvers.cpp | 18 +++++++++++++ src/Highs.h | 4 +++ src/lp_data/HStruct.h | 15 +++++++++++ src/lp_data/HighsSolve.cpp | 2 +- src/simplex/HApp.h | 4 +++ src/simplex/HEkk.cpp | 41 +++++++++++++++++++++++++++++- src/simplex/HEkk.h | 7 +++++ src/simplex/HighsSimplexAnalysis.h | 1 + 8 files changed, 90 insertions(+), 2 deletions(-) diff --git a/check/TestLpSolvers.cpp b/check/TestLpSolvers.cpp index 72b94594d0..c1470fbb58 100644 --- a/check/TestLpSolvers.cpp +++ b/check/TestLpSolvers.cpp @@ -29,6 +29,7 @@ void testDualObjective(const std::string model) { std::max(1.0, std::fabs(primal_objective)); REQUIRE(relative_primal_dual_gap < 1e-12); } + void testSolver(Highs& highs, const std::string solver, IterationCount& default_iteration_count, const HighsInt int_simplex_strategy = 0) { @@ -641,3 +642,20 @@ TEST_CASE("standard-form-lp", "[highs_lp_solver]") { "maximizing\n"); testStandardForm(highs.getLp()); } + +TEST_CASE("simplex-stats", "[highs_lp_solver]") { + HighsStatus return_status; + + Highs h; + // h.setOptionValue("output_flag", dev_run); + std::string model_file = + std::string(HIGHS_DIR) + "/check/instances/adlittle.mps"; + REQUIRE(h.readModel(model_file) == HighsStatus::kOk); + const HighsSimplexStats& simplex_stats = h.getSimplexStats(); + simplex_stats.report(stdout); + h.reportSimplexStats(stdout); + REQUIRE(h.run() == HighsStatus::kOk); + simplex_stats.report(stdout); + h.reportSimplexStats(stdout); + +} diff --git a/src/Highs.h b/src/Highs.h index 04a4a3ce25..e84558039d 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -1215,6 +1215,10 @@ class Highs { static void resetGlobalScheduler(bool blocking = false); // Start of advanced methods for HiGHS MIP solver + + const HighsSimplexStats& getSimplexStats() const { return ekk_instance_.getSimplexStats(); } + void reportSimplexStats(FILE* file) const { ekk_instance_.reportSimplexStats(file); } + /** * @brief Get the hot start basis data from the most recent simplex * solve. Advanced method: for HiGHS MIP solver diff --git a/src/lp_data/HStruct.h b/src/lp_data/HStruct.h index 3963d9df1c..86a2bde39c 100644 --- a/src/lp_data/HStruct.h +++ b/src/lp_data/HStruct.h @@ -154,4 +154,19 @@ struct HighsLinearObjective { void clear(); }; +struct HighsSimplexStats { + bool valid; + HighsInt iteration_count; + HighsInt num_invert; + HighsInt last_invert_num_el; + HighsInt last_factored_basis_num_el; + double col_aq_density; + double row_ep_density; + double row_ap_density; + double row_DSE_density; + void report(FILE* file) const; + void clear(); +}; + + #endif /* LP_DATA_HSTRUCT_H_ */ diff --git a/src/lp_data/HighsSolve.cpp b/src/lp_data/HighsSolve.cpp index f1df5239a3..118b92c54a 100644 --- a/src/lp_data/HighsSolve.cpp +++ b/src/lp_data/HighsSolve.cpp @@ -17,7 +17,7 @@ #include "pdlp/CupdlpWrapper.h" #include "simplex/HApp.h" -// The method below runs simplex or ipx solver on the lp. +// The method below runs simplex, ipx or pdlp solver on the lp. HighsStatus solveLp(HighsLpSolverObject& solver_object, const string message) { HighsStatus return_status = HighsStatus::kOk; HighsStatus call_status; diff --git a/src/simplex/HApp.h b/src/simplex/HApp.h index 5e48ae9cd3..905a290c3a 100644 --- a/src/simplex/HApp.h +++ b/src/simplex/HApp.h @@ -112,6 +112,10 @@ inline HighsStatus solveLpSimplex(HighsLpSolverObject& solver_object) { // return resetModelStatusAndHighsInfo(solver_object); + // Clear the simplex stats + ekk_instance.clearSimplexStats(); + ekk_instance.simplex_stats_.iteration_count = -ekk_instance.iteration_count_; + // Assumes that the LP has a positive number of rows, since // unconstrained LPs should be solved in solveLp bool positive_num_row = solver_object.lp_.num_row_ > 0; diff --git a/src/simplex/HEkk.cpp b/src/simplex/HEkk.cpp index dc30e2cc7e..126b56b364 100644 --- a/src/simplex/HEkk.cpp +++ b/src/simplex/HEkk.cpp @@ -296,6 +296,7 @@ void HEkk::invalidate() { assert(!this->status_.is_permuted); this->status_.initialised_for_solve = false; this->invalidateBasisMatrix(); + this->simplex_stats_.clear(); } void HEkk::invalidateBasisMatrix() { @@ -2082,6 +2083,7 @@ HighsInt HEkk::computeFactor() { // number of updates shouldn't be positive info_.update_count = 0; + simplex_stats_.num_invert++; return rank_deficiency; } @@ -3500,7 +3502,18 @@ HighsStatus HEkk::returnFromEkkSolve(const HighsStatus return_status) { // Note that in timeReporting(1), analysis_.analyse_simplex_time // reverts to its value given by options_ if (analysis_.analyse_simplex_time) analysis_.reportSimplexTimer(); - + simplex_stats_.valid = true; + // Since HEkk::iteration_count_ includes iteration on presolved LP, + // simplex_stats_.iteration_count is initialised to - + // HEkk::iteration_count_ + simplex_stats_.iteration_count += iteration_count_; + // simplex_stats_.num_invert is incremented internally + simplex_stats_.last_invert_num_el = simplex_nla_.factor_.invert_num_el; + simplex_stats_.last_factored_basis_num_el = simplex_nla_.factor_.basis_matrix_num_el; + simplex_stats_.col_aq_density = analysis_.col_aq_density; + simplex_stats_.row_ep_density = analysis_.row_ep_density; + simplex_stats_.row_ap_density = analysis_.row_ap_density; + simplex_stats_.row_DSE_density = analysis_.row_DSE_density; return return_status; } @@ -4406,3 +4419,29 @@ void HEkk::unitBtranResidual(const HighsInt row_out, const HVector& row_ep, residual_norm = max(fabs(residual.array[iRow]), residual_norm); } } + +void HighsSimplexStats::report(FILE* file) const { + fprintf(file, "\nSimplex stats\n"); + fprintf(file, " valid = %d\n", this->valid); + fprintf(file, " iteration_count = %d\n", this->iteration_count); + fprintf(file, " num_invert = %d\n", this->num_invert); + fprintf(file, " last_invert_num_el = %d\n", this->last_invert_num_el); + fprintf(file, " last_factored_basis_num_el = %d\n", this->last_factored_basis_num_el); + fprintf(file, " col_aq_density = %g\n", this->col_aq_density); + fprintf(file, " row_ep_density = %g\n", this->row_ep_density); + fprintf(file, " row_ap_density = %g\n", this->row_ap_density); + fprintf(file, " row_DSE_density = %g\n", this->row_DSE_density); + +} + +void HighsSimplexStats::clear() { + valid = false; + iteration_count = 0; + num_invert = 0; + last_invert_num_el = 0; + last_factored_basis_num_el = 0; + col_aq_density = 0; + row_ep_density = 0; + row_ap_density = 0; + row_DSE_density = 0; +} diff --git a/src/simplex/HEkk.h b/src/simplex/HEkk.h index 3b3da389da..9d54524fbc 100644 --- a/src/simplex/HEkk.h +++ b/src/simplex/HEkk.h @@ -156,6 +156,10 @@ class HEkk { const vector& rowLower, const vector& rowUpper); + const HighsSimplexStats& getSimplexStats() const { return simplex_stats_; } + void clearSimplexStats() { simplex_stats_.clear(); } + void reportSimplexStats(FILE* file) const { simplex_stats_.report(file); } + // Make this private later void chooseSimplexStrategyThreads(const HighsOptions& options, HighsSimplexInfo& info); @@ -171,6 +175,7 @@ class HEkk { const std::string message, const HighsInt alt_debug_level = -1) const; bool debugNlaScalingOk(const HighsLp& lp) const; + // Data members HighsCallback* callback_; HighsOptions* options_; @@ -255,6 +260,8 @@ class HEkk { std::vector bad_basis_change_; std::vector primal_phase1_dual_; + HighsSimplexStats simplex_stats_; + private: bool isUnconstrainedLp(); void initialiseForSolve(); diff --git a/src/simplex/HighsSimplexAnalysis.h b/src/simplex/HighsSimplexAnalysis.h index d7a93d2ca6..db43446f20 100644 --- a/src/simplex/HighsSimplexAnalysis.h +++ b/src/simplex/HighsSimplexAnalysis.h @@ -251,6 +251,7 @@ class HighsSimplexAnalysis { void reportFactorTimer(); void updateInvertFormData(const HFactor& factor); void reportInvertFormData(); + HighsInt numInvert() { return num_invert; } // Control methods to be moved to HEkkControl void dualSteepestEdgeWeightError(const double computed_edge_weight, From 258e8c063e6c517244ac3cad15f814d2494856ac Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 28 Nov 2024 17:00:06 +0000 Subject: [PATCH 44/59] HighsSimplexStats struct gives data about last simplex solve --- check/TestLpSolvers.cpp | 42 ++++++++++++++++++++++++++++++----------- src/Highs.h | 8 ++++++-- src/lp_data/HStruct.h | 5 ++--- src/simplex/HApp.h | 5 ++--- src/simplex/HEkk.cpp | 20 +++++++++++--------- src/simplex/HEkk.h | 7 ++++--- 6 files changed, 56 insertions(+), 31 deletions(-) diff --git a/check/TestLpSolvers.cpp b/check/TestLpSolvers.cpp index c1470fbb58..49da8322b0 100644 --- a/check/TestLpSolvers.cpp +++ b/check/TestLpSolvers.cpp @@ -644,18 +644,38 @@ TEST_CASE("standard-form-lp", "[highs_lp_solver]") { } TEST_CASE("simplex-stats", "[highs_lp_solver]") { - HighsStatus return_status; + HighsStatus return_status; Highs h; - // h.setOptionValue("output_flag", dev_run); - std::string model_file = - std::string(HIGHS_DIR) + "/check/instances/adlittle.mps"; - REQUIRE(h.readModel(model_file) == HighsStatus::kOk); const HighsSimplexStats& simplex_stats = h.getSimplexStats(); - simplex_stats.report(stdout); - h.reportSimplexStats(stdout); - REQUIRE(h.run() == HighsStatus::kOk); - simplex_stats.report(stdout); - h.reportSimplexStats(stdout); - + h.setOptionValue("output_flag", dev_run); + std::string model_file = + std::string(HIGHS_DIR) + "/check/instances/adlittle.mps"; + REQUIRE(h.readModel(model_file) == HighsStatus::kOk); + + REQUIRE(h.run() == HighsStatus::kOk); + REQUIRE(simplex_stats.valid); + REQUIRE(simplex_stats.iteration_count == 0); + REQUIRE(simplex_stats.num_invert == 1); + REQUIRE(simplex_stats.last_invert_num_el > 0); + REQUIRE(simplex_stats.last_factored_basis_num_el > 0); + REQUIRE(simplex_stats.col_aq_density == 0); + REQUIRE(simplex_stats.row_ep_density == 0); + REQUIRE(simplex_stats.row_ap_density == 0); + REQUIRE(simplex_stats.row_DSE_density == 0); + if (dev_run) h.reportSimplexStats(stdout); + + h.clearSolver(); + h.setOptionValue("presolve", kHighsOffString); + REQUIRE(h.run() == HighsStatus::kOk); + REQUIRE(simplex_stats.valid); + REQUIRE(simplex_stats.iteration_count > 0); + REQUIRE(simplex_stats.num_invert > 0); + REQUIRE(simplex_stats.last_invert_num_el > 0); + REQUIRE(simplex_stats.last_factored_basis_num_el > 0); + REQUIRE(simplex_stats.col_aq_density > 0); + REQUIRE(simplex_stats.row_ep_density > 0); + REQUIRE(simplex_stats.row_ap_density > 0); + REQUIRE(simplex_stats.row_DSE_density > 0); + if (dev_run) h.reportSimplexStats(stdout); } diff --git a/src/Highs.h b/src/Highs.h index e84558039d..70d800a5de 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -1216,8 +1216,12 @@ class Highs { // Start of advanced methods for HiGHS MIP solver - const HighsSimplexStats& getSimplexStats() const { return ekk_instance_.getSimplexStats(); } - void reportSimplexStats(FILE* file) const { ekk_instance_.reportSimplexStats(file); } + const HighsSimplexStats& getSimplexStats() const { + return ekk_instance_.getSimplexStats(); + } + void reportSimplexStats(FILE* file) const { + ekk_instance_.reportSimplexStats(file); + } /** * @brief Get the hot start basis data from the most recent simplex diff --git a/src/lp_data/HStruct.h b/src/lp_data/HStruct.h index 86a2bde39c..35a0b0ee42 100644 --- a/src/lp_data/HStruct.h +++ b/src/lp_data/HStruct.h @@ -164,9 +164,8 @@ struct HighsSimplexStats { double row_ep_density; double row_ap_density; double row_DSE_density; - void report(FILE* file) const; - void clear(); + void report(FILE* file, const std::string message = "") const; + void initialise(const HighsInt iteration_count_ = 0); }; - #endif /* LP_DATA_HSTRUCT_H_ */ diff --git a/src/simplex/HApp.h b/src/simplex/HApp.h index 905a290c3a..b8de2a3f59 100644 --- a/src/simplex/HApp.h +++ b/src/simplex/HApp.h @@ -112,9 +112,8 @@ inline HighsStatus solveLpSimplex(HighsLpSolverObject& solver_object) { // return resetModelStatusAndHighsInfo(solver_object); - // Clear the simplex stats - ekk_instance.clearSimplexStats(); - ekk_instance.simplex_stats_.iteration_count = -ekk_instance.iteration_count_; + // Initialise the simplex stats + ekk_instance.initialiseSimplexStats(); // Assumes that the LP has a positive number of rows, since // unconstrained LPs should be solved in solveLp diff --git a/src/simplex/HEkk.cpp b/src/simplex/HEkk.cpp index 126b56b364..caa4e641e0 100644 --- a/src/simplex/HEkk.cpp +++ b/src/simplex/HEkk.cpp @@ -296,7 +296,7 @@ void HEkk::invalidate() { assert(!this->status_.is_permuted); this->status_.initialised_for_solve = false; this->invalidateBasisMatrix(); - this->simplex_stats_.clear(); + this->simplex_stats_.initialise(); } void HEkk::invalidateBasisMatrix() { @@ -3509,7 +3509,8 @@ HighsStatus HEkk::returnFromEkkSolve(const HighsStatus return_status) { simplex_stats_.iteration_count += iteration_count_; // simplex_stats_.num_invert is incremented internally simplex_stats_.last_invert_num_el = simplex_nla_.factor_.invert_num_el; - simplex_stats_.last_factored_basis_num_el = simplex_nla_.factor_.basis_matrix_num_el; + simplex_stats_.last_factored_basis_num_el = + simplex_nla_.factor_.basis_matrix_num_el; simplex_stats_.col_aq_density = analysis_.col_aq_density; simplex_stats_.row_ep_density = analysis_.row_ep_density; simplex_stats_.row_ap_density = analysis_.row_ap_density; @@ -4420,23 +4421,24 @@ void HEkk::unitBtranResidual(const HighsInt row_out, const HVector& row_ep, } } -void HighsSimplexStats::report(FILE* file) const { - fprintf(file, "\nSimplex stats\n"); +void HighsSimplexStats::report(FILE* file, std::string message) const { + fprintf(file, "\nSimplex stats: %s\n", message.c_str()); fprintf(file, " valid = %d\n", this->valid); fprintf(file, " iteration_count = %d\n", this->iteration_count); fprintf(file, " num_invert = %d\n", this->num_invert); - fprintf(file, " last_invert_num_el = %d\n", this->last_invert_num_el); - fprintf(file, " last_factored_basis_num_el = %d\n", this->last_factored_basis_num_el); + fprintf(file, " last_invert_num_el = %d\n", + this->last_invert_num_el); + fprintf(file, " last_factored_basis_num_el = %d\n", + this->last_factored_basis_num_el); fprintf(file, " col_aq_density = %g\n", this->col_aq_density); fprintf(file, " row_ep_density = %g\n", this->row_ep_density); fprintf(file, " row_ap_density = %g\n", this->row_ap_density); fprintf(file, " row_DSE_density = %g\n", this->row_DSE_density); - } -void HighsSimplexStats::clear() { +void HighsSimplexStats::initialise(const HighsInt iteration_count_) { valid = false; - iteration_count = 0; + iteration_count = -iteration_count_; num_invert = 0; last_invert_num_el = 0; last_factored_basis_num_el = 0; diff --git a/src/simplex/HEkk.h b/src/simplex/HEkk.h index 9d54524fbc..0b253bebc0 100644 --- a/src/simplex/HEkk.h +++ b/src/simplex/HEkk.h @@ -157,8 +157,10 @@ class HEkk { const vector& rowUpper); const HighsSimplexStats& getSimplexStats() const { return simplex_stats_; } - void clearSimplexStats() { simplex_stats_.clear(); } - void reportSimplexStats(FILE* file) const { simplex_stats_.report(file); } + void initialiseSimplexStats() { simplex_stats_.initialise(iteration_count_); } + void reportSimplexStats(FILE* file, const std::string message = "") const { + simplex_stats_.report(file, message); + } // Make this private later void chooseSimplexStrategyThreads(const HighsOptions& options, @@ -175,7 +177,6 @@ class HEkk { const std::string message, const HighsInt alt_debug_level = -1) const; bool debugNlaScalingOk(const HighsLp& lp) const; - // Data members HighsCallback* callback_; HighsOptions* options_; From 6769ea44a7c5f3dcf4985b601dcdcdce64b96b26 Mon Sep 17 00:00:00 2001 From: quantresearch1 <107650207+quantresearch1@users.noreply.github.com> Date: Tue, 3 Dec 2024 14:51:48 -0500 Subject: [PATCH 45/59] Fix issue #2025 Numpy versions prior to 1.22.0 will raise >>> ndarray_object_type = np.ndarray[Any, np.dtype[np.object_]] Traceback (most recent call last): File "", line 1, in TypeError: 'numpy._DTypeMeta' object is not subscriptable --- src/highspy/highs.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/highspy/highs.py b/src/highspy/highs.py index 80255b15d6..bc04ce8d77 100644 --- a/src/highspy/highs.py +++ b/src/highspy/highs.py @@ -17,7 +17,8 @@ ) # backwards typing support information for HighspyArray -if sys.version_info >= (3, 9): +np_version = tuple(map(int, np.__version__.split('.'))) +if sys.version_info >= (3, 9) and np_version >= (1.22.0): ndarray_object_type = np.ndarray[Any, np.dtype[np.object_]] else: ndarray_object_type = np.ndarray From e574144b67747287ca5e3bce3f3983967dff2d30 Mon Sep 17 00:00:00 2001 From: quantresearch1 <107650207+quantresearch1@users.noreply.github.com> Date: Tue, 3 Dec 2024 19:57:53 +0000 Subject: [PATCH 46/59] fix-2025 --- src/highspy/highs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/highspy/highs.py b/src/highspy/highs.py index bc04ce8d77..af42594be9 100644 --- a/src/highspy/highs.py +++ b/src/highspy/highs.py @@ -18,7 +18,7 @@ # backwards typing support information for HighspyArray np_version = tuple(map(int, np.__version__.split('.'))) -if sys.version_info >= (3, 9) and np_version >= (1.22.0): +if sys.version_info >= (3, 9) and np_version >= (1,22,0): ndarray_object_type = np.ndarray[Any, np.dtype[np.object_]] else: ndarray_object_type = np.ndarray From d02fd43f4f0d87bf6e86147cd091ea2900005989 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 4 Dec 2024 08:15:36 +0100 Subject: [PATCH 47/59] WIP --- src/mip/HighsCutGeneration.cpp | 79 +++++++++++++--------------------- src/mip/HighsCutGeneration.h | 4 ++ src/mip/HighsTransformedLp.cpp | 5 +++ 3 files changed, 38 insertions(+), 50 deletions(-) diff --git a/src/mip/HighsCutGeneration.cpp b/src/mip/HighsCutGeneration.cpp index d265c24b78..140a05a9ad 100644 --- a/src/mip/HighsCutGeneration.cpp +++ b/src/mip/HighsCutGeneration.cpp @@ -523,12 +523,7 @@ bool HighsCutGeneration::cmirCutGenerationHeuristic(double minEfficacy, if (isintegral[i]) { integerinds.push_back(i); - if (upper[i] < 2 * solval[i]) { - complementation[i] = 1 - complementation[i]; - rhs -= upper[i] * vals[i]; - vals[i] = -vals[i]; - solval[i] = upper[i] - solval[i]; - } + if (upper[i] < 2 * solval[i]) flipComplementation(i); if (onlyInitialCMIRScale) continue; @@ -655,10 +650,7 @@ bool HighsCutGeneration::cmirCutGenerationHeuristic(double minEfficacy, if (upper[k] == kHighsInf) continue; if (solval[k] <= feastol) continue; - complementation[k] = 1 - complementation[k]; - solval[k] = upper[k] - solval[k]; - rhs -= upper[k] * vals[k]; - vals[k] = -vals[k]; + flipComplementation(k); double delta = bestdelta; double scale = 1.0 / delta; @@ -667,20 +659,13 @@ bool HighsCutGeneration::cmirCutGenerationHeuristic(double minEfficacy, double f0 = scalrhs - downrhs; if (f0 < f0min || f0 > f0max) { - complementation[k] = 1 - complementation[k]; - solval[k] = upper[k] - solval[k]; - rhs -= upper[k] * vals[k]; - vals[k] = -vals[k]; - + flipComplementation(k); continue; } double oneoveroneminusf0 = 1.0 / (1.0 - f0); if (oneoveroneminusf0 > maxCMirScale) { - complementation[k] = 1 - complementation[k]; - solval[k] = upper[k] - solval[k]; - rhs -= upper[k] * vals[k]; - vals[k] = -vals[k]; + flipComplementation(k); continue; } @@ -704,10 +689,7 @@ bool HighsCutGeneration::cmirCutGenerationHeuristic(double minEfficacy, if (efficacy > bestefficacy) { bestefficacy = efficacy; } else { - complementation[k] = 1 - complementation[k]; - solval[k] = upper[k] - solval[k]; - rhs -= upper[k] * vals[k]; - vals[k] = -vals[k]; + flipComplementation(k); } } @@ -944,9 +926,7 @@ bool HighsCutGeneration::preprocessBaseInequality(bool& hasUnboundedInts, if (upper[i] - solval[i] < solval[i]) { if (complementation.empty()) complementation.resize(rowlen); - complementation[i] = 1 - complementation[i]; - rhs -= upper[i] * vals[i]; - vals[i] = -vals[i]; + flipComplementation(i); } // relax positive continuous variables and those with small contributions @@ -1144,10 +1124,7 @@ bool HighsCutGeneration::generateCut(HighsTransformedLp& transLp, for (HighsInt i = 0; i != rowlen; ++i) { if (vals[i] > 0 || !isintegral[i]) continue; - complementation[i] = 1 - complementation[i]; - rhs -= upper[i] * vals[i]; - vals[i] = -vals[i]; - solval[i] = upper[i] - solval[i]; + flipComplementation(i); } } @@ -1203,17 +1180,9 @@ bool HighsCutGeneration::generateCut(HighsTransformedLp& transLp, rhs = tmpRhs; } else { minMirEfficacy += efficacy; - if (!complementation.empty()) { - // remove the complementation if it exists, so that the values stored - // are uncomplemented - for (HighsInt i = 0; i != rowlen; ++i) { - if (complementation[i]) { - rhs -= upper[i] * vals[i]; - vals[i] = -vals[i]; - solval[i] = upper[i] - solval[i]; - } - } - } + // remove the complementation if it exists, so that the values stored + // are uncomplemented + removeComplementation(); std::swap(tmpRhs, rhs); } } @@ -1252,15 +1221,8 @@ bool HighsCutGeneration::generateCut(HighsTransformedLp& transLp, return false; } - if (!complementation.empty()) { - // remove the complementation if exists - for (HighsInt i = 0; i != rowlen; ++i) { - if (complementation[i]) { - rhs -= upper[i] * vals[i]; - vals[i] = -vals[i]; - } - } - } + // remove the complementation if exists + removeComplementation(); // remove zeros in place for (HighsInt i = rowlen - 1; i >= 0; --i) { @@ -1542,3 +1504,20 @@ bool HighsCutGeneration::finalizeAndAddCut(std::vector& inds_, // of a cut already in the pool return cutindex != -1; } + +void HighsCutGeneration::flipComplementation(HighsInt index) { + // flip complementation + complementation[index] = 1 - complementation[index]; + solval[index] = upper[index] - solval[index]; + rhs -= upper[index] * vals[index]; + vals[index] = -vals[index]; +} + +void HighsCutGeneration::removeComplementation() { + // remove complementation + if (complementation.empty()) return; + // remove the complementation if exists + for (HighsInt i = 0; i != rowlen; ++i) { + if (complementation[i]) flipComplementation(i); + } +} \ No newline at end of file diff --git a/src/mip/HighsCutGeneration.h b/src/mip/HighsCutGeneration.h index cc18998c6d..8861e81123 100644 --- a/src/mip/HighsCutGeneration.h +++ b/src/mip/HighsCutGeneration.h @@ -73,6 +73,10 @@ class HighsCutGeneration { bool preprocessBaseInequality(bool& hasUnboundedInts, bool& hasGeneralInts, bool& hasContinuous); + void flipComplementation(HighsInt index); + + void removeComplementation(); + public: HighsCutGeneration(const HighsLpRelaxation& lpRelaxation, HighsCutPool& cutpool); diff --git a/src/mip/HighsTransformedLp.cpp b/src/mip/HighsTransformedLp.cpp index 68be21844b..a11e25c3e9 100644 --- a/src/mip/HighsTransformedLp.cpp +++ b/src/mip/HighsTransformedLp.cpp @@ -192,6 +192,11 @@ bool HighsTransformedLp::transform(std::vector& vals, if (lprelaxation.isColIntegral(col)) { if (ub - lb <= 1.5 || boundDist[col] != 0.0 || simpleLbDist[col] == 0 || simpleUbDist[col] == 0) { + // since we skip the handling of variable bound constraints for all + // binary and some general-integer variables, the bound type used should + // be a simple lower or upper bound + assert(oldBoundType == BoundType::kSimpleLb || + oldBoundType == BoundType::kSimpleUb); i++; continue; } From 20dbb7a9c678fa8065f86b7c7df7b0a9a53d9cc5 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 4 Dec 2024 08:30:35 +0100 Subject: [PATCH 48/59] fix comment --- src/mip/HighsCutGeneration.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/mip/HighsCutGeneration.cpp b/src/mip/HighsCutGeneration.cpp index 140a05a9ad..82693d14a0 100644 --- a/src/mip/HighsCutGeneration.cpp +++ b/src/mip/HighsCutGeneration.cpp @@ -1514,9 +1514,8 @@ void HighsCutGeneration::flipComplementation(HighsInt index) { } void HighsCutGeneration::removeComplementation() { - // remove complementation + // remove the complementation if it exists if (complementation.empty()) return; - // remove the complementation if exists for (HighsInt i = 0; i != rowlen; ++i) { if (complementation[i]) flipComplementation(i); } From 38bfe780a403a8413d7cfb0bab7de9c2a0e313e0 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 4 Dec 2024 08:43:51 +0100 Subject: [PATCH 49/59] Add one more utility --- src/mip/HighsCutGeneration.cpp | 54 +++++++++++++--------------------- src/mip/HighsCutGeneration.h | 3 ++ 2 files changed, 23 insertions(+), 34 deletions(-) diff --git a/src/mip/HighsCutGeneration.cpp b/src/mip/HighsCutGeneration.cpp index 82693d14a0..54f64c12c0 100644 --- a/src/mip/HighsCutGeneration.cpp +++ b/src/mip/HighsCutGeneration.cpp @@ -534,11 +534,8 @@ bool HighsCutGeneration::cmirCutGenerationHeuristic(double minEfficacy, deltas.push_back(delta); } } else { - continuouscontribution += vals[i] * solval[i]; - - if (vals[i] > 0 && solval[i] <= feastol) continue; - if (vals[i] < 0 && solval[i] >= upper[i] - feastol) continue; - continuoussqrnorm += vals[i] * vals[i]; + updateViolationAndNorm(i, vals[i], continuouscontribution, + continuoussqrnorm); } } @@ -591,12 +588,7 @@ bool HighsCutGeneration::cmirCutGenerationHeuristic(double minEfficacy, double downaj = fast_floor(scalaj + kHighsTiny); double fj = scalaj - downaj; double aj = downaj + std::max(0.0, fj - f0); - - viol += aj * solval[j]; - - if (aj > 0 && solval[j] <= feastol) continue; - if (aj < 0 && solval[j] >= upper[j] - feastol) continue; - sqrnorm += aj * aj; + updateViolationAndNorm(j, aj, viol, sqrnorm); } double efficacy = viol / sqrt(sqrnorm); @@ -628,12 +620,7 @@ bool HighsCutGeneration::cmirCutGenerationHeuristic(double minEfficacy, double downaj = fast_floor(scalaj + kHighsTiny); double fj = scalaj - downaj; double aj = downaj + std::max(0.0, fj - f0); - - viol += aj * solval[j]; - - if (aj > 0 && solval[j] <= feastol) continue; - if (aj < 0 && solval[j] >= upper[j] - feastol) continue; - sqrnorm += aj * aj; + updateViolationAndNorm(j, aj, viol, sqrnorm); } double efficacy = viol / sqrt(sqrnorm); @@ -677,12 +664,7 @@ bool HighsCutGeneration::cmirCutGenerationHeuristic(double minEfficacy, double downaj = fast_floor(scalaj + kHighsTiny); double fj = scalaj - downaj; double aj = downaj + std::max(0.0, fj - f0); - - viol += aj * solval[j]; - - if (aj > 0 && solval[j] <= feastol) continue; - if (aj < 0 && solval[j] >= upper[j] - feastol) continue; - sqrnorm += aj * aj; + updateViolationAndNorm(j, aj, viol, sqrnorm); } double efficacy = viol / sqrt(sqrnorm); @@ -1167,11 +1149,7 @@ bool HighsCutGeneration::generateCut(HighsTransformedLp& transLp, double sqrnorm = 0.0; for (HighsInt i = 0; i < rowlen; ++i) { - violation += vals[i] * solval[i]; - - if (vals[i] > 0 && solval[i] <= feastol) continue; - if (vals[i] < 0 && solval[i] >= upper[i] - feastol) continue; - sqrnorm += vals[i] * vals[i]; + updateViolationAndNorm(i, vals[i], violation, sqrnorm); } double efficacy = violation / std::sqrt(sqrnorm); @@ -1371,11 +1349,7 @@ bool HighsCutGeneration::generateConflict(HighsDomain& localdomain, double sqrnorm = 0.0; for (HighsInt i = 0; i < rowlen; ++i) { - violation += vals[i] * solval[i]; - - if (vals[i] > 0 && solval[i] <= feastol) continue; - if (vals[i] < 0 && solval[i] >= upper[i] - feastol) continue; - sqrnorm += vals[i] * vals[i]; + updateViolationAndNorm(i, vals[i], violation, sqrnorm); } minEfficacy = violation / std::sqrt(sqrnorm); @@ -1519,4 +1493,16 @@ void HighsCutGeneration::removeComplementation() { for (HighsInt i = 0; i != rowlen; ++i) { if (complementation[i]) flipComplementation(i); } -} \ No newline at end of file +} + +void HighsCutGeneration::updateViolationAndNorm(HighsInt index, double aj, + double& violation, + double& norm) { + // update violation + violation += aj * solval[index]; + + // update norm + if (aj > 0 && solval[index] <= feastol) return; + if (aj < 0 && solval[index] >= upper[index] - feastol) return; + norm += aj * aj; +} diff --git a/src/mip/HighsCutGeneration.h b/src/mip/HighsCutGeneration.h index 8861e81123..5a6e2396a9 100644 --- a/src/mip/HighsCutGeneration.h +++ b/src/mip/HighsCutGeneration.h @@ -77,6 +77,9 @@ class HighsCutGeneration { void removeComplementation(); + void updateViolationAndNorm(HighsInt index, double aj, double& violation, + double& norm); + public: HighsCutGeneration(const HighsLpRelaxation& lpRelaxation, HighsCutPool& cutpool); From 7a5ed4b139a4df5446c79690bd6d56ae4dd84aa6 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 4 Dec 2024 10:16:50 +0100 Subject: [PATCH 50/59] Add another small utility to simplify code --- src/mip/HighsCutGeneration.cpp | 264 +++++++++++++-------------------- src/mip/HighsCutGeneration.h | 6 + 2 files changed, 107 insertions(+), 163 deletions(-) diff --git a/src/mip/HighsCutGeneration.cpp b/src/mip/HighsCutGeneration.cpp index 54f64c12c0..16db0b5b81 100644 --- a/src/mip/HighsCutGeneration.cpp +++ b/src/mip/HighsCutGeneration.cpp @@ -1110,94 +1110,10 @@ bool HighsCutGeneration::generateCut(HighsTransformedLp& transLp, } } - const double minEfficacy = 10 * feastol; - - if (hasUnboundedInts) { - if (!cmirCutGenerationHeuristic(minEfficacy, onlyInitialCMIRScale)) - return false; - } else { - // 1. Determine a cover, cover does not need to be minimal as neither of - // the - // lifting functions have minimality of the cover as necessary facet - // condition - std::vector tmpVals(vals, vals + rowlen); - std::vector tmpInds(inds, inds + rowlen); - HighsCDouble tmpRhs = rhs; - bool success = false; - do { - if (!determineCover()) break; - - // 2. use superadditive lifting function depending on structure of base - // inequality: - // We have 3 lifting functions available for pure binary knapsack sets, - // for mixed-binary knapsack sets and for mixed integer knapsack sets. - if (!hasContinuous && !hasGeneralInts) { - separateLiftedKnapsackCover(); - success = true; - } else if (hasGeneralInts) { - success = separateLiftedMixedIntegerCover(); - } else { - assert(hasContinuous); - assert(!hasGeneralInts); - success = separateLiftedMixedBinaryCover(); - } - } while (false); - - double minMirEfficacy = minEfficacy; - if (success) { - double violation = -double(rhs); - double sqrnorm = 0.0; - - for (HighsInt i = 0; i < rowlen; ++i) { - updateViolationAndNorm(i, vals[i], violation, sqrnorm); - } - - double efficacy = violation / std::sqrt(sqrnorm); - if (efficacy <= minEfficacy) { - success = false; - rhs = tmpRhs; - } else { - minMirEfficacy += efficacy; - // remove the complementation if it exists, so that the values stored - // are uncomplemented - removeComplementation(); - std::swap(tmpRhs, rhs); - } - } - - inds = tmpInds.data(); - vals = tmpVals.data(); - - // save data that might otherwise be overwritten when calling the cmir - // separator - bool saveIntegalSupport = integralSupport; - bool saveIntegralCoefficients = integralCoefficients; - - bool cmirSuccess = - cmirCutGenerationHeuristic(minMirEfficacy, onlyInitialCMIRScale); - - if (cmirSuccess) { - // take the cmir cut as it is better - inds_.swap(tmpInds); - vals_.swap(tmpVals); - inds = inds_.data(); - vals = vals_.data(); - } else if (success) { - // take the previous lifted cut as cmir could not improve - // as we already removed the complementation we simply clear - // the vector if altered by the cmir routine and restore the old - // right hand side and values - rhs = tmpRhs; - complementation.clear(); - inds = inds_.data(); - vals = vals_.data(); - // restore indicators - integralSupport = saveIntegalSupport; - integralCoefficients = saveIntegralCoefficients; - } else - // neither cmir nor lifted cut successful - return false; - } + // try to generate a cut + if (!tryGenerateCut(inds_, vals_, hasUnboundedInts, hasGeneralInts, + hasContinuous, 10 * feastol, onlyInitialCMIRScale)) + return false; // remove the complementation if exists removeComplementation(); @@ -1312,81 +1228,10 @@ bool HighsCutGeneration::generateConflict(HighsDomain& localdomain, hasContinuous)) return false; - if (hasUnboundedInts) { - if (!cmirCutGenerationHeuristic(feastol)) return false; - } else { - // 1. Determine a cover, cover does not need to be minimal as neither of - // the - // lifting functions have minimality of the cover as necessary facet - // condition - std::vector tmpVals(vals, vals + rowlen); - std::vector tmpInds(inds, inds + rowlen); - std::vector tmpComplementation(complementation); - HighsCDouble tmpRhs = rhs; - bool success = false; - do { - if (!determineCover(false)) break; - - // 2. use superadditive lifting function depending on structure of base - // inequality: - // We have 3 lifting functions available for pure binary knapsack sets, - // for mixed-binary knapsack sets and for mixed integer knapsack sets. - if (!hasContinuous && !hasGeneralInts) { - separateLiftedKnapsackCover(); - success = true; - } else if (hasGeneralInts) { - success = separateLiftedMixedIntegerCover(); - } else { - assert(hasContinuous); - assert(!hasGeneralInts); - success = separateLiftedMixedBinaryCover(); - } - } while (false); - - double minEfficacy = feastol; - if (success) { - double violation = -double(rhs); - double sqrnorm = 0.0; - - for (HighsInt i = 0; i < rowlen; ++i) { - updateViolationAndNorm(i, vals[i], violation, sqrnorm); - } - - minEfficacy = violation / std::sqrt(sqrnorm); - minEfficacy += feastol; - std::swap(tmpRhs, rhs); - } - - inds = tmpInds.data(); - vals = tmpVals.data(); - - // save data that might otherwise be overwritten when calling the cmir - // separator - bool saveIntegalSupport = integralSupport; - bool saveIntegralCoefficients = integralCoefficients; - - bool cmirSuccess = cmirCutGenerationHeuristic(minEfficacy); - - if (cmirSuccess) { - // take the cmir cut as it is better - proofinds.swap(tmpInds); - proofvals.swap(tmpVals); - inds = proofinds.data(); - vals = proofvals.data(); - } else if (success) { - // take the previous lifted cut as cmir could not improve - // we restore the old complementation vector, right hand side, and values - rhs = tmpRhs; - complementation.swap(tmpComplementation); - inds = proofinds.data(); - vals = proofvals.data(); - // restore indicators - integralSupport = saveIntegalSupport; - integralCoefficients = saveIntegralCoefficients; - } else - // neither cmir nor lifted cut successful - return false; - } + // try to generate a cut + if (!tryGenerateCut(proofinds, proofvals, hasUnboundedInts, hasGeneralInts, + hasContinuous, feastol, false, false, false)) + return false; // remove the complementation if (!complementation.empty()) { @@ -1506,3 +1351,96 @@ void HighsCutGeneration::updateViolationAndNorm(HighsInt index, double aj, if (aj < 0 && solval[index] >= upper[index] - feastol) return; norm += aj * aj; } + +bool HighsCutGeneration::tryGenerateCut(std::vector& inds_, + std::vector& vals_, + bool hasUnboundedInts, + bool hasGeneralInts, bool hasContinuous, + double minEfficacy, + bool onlyInitialCMIRScale, + bool allowRejectCut, bool lpSol) { + // use cmir if there are unbounded integer variables + if (hasUnboundedInts) + return cmirCutGenerationHeuristic(minEfficacy, onlyInitialCMIRScale); + + // 1. Determine a cover, cover does not need to be minimal as neither of + // the lifting functions have minimality of the cover as necessary facet + // condition + std::vector tmpVals(vals, vals + rowlen); + std::vector tmpInds(inds, inds + rowlen); + std::vector tmpComplementation(complementation); + std::vector tmpSolval(solval); + HighsCDouble tmpRhs = rhs; + bool success = false; + do { + if (!determineCover(lpSol)) break; + + // 2. use superadditive lifting function depending on structure of base + // inequality: + // We have 3 lifting functions available for pure binary knapsack sets, + // for mixed-binary knapsack sets and for mixed integer knapsack sets. + if (!hasContinuous && !hasGeneralInts) { + separateLiftedKnapsackCover(); + success = true; + } else if (hasGeneralInts) { + success = separateLiftedMixedIntegerCover(); + } else { + assert(hasContinuous); + assert(!hasGeneralInts); + success = separateLiftedMixedBinaryCover(); + } + } while (false); + + double minMirEfficacy = minEfficacy; + if (success) { + double violation = -double(rhs); + double sqrnorm = 0.0; + + for (HighsInt i = 0; i < rowlen; ++i) { + updateViolationAndNorm(i, vals[i], violation, sqrnorm); + } + + double efficacy = violation / std::sqrt(sqrnorm); + if (allowRejectCut && efficacy <= minEfficacy) { + success = false; + rhs = tmpRhs; + } else { + minMirEfficacy += efficacy; + std::swap(tmpRhs, rhs); + } + } + + inds = tmpInds.data(); + vals = tmpVals.data(); + + // save data that might otherwise be overwritten when calling the cmir + // separator + bool saveIntegalSupport = integralSupport; + bool saveIntegralCoefficients = integralCoefficients; + + if (cmirCutGenerationHeuristic(minMirEfficacy, onlyInitialCMIRScale)) { + // take the cmir cut as it is better + inds_.swap(tmpInds); + vals_.swap(tmpVals); + inds = inds_.data(); + vals = vals_.data(); + return true; + } else if (success) { + // take the previous lifted cut as cmir could not improve + // we restore the old complementation vector, right hand side, and values + rhs = tmpRhs; + // note that the solution vector solval also needs to be restored because it + // depends on the complementation. it would be OK not to restore solval, if + // there would be a guarantee that it is not used from here on. + complementation.swap(tmpComplementation); + solval.swap(tmpSolval); + inds = inds_.data(); + vals = vals_.data(); + // restore indicators + integralSupport = saveIntegalSupport; + integralCoefficients = saveIntegralCoefficients; + return true; + } else + // neither cmir nor lifted cut successful + return false; +} diff --git a/src/mip/HighsCutGeneration.h b/src/mip/HighsCutGeneration.h index 5a6e2396a9..3e85eb0137 100644 --- a/src/mip/HighsCutGeneration.h +++ b/src/mip/HighsCutGeneration.h @@ -80,6 +80,12 @@ class HighsCutGeneration { void updateViolationAndNorm(HighsInt index, double aj, double& violation, double& norm); + bool tryGenerateCut(std::vector& inds, std::vector& vals, + bool hasUnboundedInts, bool hasGeneralInts, + bool hasContinuous, double minEfficacy, + bool onlyInitialCMIRScale = false, + bool allowRejectCut = true, bool lpSol = true); + public: HighsCutGeneration(const HighsLpRelaxation& lpRelaxation, HighsCutPool& cutpool); From ec017499bd6bff64f389c9ccb6d0000e6c4730e9 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 4 Dec 2024 14:07:10 +0100 Subject: [PATCH 51/59] Add some more comments --- src/mip/HighsCutGeneration.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/mip/HighsCutGeneration.cpp b/src/mip/HighsCutGeneration.cpp index 16db0b5b81..8bf504efae 100644 --- a/src/mip/HighsCutGeneration.cpp +++ b/src/mip/HighsCutGeneration.cpp @@ -1363,14 +1363,16 @@ bool HighsCutGeneration::tryGenerateCut(std::vector& inds_, if (hasUnboundedInts) return cmirCutGenerationHeuristic(minEfficacy, onlyInitialCMIRScale); - // 1. Determine a cover, cover does not need to be minimal as neither of - // the lifting functions have minimality of the cover as necessary facet - // condition + // 0. Save data before determining cover and applying lifting functions std::vector tmpVals(vals, vals + rowlen); std::vector tmpInds(inds, inds + rowlen); std::vector tmpComplementation(complementation); std::vector tmpSolval(solval); HighsCDouble tmpRhs = rhs; + + // 1. Determine a cover, cover does not need to be minimal as neither of + // the lifting functions have minimality of the cover as necessary facet + // condition bool success = false; do { if (!determineCover(lpSol)) break; @@ -1393,23 +1395,28 @@ bool HighsCutGeneration::tryGenerateCut(std::vector& inds_, double minMirEfficacy = minEfficacy; if (success) { + // compute violation and squared norm double violation = -double(rhs); double sqrnorm = 0.0; - for (HighsInt i = 0; i < rowlen; ++i) { updateViolationAndNorm(i, vals[i], violation, sqrnorm); } + // compute efficacy (distance cut off) double efficacy = violation / std::sqrt(sqrnorm); if (allowRejectCut && efficacy <= minEfficacy) { + // reject cut success = false; rhs = tmpRhs; } else { + // accept cut and increase minimum efficiency requirement for cmir cut minMirEfficacy += efficacy; std::swap(tmpRhs, rhs); } } + // restore indices and values; lifting methods do not modify complementation + // and, thus, complementation-related data does not have to be restored here. inds = tmpInds.data(); vals = tmpVals.data(); From 9ed363afc733bcf8fde856a53159a6f047df4118 Mon Sep 17 00:00:00 2001 From: Arnaud Baguet Date: Thu, 5 Dec 2024 17:37:34 -0500 Subject: [PATCH 52/59] fix #2017 --- CMakeLists.txt | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c8deeee559..d27135425b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -319,16 +319,17 @@ if(NOT FAST_BUILD) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${HIGHS_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) endif() -if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86\_64|i686)") - if (WIN32) - message("FLAG_MPOPCNT_SUPPORTED is not available on this architecture") - else() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mpopcnt") - endif() -elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(ppc64|powerpc64)" AND NOT APPLE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mpopcntd") +include(CheckCXXCompilerFlag) +if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(ppc64|powerpc64)" AND NOT APPLE) + check_cxx_compiler_flag("-mpopcntd" COMPILER_SUPPORTS_POPCNTD) + if(COMPILER_SUPPORTS_POPCNTD) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mpopcntd") + endif() else() - message("FLAG_MPOPCNT_SUPPORTED is not available on this architecture") + check_cxx_compiler_flag("-mpopcnt" COMPILER_SUPPORTS_POPCNT) + if(COMPILER_SUPPORTS_POPCNT) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mpopcnt") + endif() endif() option(DEBUGSOL "check the debug solution" OFF) From b84d57d8e1bbe2bf42a18eae0f355b0a0cf0978f Mon Sep 17 00:00:00 2001 From: fwesselm Date: Fri, 6 Dec 2024 10:01:21 +0100 Subject: [PATCH 53/59] Add comment --- src/mip/HighsCutGeneration.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mip/HighsCutGeneration.cpp b/src/mip/HighsCutGeneration.cpp index 8bf504efae..7067ade0b7 100644 --- a/src/mip/HighsCutGeneration.cpp +++ b/src/mip/HighsCutGeneration.cpp @@ -905,7 +905,9 @@ bool HighsCutGeneration::preprocessBaseInequality(bool& hasUnboundedInts, lpRelaxation.isColIntegral(inds[i]) && std::abs(vals[i]) > 10 * feastol; if (!isintegral[i]) { - if (upper[i] - solval[i] < solval[i]) { + // complement non-integer variable (cmir separation heuristic complements + // integral variables in the same way) + if (upper[i] < 2 * solval[i]) { if (complementation.empty()) complementation.resize(rowlen); flipComplementation(i); From 667ab4b7bfe14ac39e350edee80c019d93968de1 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Mon, 9 Dec 2024 09:46:13 +0100 Subject: [PATCH 54/59] Use const --- src/mip/HighsCutGeneration.cpp | 2 +- src/mip/HighsCutGeneration.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mip/HighsCutGeneration.cpp b/src/mip/HighsCutGeneration.cpp index 7067ade0b7..14cdb034c8 100644 --- a/src/mip/HighsCutGeneration.cpp +++ b/src/mip/HighsCutGeneration.cpp @@ -1344,7 +1344,7 @@ void HighsCutGeneration::removeComplementation() { void HighsCutGeneration::updateViolationAndNorm(HighsInt index, double aj, double& violation, - double& norm) { + double& norm) const { // update violation violation += aj * solval[index]; diff --git a/src/mip/HighsCutGeneration.h b/src/mip/HighsCutGeneration.h index 3e85eb0137..dc45924e6a 100644 --- a/src/mip/HighsCutGeneration.h +++ b/src/mip/HighsCutGeneration.h @@ -78,7 +78,7 @@ class HighsCutGeneration { void removeComplementation(); void updateViolationAndNorm(HighsInt index, double aj, double& violation, - double& norm); + double& norm) const; bool tryGenerateCut(std::vector& inds, std::vector& vals, bool hasUnboundedInts, bool hasGeneralInts, From 0f2ef94b8ed8b456073ce2b5e2a42925874434ca Mon Sep 17 00:00:00 2001 From: fwesselm Date: Tue, 10 Dec 2024 08:42:42 +0100 Subject: [PATCH 55/59] Added an assertion --- src/mip/HighsCutGeneration.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mip/HighsCutGeneration.cpp b/src/mip/HighsCutGeneration.cpp index 14cdb034c8..be210324a5 100644 --- a/src/mip/HighsCutGeneration.cpp +++ b/src/mip/HighsCutGeneration.cpp @@ -1327,6 +1327,9 @@ bool HighsCutGeneration::finalizeAndAddCut(std::vector& inds_, } void HighsCutGeneration::flipComplementation(HighsInt index) { + // only variables with finite upper bounds can be complemented + assert(upper[index] != kHighsInf); + // flip complementation complementation[index] = 1 - complementation[index]; solval[index] = upper[index] - solval[index]; From fcce0ca3f0a02dc255a217c2d4779128b0688124 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 11 Dec 2024 09:55:02 +0100 Subject: [PATCH 56/59] Fix include file --- src/util/HighsMatrixSlice.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/util/HighsMatrixSlice.h b/src/util/HighsMatrixSlice.h index 3da09ce402..a96f826ec8 100644 --- a/src/util/HighsMatrixSlice.h +++ b/src/util/HighsMatrixSlice.h @@ -13,15 +13,15 @@ * underlying matrix storage formats */ +#ifndef UTIL_HIGHS_MATRIX_SLICE_H_ +#define UTIL_HIGHS_MATRIX_SLICE_H_ + #include #include #include #include "util/HighsInt.h" -#ifndef UTIL_HIGHS_MATRIX_SLICE_H_ -#define UTIL_HIGHS_MATRIX_SLICE_H_ - template class HighsMatrixSlice; From f798574b9f2c3b488ccb305c215de98dcfd619c0 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Thu, 12 Dec 2024 16:27:03 +0100 Subject: [PATCH 57/59] Simplify two loops --- src/simplex/HEkkDualRow.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/simplex/HEkkDualRow.cpp b/src/simplex/HEkkDualRow.cpp index 6f0e00bc6c..9a455b4b7a 100644 --- a/src/simplex/HEkkDualRow.cpp +++ b/src/simplex/HEkkDualRow.cpp @@ -587,9 +587,7 @@ void HEkkDualRow::createFreemove(HVector* row_ep) { : ekk_instance_.info_.update_count < 20 ? 3e-8 : 1e-6; HighsInt move_out = workDelta < 0 ? -1 : 1; - set::iterator sit; - for (sit = freeList.begin(); sit != freeList.end(); sit++) { - HighsInt iVar = *sit; + for (HighsInt iVar : freeList) { assert(iVar < ekk_instance_.lp_.num_col_ + ekk_instance_.lp_.num_row_); double alpha = ekk_instance_.lp_.a_matrix_.computeDot(*row_ep, iVar); if (fabs(alpha) > Ta) { @@ -603,9 +601,7 @@ void HEkkDualRow::createFreemove(HVector* row_ep) { } void HEkkDualRow::deleteFreemove() { if (!freeList.empty()) { - set::iterator sit; - for (sit = freeList.begin(); sit != freeList.end(); sit++) { - HighsInt iVar = *sit; + for (HighsInt iVar : freeList) { assert(iVar < ekk_instance_.lp_.num_col_ + ekk_instance_.lp_.num_row_); ekk_instance_.basis_.nonbasicMove_[iVar] = 0; } From 66c256c26c728c6422611671ee446772f073acda Mon Sep 17 00:00:00 2001 From: fwesselm Date: Thu, 12 Dec 2024 16:40:17 +0100 Subject: [PATCH 58/59] Minor change --- src/presolve/HPresolve.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/presolve/HPresolve.cpp b/src/presolve/HPresolve.cpp index 512870b484..281d7fa7dd 100644 --- a/src/presolve/HPresolve.cpp +++ b/src/presolve/HPresolve.cpp @@ -2050,8 +2050,7 @@ void HPresolve::storeRow(HighsInt row) { rowpositions.clear(); auto rowVec = getSortedRowVector(row); - auto rowVecEnd = rowVec.end(); - for (auto iter = rowVec.begin(); iter != rowVecEnd; ++iter) + for (auto iter = rowVec.begin(); iter != rowVec.end(); ++iter) rowpositions.push_back(iter.position()); } From 5b295cc9a6c465fbba728a460c4a6a367fac665d Mon Sep 17 00:00:00 2001 From: fwesselm Date: Fri, 13 Dec 2024 10:00:08 +0100 Subject: [PATCH 59/59] Use const reference --- src/simplex/HEkkDualRow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/simplex/HEkkDualRow.cpp b/src/simplex/HEkkDualRow.cpp index 9a455b4b7a..9dfcd41b2b 100644 --- a/src/simplex/HEkkDualRow.cpp +++ b/src/simplex/HEkkDualRow.cpp @@ -587,7 +587,7 @@ void HEkkDualRow::createFreemove(HVector* row_ep) { : ekk_instance_.info_.update_count < 20 ? 3e-8 : 1e-6; HighsInt move_out = workDelta < 0 ? -1 : 1; - for (HighsInt iVar : freeList) { + for (const HighsInt& iVar : freeList) { assert(iVar < ekk_instance_.lp_.num_col_ + ekk_instance_.lp_.num_row_); double alpha = ekk_instance_.lp_.a_matrix_.computeDot(*row_ep, iVar); if (fabs(alpha) > Ta) { @@ -601,7 +601,7 @@ void HEkkDualRow::createFreemove(HVector* row_ep) { } void HEkkDualRow::deleteFreemove() { if (!freeList.empty()) { - for (HighsInt iVar : freeList) { + for (const HighsInt& iVar : freeList) { assert(iVar < ekk_instance_.lp_.num_col_ + ekk_instance_.lp_.num_row_); ekk_instance_.basis_.nonbasicMove_[iVar] = 0; }