From c1b85ec74d80b673b22d729122445e5eb08bdbe3 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 24 Apr 2024 08:57:27 -0600 Subject: [PATCH 001/194] highspy: release python GIL when calling run --- highspy/highs_bindings.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/highspy/highs_bindings.cpp b/highspy/highs_bindings.cpp index 5c64109adb..1f4e9c0423 100644 --- a/highspy/highs_bindings.cpp +++ b/highspy/highs_bindings.cpp @@ -551,6 +551,15 @@ std::tuple highs_getRowByName(Highs* h, } +HighsStatus highs_run(Highs* h) +{ + py::gil_scoped_release release; + HighsStatus status = h->run(); + py::gil_scoped_acquire(); + return status; +} + + PYBIND11_MODULE(highspy, m) { // enum classes @@ -835,7 +844,7 @@ PYBIND11_MODULE(highspy, m) { .def("writeBasis", &Highs::writeBasis) .def("postsolve", &highs_postsolve) .def("postsolve", &highs_mipPostsolve) - .def("run", &Highs::run) + .def("run", &highs_run) .def("presolve", &Highs::presolve) .def("writeSolution", &highs_writeSolution) .def("readSolution", &Highs::readSolution) From 73d5b1cdd170dfac183d7b1b25d8bedb5a8d2541 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 3 Jun 2024 16:49:42 +0100 Subject: [PATCH 002/194] Introduced Highs::getIis, HighsIis.h and HighsIis.cpp --- check/CMakeLists.txt | 3 ++- cmake/sources-python.cmake | 2 ++ cmake/sources.cmake | 2 ++ src/Highs.h | 13 +++++++++++++ src/lp_data/HStruct.h | 8 ++++++++ src/lp_data/Highs.cpp | 14 ++++++++++++++ src/lp_data/HighsInterface.cpp | 23 +++++++++++++++++++++++ src/meson.build | 1 + 8 files changed, 65 insertions(+), 1 deletion(-) diff --git a/check/CMakeLists.txt b/check/CMakeLists.txt index 0367e593be..f258f4c514 100644 --- a/check/CMakeLists.txt +++ b/check/CMakeLists.txt @@ -57,6 +57,7 @@ if (NOT FAST_BUILD OR ALL_TESTS) TestHighsSparseMatrix.cpp TestHSet.cpp TestICrash.cpp + TestIis.cpp TestIpm.cpp TestIpx.cpp TestLogging.cpp @@ -321,4 +322,4 @@ if (NOT FAST_BUILD OR ALL_TESTS) endforeach(setting) endforeach() -endif() \ No newline at end of file +endif() diff --git a/cmake/sources-python.cmake b/cmake/sources-python.cmake index baff378e61..36c402ae19 100644 --- a/cmake/sources-python.cmake +++ b/cmake/sources-python.cmake @@ -184,6 +184,7 @@ set(highs_sources_python src/lp_data/Highs.cpp src/lp_data/HighsCallback.cpp src/lp_data/HighsDebug.cpp + src/lp_data/HighsIis.cpp src/lp_data/HighsInfo.cpp src/lp_data/HighsInfoDebug.cpp src/lp_data/HighsInterface.cpp @@ -298,6 +299,7 @@ set(highs_headers_python src/lp_data/HighsCallback.h src/lp_data/HighsCallbackStruct.h src/lp_data/HighsDebug.h + src/lp_data/HighsIis.h src/lp_data/HighsInfo.h src/lp_data/HighsInfoDebug.h src/lp_data/HighsLp.h diff --git a/cmake/sources.cmake b/cmake/sources.cmake index 6c7e4647df..29dce19f0d 100644 --- a/cmake/sources.cmake +++ b/cmake/sources.cmake @@ -184,6 +184,7 @@ set(highs_sources lp_data/Highs.cpp lp_data/HighsCallback.cpp lp_data/HighsDebug.cpp + lp_data/HighsIis.cpp lp_data/HighsInfo.cpp lp_data/HighsInfoDebug.cpp lp_data/HighsDeprecated.cpp @@ -302,6 +303,7 @@ set(highs_headers lp_data/HighsCallback.h lp_data/HighsCallbackStruct.h lp_data/HighsDebug.h + lp_data/HighsIis.h lp_data/HighsInfo.h lp_data/HighsInfoDebug.h lp_data/HighsLp.h diff --git a/src/Highs.h b/src/Highs.h index 94d6193cf3..1455d4152d 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -507,6 +507,11 @@ class Highs { const HighsInt method = 0, const double ill_conditioning_bound = 1e-4); + HighsStatus getIis(HighsInt& num_iis_col, HighsInt& num_iis_row, + HighsInt* iis_col_index = nullptr, + HighsInt* iis_row_index = nullptr, + HighsInt* iis_col_bound = nullptr, + HighsInt* iis_row_bound = nullptr); /** * @brief Get the current model objective value */ @@ -1330,6 +1335,7 @@ class Highs { HighsOptions options_; HighsInfo info_; HighsRanging ranging_; + HighsIis iis_; std::vector saved_objective_and_solution_; @@ -1419,6 +1425,9 @@ class Highs { // Invalidates ekk_instance_ void invalidateEkk(); + // Invalidates iis_ + void invalidateIis(); + HighsStatus returnFromWriteSolution(FILE* file, const HighsStatus return_status); HighsStatus returnFromRun(const HighsStatus return_status, @@ -1501,6 +1510,10 @@ class Highs { double* primal_ray_value); HighsStatus getRangingInterface(); + HighsStatus getIisInterface(HighsInt& num_iis_col, HighsInt& num_iis_row, + HighsInt* iis_col_index, HighsInt* iis_row_index, + HighsInt* iis_col_bound, HighsInt* iis_row_bound); + 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/HStruct.h b/src/lp_data/HStruct.h index 863020bfde..ba61edafff 100644 --- a/src/lp_data/HStruct.h +++ b/src/lp_data/HStruct.h @@ -143,4 +143,12 @@ struct HighsIllConditioning { void clear(); }; +struct HighsIis { + std::vector col_index; + std::vector row_index; + std::vector col_bound; + std::vector row_bound; + void clear(); +}; + #endif /* LP_DATA_HSTRUCT_H_ */ diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index a2f6cd517d..dd43f767ec 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -1679,6 +1679,15 @@ HighsStatus Highs::getIllConditioning(HighsIllConditioning& ill_conditioning, ill_conditioning_bound); } +HighsStatus Highs::getIis(HighsInt& num_iis_col, HighsInt& num_iis_row, + HighsInt* iis_col_index, HighsInt* iis_row_index, + HighsInt* iis_col_bound, HighsInt* iis_row_bound) { + if (!ekk_instance_.status_.has_invert) + return lpInvertRequirementError("getIss"); + return getIisInterface(num_iis_col, num_iis_row, iis_col_index, iis_row_index, + iis_col_bound, iis_row_bound); +} + bool Highs::hasInvert() const { return ekk_instance_.status_.has_invert; } const HighsInt* Highs::getBasicVariablesArray() const { @@ -3311,12 +3320,15 @@ void Highs::invalidateUserSolverData() { invalidateRanging(); invalidateInfo(); invalidateEkk(); + invalidateIis(); } void Highs::invalidateModelStatusSolutionAndInfo() { invalidateModelStatus(); invalidateSolution(); + invalidateRanging(); invalidateInfo(); + invalidateIis(); } void Highs::invalidateModelStatus() { @@ -3346,6 +3358,8 @@ void Highs::invalidateRanging() { ranging_.invalidate(); } void Highs::invalidateEkk() { ekk_instance_.invalidate(); } +void Highs::invalidateIis() { iis_.clear(); } + HighsStatus Highs::completeSolutionFromDiscreteAssignment() { // Determine whether the current solution of a MIP is feasible and, // if not, try to assign values to continuous variables to achieve a diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 7f7a201f99..84f5be361e 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -1549,6 +1549,22 @@ HighsStatus Highs::getRangingInterface() { return getRangingData(this->ranging_, solver_object); } +HighsStatus Highs::getIisInterface(HighsInt& num_iis_col, HighsInt& num_iis_row, + HighsInt* iis_col_index, + HighsInt* iis_row_index, + HighsInt* iis_col_bound, + HighsInt* iis_row_bound) { + HighsStatus return_status = HighsStatus::kOk; + HighsLp& lp = model_.lp_; + HighsInt num_row = lp.num_row_; + // For an LP with no rows the dual ray is vacuous + if (num_row == 0) return return_status; + assert(ekk_instance_.status_.has_invert); + assert(!lp.is_moved_); + return_status = HighsStatus::kError; + return return_status; +} + bool Highs::aFormatOk(const HighsInt num_nz, const HighsInt format) { if (!num_nz) return true; const bool ok_format = format == (HighsInt)MatrixFormat::kColwise || @@ -2527,3 +2543,10 @@ bool Highs::infeasibleBoundsOk() { int(num_true_infeasible_bound)); return num_true_infeasible_bound == 0; } + +void HighsIis::clear() { + this->col_index.clear(); + this->row_index.clear(); + this->col_bound.clear(); + this->row_bound.clear(); +} diff --git a/src/meson.build b/src/meson.build index 61dd94f9e7..56b83517ea 100644 --- a/src/meson.build +++ b/src/meson.build @@ -205,6 +205,7 @@ _srcs = [ 'lp_data/Highs.cpp', 'lp_data/HighsCallback.cpp', 'lp_data/HighsDebug.cpp', + 'lp_data/HighsIis.cpp', 'lp_data/HighsInfo.cpp', 'lp_data/HighsInfoDebug.cpp', 'lp_data/HighsDeprecated.cpp', From cb378e50e53fdc758274cde4d403a23dc1608ede Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 3 Jun 2024 21:03:21 +0100 Subject: [PATCH 003/194] dual_ray_value reaches getIisData --- src/Highs.h | 1 + src/lp_data/HStruct.h | 8 ---- src/lp_data/Highs.cpp | 9 +++-- src/lp_data/HighsInterface.cpp | 71 ++++++++++++++++++++++++++-------- 4 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/Highs.h b/src/Highs.h index 1455d4152d..dfc1e38bbd 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -17,6 +17,7 @@ #include #include "lp_data/HighsCallback.h" +#include "lp_data/HighsIis.h" #include "lp_data/HighsLpUtils.h" #include "lp_data/HighsRanging.h" #include "lp_data/HighsSolutionDebug.h" diff --git a/src/lp_data/HStruct.h b/src/lp_data/HStruct.h index ba61edafff..863020bfde 100644 --- a/src/lp_data/HStruct.h +++ b/src/lp_data/HStruct.h @@ -143,12 +143,4 @@ struct HighsIllConditioning { void clear(); }; -struct HighsIis { - std::vector col_index; - std::vector row_index; - std::vector col_bound; - std::vector row_bound; - void clear(); -}; - #endif /* LP_DATA_HSTRUCT_H_ */ diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index dd43f767ec..e8c9d9dad7 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -1682,8 +1682,11 @@ HighsStatus Highs::getIllConditioning(HighsIllConditioning& ill_conditioning, HighsStatus Highs::getIis(HighsInt& num_iis_col, HighsInt& num_iis_row, HighsInt* iis_col_index, HighsInt* iis_row_index, HighsInt* iis_col_bound, HighsInt* iis_row_bound) { - if (!ekk_instance_.status_.has_invert) - return lpInvertRequirementError("getIss"); + if (model_status_ != HighsModelStatus::kInfeasible) { + highsLogUser(options_.log_options, HighsLogType::kError, + "getIis: model status is not infeasible\n"); + return HighsStatus::kError; + } return getIisInterface(num_iis_col, num_iis_row, iis_col_index, iis_row_index, iis_col_bound, iis_row_bound); } @@ -3358,7 +3361,7 @@ void Highs::invalidateRanging() { ranging_.invalidate(); } void Highs::invalidateEkk() { ekk_instance_.invalidate(); } -void Highs::invalidateIis() { iis_.clear(); } +void Highs::invalidateIis() { iis_.invalidate(); } HighsStatus Highs::completeSolutionFromDiscreteAssignment() { // Determine whether the current solution of a MIP is feasible and, diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 84f5be361e..4bd662e3d8 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -1554,15 +1554,61 @@ HighsStatus Highs::getIisInterface(HighsInt& num_iis_col, HighsInt& num_iis_row, HighsInt* iis_row_index, HighsInt* iis_col_bound, HighsInt* iis_row_bound) { - HighsStatus return_status = HighsStatus::kOk; - HighsLp& lp = model_.lp_; - HighsInt num_row = lp.num_row_; - // For an LP with no rows the dual ray is vacuous - if (num_row == 0) return return_status; - assert(ekk_instance_.status_.has_invert); - assert(!lp.is_moved_); - return_status = HighsStatus::kError; - return return_status; + if (!this->iis_.valid) { + HighsLp& lp = model_.lp_; + HighsInt num_row = lp.num_row_; + // For an LP with no rows the dual ray is vacuous + if (num_row == 0) return HighsStatus::kOk; + assert(model_status_ == HighsModelStatus::kInfeasible); + if (!ekk_instance_.status_.has_invert) { + // No INVERT - presumably because infeasibility detected in + // presolve + std::string presolve = options_.presolve; + printf("Highs::getIisInterface options_.presolve = %s; kHighsOnString = %s\n", + options_.presolve.c_str(), + kHighsOnString.c_str()); + options_.presolve = kHighsOffString; + HighsStatus return_status = this->run(); + options_.presolve = presolve; + if (return_status != HighsStatus::kOk) return return_status; + } + assert(ekk_instance_.status_.has_invert); + assert(!lp.is_moved_); + const bool has_dual_ray = ekk_instance_.status_.has_dual_ray; + std::vector dual_ray_value; + if (has_dual_ray) { + std::vector rhs; + HighsInt iRow = ekk_instance_.info_.dual_ray_row_; + rhs.assign(num_row, 0); + rhs[iRow] = ekk_instance_.info_.dual_ray_sign_; + dual_ray_value.resize(num_row); + HighsInt* dual_ray_num_nz = 0; + basisSolveInterface(rhs, dual_ray_value.data(), dual_ray_num_nz, NULL, true); + } else { + highsLogUser(options_.log_options, HighsLogType::kError, + "No dual ray to start IIS calculation\n"); + return HighsStatus::kError; + } + HighsStatus return_status = getIisData(lp, dual_ray_value, this->iis_); + if (return_status != HighsStatus::kOk) return return_status; + } + assert(this->iis_.valid); + + num_iis_col = this->iis_.col_index.size(); + num_iis_row = this->iis_.row_index.size(); + if (iis_col_index || iis_col_bound) { + for (HighsInt iCol = 0; iCol < num_iis_col; iCol++) { + if (iis_col_index) iis_col_index[iCol] = this->iis_.col_index[iCol]; + if (iis_col_bound) iis_col_bound[iCol] = this->iis_.col_bound[iCol]; + } + } + if (iis_row_index || iis_row_bound) { + for (HighsInt iRow = 0; iRow < num_iis_row; iRow++) { + if (iis_row_index) iis_row_index[iRow] = this->iis_.row_index[iRow]; + if (iis_row_bound) iis_row_bound[iRow] = this->iis_.row_bound[iRow]; + } + } + return HighsStatus::kOk; } bool Highs::aFormatOk(const HighsInt num_nz, const HighsInt format) { @@ -2543,10 +2589,3 @@ bool Highs::infeasibleBoundsOk() { int(num_true_infeasible_bound)); return num_true_infeasible_bound == 0; } - -void HighsIis::clear() { - this->col_index.clear(); - this->row_index.clear(); - this->col_bound.clear(); - this->row_bound.clear(); -} From d18472a00b6084e4506dcda1c8d2200f09a7c813 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 3 Jun 2024 23:06:37 +0100 Subject: [PATCH 004/194] Now extracting and solving the LP corresponding to the dual ray --- src/lp_data/HighsIis.cpp | 87 ++++++++++++++++++++++++++++++++++ src/lp_data/HighsIis.h | 32 +++++++++++++ src/lp_data/HighsInterface.cpp | 12 +++-- 3 files changed, 126 insertions(+), 5 deletions(-) create mode 100644 src/lp_data/HighsIis.cpp create mode 100644 src/lp_data/HighsIis.h diff --git a/src/lp_data/HighsIis.cpp b/src/lp_data/HighsIis.cpp new file mode 100644 index 0000000000..6881146555 --- /dev/null +++ b/src/lp_data/HighsIis.cpp @@ -0,0 +1,87 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/* */ +/* This file is part of the HiGHS linear optimization suite */ +/* */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ +/* */ +/* Available as open-source under the MIT License */ +/* */ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/**@file lp_data/HighsIis.cpp + * @brief Class-independent utilities for HiGHS + */ + +#include "lp_data/HighsIis.h" + +#include "Highs.h" + +void HighsIis::invalidate() { + this->valid = false; + this->col_index.clear(); + this->row_index.clear(); + this->col_bound.clear(); + this->row_bound.clear(); +} + +HighsStatus getIisData(const HighsLp& lp, + const std::vector& dual_ray_value, + HighsIis& iis) { + std::vector from_row; + std::vector from_col; + std::vector to_row; + to_row.assign(lp.num_row_, -1); + assert(lp.a_matrix_.isColwise()); + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { + if (dual_ray_value[iRow]) { + to_row[iRow] = from_row.size(); + from_row.push_back(iRow); + } + printf("getIisData: dual_ray_value[%2d] = %g; to_row[%2d] = %d\n", + int(iRow), dual_ray_value[iRow], int(iRow), to_row[iRow]); + } + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + bool use_col = false; + for (HighsInt iEl = lp.a_matrix_.start_[iCol]; + iEl < lp.a_matrix_.start_[iCol + 1]; iEl++) + use_col = use_col || to_row[lp.a_matrix_.index_[iEl]] >= 0; + if (use_col) from_col.push_back(iCol); + } + HighsLp to_lp; + HighsInt to_num_col = from_col.size(); + HighsInt to_num_row = from_row.size(); + to_lp.num_col_ = to_num_col; + to_lp.num_row_ = to_num_row; + for (HighsInt iCol = 0; iCol < to_num_col; iCol++) { + printf("getIisData: from_col[%2d] = %d\n", int(iCol), int(from_col[iCol])); + to_lp.col_cost_.push_back(0); + to_lp.col_lower_.push_back(lp.col_lower_[from_col[iCol]]); + to_lp.col_upper_.push_back(lp.col_upper_[from_col[iCol]]); + for (HighsInt iEl = lp.a_matrix_.start_[from_col[iCol]]; + iEl < lp.a_matrix_.start_[from_col[iCol] + 1]; iEl++) { + HighsInt iRow = lp.a_matrix_.index_[iEl]; + if (to_row[iRow] >= 0) { + to_lp.a_matrix_.index_.push_back(to_row[iRow]); + to_lp.a_matrix_.value_.push_back(lp.a_matrix_.value_[iEl]); + } + } + to_lp.a_matrix_.start_.push_back(to_lp.a_matrix_.index_.size()); + } + for (HighsInt iRow = 0; iRow < to_num_row; iRow++) { + printf("getIisData: from_row[%2d] = %d\n", int(iRow), int(from_row[iRow])); + to_lp.row_lower_.push_back(lp.row_lower_[from_row[iRow]]); + to_lp.row_upper_.push_back(lp.row_upper_[from_row[iRow]]); + } + Highs highs; + highs.setOptionValue("presolve", kHighsOffString); + HighsStatus status = highs.passModel(to_lp); + assert(status == HighsStatus::kOk); + status = highs.run(); + if (status != HighsStatus::kOk) return status; + assert(highs.getModelStatus() == HighsModelStatus::kInfeasible); + iis.valid = true; + iis.col_index = from_col; + iis.row_index = from_row; + + return HighsStatus::kOk; +} diff --git a/src/lp_data/HighsIis.h b/src/lp_data/HighsIis.h new file mode 100644 index 0000000000..c01aabbeb9 --- /dev/null +++ b/src/lp_data/HighsIis.h @@ -0,0 +1,32 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/* */ +/* This file is part of the HiGHS linear optimization suite */ +/* */ +/* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, */ +/* Leona Gottwald and Michael Feldmeier */ +/* */ +/* Available as open-source under the MIT License */ +/* */ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/**@file lp_data/HighsIis.h + * @brief Class-independent utilities for HiGHS + */ +#ifndef LP_DATA_HIGHSIIS_H_ +#define LP_DATA_HIGHSIIS_H_ + +#include "lp_data/HighsLp.h" + +struct HighsIis { + bool valid = false; + std::vector col_index; + std::vector row_index; + std::vector col_bound; + std::vector row_bound; + void invalidate(); +}; + +HighsStatus getIisData(const HighsLp& lp, + const std::vector& dual_ray_value, + HighsIis& iis); + +#endif // LP_DATA_HIGHSIIS_H_ diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 4bd662e3d8..f4a1ee5dad 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -1564,9 +1564,10 @@ HighsStatus Highs::getIisInterface(HighsInt& num_iis_col, HighsInt& num_iis_row, // No INVERT - presumably because infeasibility detected in // presolve std::string presolve = options_.presolve; - printf("Highs::getIisInterface options_.presolve = %s; kHighsOnString = %s\n", - options_.presolve.c_str(), - kHighsOnString.c_str()); + printf( + "Highs::getIisInterface options_.presolve = %s; kHighsOnString = " + "%s\n", + options_.presolve.c_str(), kHighsOnString.c_str()); options_.presolve = kHighsOffString; HighsStatus return_status = this->run(); options_.presolve = presolve; @@ -1583,10 +1584,11 @@ HighsStatus Highs::getIisInterface(HighsInt& num_iis_col, HighsInt& num_iis_row, rhs[iRow] = ekk_instance_.info_.dual_ray_sign_; dual_ray_value.resize(num_row); HighsInt* dual_ray_num_nz = 0; - basisSolveInterface(rhs, dual_ray_value.data(), dual_ray_num_nz, NULL, true); + basisSolveInterface(rhs, dual_ray_value.data(), dual_ray_num_nz, NULL, + true); } else { highsLogUser(options_.log_options, HighsLogType::kError, - "No dual ray to start IIS calculation\n"); + "No dual ray to start IIS calculation\n"); return HighsStatus::kError; } HighsStatus return_status = getIisData(lp, dual_ray_value, this->iis_); From 393ff27ea14b56ff72e86973972f88439ba405fa Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 3 Jun 2024 23:08:52 +0100 Subject: [PATCH 005/194] Added check/TestIis.cpp --- check/TestIis.cpp | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 check/TestIis.cpp diff --git a/check/TestIis.cpp b/check/TestIis.cpp new file mode 100644 index 0000000000..0aa02e76aa --- /dev/null +++ b/check/TestIis.cpp @@ -0,0 +1,36 @@ +#include +#include + +#include "HCheckConfig.h" +#include "Highs.h" +#include "catch.hpp" +//#include "io/FilereaderLp.h" + +const bool dev_run = false; +const double inf = kHighsInf; + +TEST_CASE("lp-get-iis", "[iis]") { + std::string model = "galenet"; + std::string model_file = + std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; + Highs highs; + // highs.setOptionValue("output_flag", dev_run); + + HighsInt num_iis_col; + HighsInt num_iis_row; + + REQUIRE(highs.readModel(model_file) == HighsStatus::kOk); + REQUIRE(highs.run() == HighsStatus::kOk); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); + REQUIRE(highs.getIis(num_iis_col, num_iis_row) == HighsStatus::kOk); + REQUIRE(num_iis_col > 0); + REQUIRE(num_iis_row > 0); + std::vector iis_col_index(num_iis_col); + std::vector iis_row_index(num_iis_row); + std::vector iis_col_bound(num_iis_col); + std::vector iis_row_bound(num_iis_row); + REQUIRE(highs.getIis(num_iis_col, num_iis_row, iis_col_index.data(), + iis_row_index.data(), + // iis_col_bound.data(), iis_row_bound.data() + nullptr, nullptr) == HighsStatus::kOk); +} From 72e245a2277c63e7df8376c3d5b95bcb95252866 Mon Sep 17 00:00:00 2001 From: jajhall Date: Tue, 4 Jun 2024 17:52:17 +0100 Subject: [PATCH 006/194] Inconsistent bounds ISS unit test passes --- check/TestIis.cpp | 56 +++++++++++++++++ src/Highs.h | 7 ++- src/lp_data/HConst.h | 9 +++ src/lp_data/Highs.cpp | 6 +- src/lp_data/HighsIis.cpp | 65 ++++++++++++++++++- src/lp_data/HighsIis.h | 5 +- src/lp_data/HighsInterface.cpp | 111 +++++++++++++++++++-------------- src/lp_data/HighsOptions.h | 15 +++++ 8 files changed, 221 insertions(+), 53 deletions(-) diff --git a/check/TestIis.cpp b/check/TestIis.cpp index 0aa02e76aa..055acd8f3d 100644 --- a/check/TestIis.cpp +++ b/check/TestIis.cpp @@ -9,6 +9,62 @@ const bool dev_run = false; const double inf = kHighsInf; +TEST_CASE("lp-incompatible-bounds", "[iis]") { + HighsLp lp; + lp.num_col_ = 3; + lp.num_row_ = 2; + lp.col_cost_ = {0, 0, 0}; + lp.col_lower_ = {0, 0, 0}; + lp.col_upper_ = {1, 1, -1}; + lp.row_lower_ = {1, 0}; + lp.row_upper_ = {0, 1}; + lp.a_matrix_.format_ = MatrixFormat::kRowwise; + lp.a_matrix_.start_ = {0, 2, 4}; + lp.a_matrix_.index_ = {1, 2, 0, 2}; + lp.a_matrix_.value_ = {1, 1, 1, 1}; + Highs highs; + // highs.setOptionValue("output_flag", dev_run); + highs.passModel(lp); + REQUIRE(highs.run() == HighsStatus::kOk); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); + HighsInt num_iis_col; + HighsInt num_iis_row; + std::vector iis_col_index; + std::vector iis_row_index; + std::vector iis_col_bound; + std::vector iis_row_bound; + highs.setOptionValue("iis_strategy", kIisStrategyFromLpRowPriority); + REQUIRE(highs.getIis(num_iis_col, num_iis_row) == HighsStatus::kOk); + REQUIRE(num_iis_col == 2); + REQUIRE(num_iis_row == 1); + iis_col_index.resize(num_iis_col); + iis_row_index.resize(num_iis_row); + iis_col_bound.resize(num_iis_col); + iis_row_bound.resize(num_iis_row); + REQUIRE(highs.getIis(num_iis_col, num_iis_row, iis_col_index.data(), + iis_row_index.data(), + // iis_col_bound.data(), iis_row_bound.data() + nullptr, nullptr) == HighsStatus::kOk); + REQUIRE(iis_col_index[0] == 1); + REQUIRE(iis_col_index[1] == 2); + REQUIRE(iis_row_index[0] == 0); + highs.setOptionValue("iis_strategy", kIisStrategyFromLpColPriority); + REQUIRE(highs.getIis(num_iis_col, num_iis_row) == HighsStatus::kOk); + REQUIRE(num_iis_col == 1); + REQUIRE(num_iis_row == 2); + iis_col_index.resize(num_iis_col); + iis_row_index.resize(num_iis_row); + iis_col_bound.resize(num_iis_col); + iis_row_bound.resize(num_iis_row); + REQUIRE(highs.getIis(num_iis_col, num_iis_row, iis_col_index.data(), + iis_row_index.data(), + // iis_col_bound.data(), iis_row_bound.data() + nullptr, nullptr) == HighsStatus::kOk); + REQUIRE(iis_col_index[0] == 2); + REQUIRE(iis_row_index[0] == 0); + REQUIRE(iis_row_index[1] == 1); +} + TEST_CASE("lp-get-iis", "[iis]") { std::string model = "galenet"; std::string model_file = diff --git a/src/Highs.h b/src/Highs.h index dfc1e38bbd..9a97dba925 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -1511,9 +1511,10 @@ class Highs { double* primal_ray_value); HighsStatus getRangingInterface(); - HighsStatus getIisInterface(HighsInt& num_iis_col, HighsInt& num_iis_row, - HighsInt* iis_col_index, HighsInt* iis_row_index, - HighsInt* iis_col_bound, HighsInt* iis_row_bound); + HighsStatus getIisInterface(); + HighsStatus extractIisData(HighsInt& num_iis_col, HighsInt& num_iis_row, + HighsInt* iis_col_index, HighsInt* iis_row_index, + HighsInt* iis_col_bound, HighsInt* iis_row_bound); bool aFormatOk(const HighsInt num_nz, const HighsInt format); bool qFormatOk(const HighsInt num_nz, const HighsInt format); diff --git a/src/lp_data/HConst.h b/src/lp_data/HConst.h index e9359556e2..edba5ccf85 100644 --- a/src/lp_data/HConst.h +++ b/src/lp_data/HConst.h @@ -264,6 +264,15 @@ enum PresolveRuleType : int { kPresolveRuleCount, }; +enum IisStrategy { + kIisStrategyMin = 0, + kIisStrategyFromRayRowPriority = kIisStrategyMin, // 0 + kIisStrategyFromRayColPriority, // 1 + kIisStrategyFromLpRowPriority, // 2 + kIisStrategyFromLpColPriority, // 3 + kIisStrategyMax = kIisStrategyFromLpColPriority +}; + // Default and max allowed power-of-two matrix scale factor const HighsInt kDefaultAllowedMatrixPow2Scale = 20; const HighsInt kMaxAllowedMatrixPow2Scale = 30; diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index e8c9d9dad7..3d54c48f8f 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -1687,8 +1687,10 @@ HighsStatus Highs::getIis(HighsInt& num_iis_col, HighsInt& num_iis_row, "getIis: model status is not infeasible\n"); return HighsStatus::kError; } - return getIisInterface(num_iis_col, num_iis_row, iis_col_index, iis_row_index, - iis_col_bound, iis_row_bound); + HighsStatus return_status = this->getIisInterface(); + if (return_status != HighsStatus::kOk) return return_status; + return this->extractIisData(num_iis_col, num_iis_row, iis_col_index, + iis_row_index, iis_col_bound, iis_row_bound); } bool Highs::hasInvert() const { return ekk_instance_.status_.has_invert; } diff --git a/src/lp_data/HighsIis.cpp b/src/lp_data/HighsIis.cpp index 6881146555..1ddd9d6735 100644 --- a/src/lp_data/HighsIis.cpp +++ b/src/lp_data/HighsIis.cpp @@ -18,15 +18,77 @@ void HighsIis::invalidate() { this->valid = false; + this->strategy = kIisStrategyMin; this->col_index.clear(); this->row_index.clear(); this->col_bound.clear(); this->row_bound.clear(); } -HighsStatus getIisData(const HighsLp& lp, +bool iisInconsistentBounds(const HighsLp& lp, const HighsOptions& options, + HighsIis& iis) { + iis.invalidate(); + const bool col_priority = + options.iis_strategy == kIisStrategyFromRayColPriority || + options.iis_strategy == kIisStrategyFromLpColPriority; + for (HighsInt k = 0; k < 2; k++) { + if ((col_priority && k == 0) || (!col_priority && k == 1)) { + // Loop over columns first + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + if (lp.col_lower_[iCol] - lp.col_upper_[iCol] > + 2 * options.primal_feasibility_tolerance) { + iis.col_index.push_back(iCol); + break; + } + } + if (iis.col_index.size() > 0) break; + } else { + // Loop over rows first + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { + if (lp.row_lower_[iRow] - lp.row_upper_[iRow] > + 2 * options.primal_feasibility_tolerance) { + iis.row_index.push_back(iRow); + break; + } + } + if (iis.row_index.size() > 0) break; + } + } + HighsInt num_iis_col = iis.col_index.size(); + HighsInt num_iis_row = iis.row_index.size(); + // If none found then return false + if (num_iis_col + num_iis_row == 0) return false; + // Should have found exactly 1 + assert((num_iis_col == 1 || num_iis_row == 1) && + num_iis_col + num_iis_row < 2); + assert(lp.a_matrix_.isColwise()); + if (num_iis_col > 0) { + // Found inconsistent column + HighsInt iCol = iis.col_index[0]; + for (HighsInt iEl = lp.a_matrix_.start_[iCol]; + iEl < lp.a_matrix_.start_[iCol + 1]; iEl++) + iis.row_index.push_back(lp.a_matrix_.index_[iEl]); + + } else { + // Found inconsistent row + HighsInt iRow = iis.row_index[0]; + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + for (HighsInt iEl = lp.a_matrix_.start_[iCol]; + iEl < lp.a_matrix_.start_[iCol + 1]; iEl++) + if (lp.a_matrix_.index_[iEl] == iRow) iis.col_index.push_back(iCol); + } + } + iis.valid = true; + iis.strategy = options.iis_strategy; + return true; +} + +HighsStatus getIisData(const HighsLp& lp, const HighsOptions& options, const std::vector& dual_ray_value, HighsIis& iis) { + // Check for inconsistent column and row bounds should have been + // done earlier + assert(!iisInconsistentBounds(lp, options, iis)); std::vector from_row; std::vector from_col; std::vector to_row; @@ -80,6 +142,7 @@ HighsStatus getIisData(const HighsLp& lp, if (status != HighsStatus::kOk) return status; assert(highs.getModelStatus() == HighsModelStatus::kInfeasible); iis.valid = true; + iis.strategy = options.iis_strategy; iis.col_index = from_col; iis.row_index = from_row; diff --git a/src/lp_data/HighsIis.h b/src/lp_data/HighsIis.h index c01aabbeb9..e8ba9a7039 100644 --- a/src/lp_data/HighsIis.h +++ b/src/lp_data/HighsIis.h @@ -18,6 +18,7 @@ struct HighsIis { bool valid = false; + HighsInt strategy = kIisStrategyMin; std::vector col_index; std::vector row_index; std::vector col_bound; @@ -25,8 +26,10 @@ struct HighsIis { void invalidate(); }; -HighsStatus getIisData(const HighsLp& lp, +HighsStatus getIisData(const HighsLp& lp, const HighsOptions& options, const std::vector& dual_ray_value, HighsIis& iis); +bool iisInconsistentBounds(const HighsLp& lp, const HighsOptions& options, + HighsIis& iis); #endif // LP_DATA_HIGHSIIS_H_ diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index f4a1ee5dad..494a258a11 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -1549,34 +1549,34 @@ HighsStatus Highs::getRangingInterface() { return getRangingData(this->ranging_, solver_object); } -HighsStatus Highs::getIisInterface(HighsInt& num_iis_col, HighsInt& num_iis_row, - HighsInt* iis_col_index, - HighsInt* iis_row_index, - HighsInt* iis_col_bound, - HighsInt* iis_row_bound) { - if (!this->iis_.valid) { - HighsLp& lp = model_.lp_; - HighsInt num_row = lp.num_row_; - // For an LP with no rows the dual ray is vacuous - if (num_row == 0) return HighsStatus::kOk; - assert(model_status_ == HighsModelStatus::kInfeasible); - if (!ekk_instance_.status_.has_invert) { - // No INVERT - presumably because infeasibility detected in - // presolve - std::string presolve = options_.presolve; - printf( - "Highs::getIisInterface options_.presolve = %s; kHighsOnString = " - "%s\n", - options_.presolve.c_str(), kHighsOnString.c_str()); - options_.presolve = kHighsOffString; - HighsStatus return_status = this->run(); - options_.presolve = presolve; - if (return_status != HighsStatus::kOk) return return_status; - } - assert(ekk_instance_.status_.has_invert); - assert(!lp.is_moved_); +HighsStatus Highs::getIisInterface() { + if (this->iis_.valid) return HighsStatus::kOk; + HighsLp& lp = model_.lp_; + // Check for inconsistent column and row bounds + if (iisInconsistentBounds(lp, options_, iis_)) return HighsStatus::kOk; + HighsInt num_row = lp.num_row_; + // For an LP with no rows the dual ray is vacuous + if (num_row == 0) return HighsStatus::kOk; + assert(model_status_ == HighsModelStatus::kInfeasible); + if (!ekk_instance_.status_.has_invert) { + // No INVERT - presumably because infeasibility detected in + // presolve + std::string presolve = options_.presolve; + printf( + "Highs::getIisInterface options_.presolve = %s; kHighsOnString = " + "%s\n", + options_.presolve.c_str(), kHighsOnString.c_str()); + options_.presolve = kHighsOffString; + HighsStatus return_status = this->run(); + options_.presolve = presolve; + if (return_status != HighsStatus::kOk) return return_status; + } + assert(ekk_instance_.status_.has_invert); + assert(!lp.is_moved_); + std::vector dual_ray_value; + if (options_.iis_strategy == kIisStrategyFromRayRowPriority || + options_.iis_strategy == kIisStrategyFromRayColPriority) { const bool has_dual_ray = ekk_instance_.status_.has_dual_ray; - std::vector dual_ray_value; if (has_dual_ray) { std::vector rhs; HighsInt iRow = ekk_instance_.info_.dual_ray_row_; @@ -1591,11 +1591,16 @@ HighsStatus Highs::getIisInterface(HighsInt& num_iis_col, HighsInt& num_iis_row, "No dual ray to start IIS calculation\n"); return HighsStatus::kError; } - HighsStatus return_status = getIisData(lp, dual_ray_value, this->iis_); - if (return_status != HighsStatus::kOk) return return_status; } - assert(this->iis_.valid); + return getIisData(lp, options_, dual_ray_value, this->iis_); +} +HighsStatus Highs::extractIisData(HighsInt& num_iis_col, HighsInt& num_iis_row, + HighsInt* iis_col_index, + HighsInt* iis_row_index, + HighsInt* iis_col_bound, + HighsInt* iis_row_bound) { + assert(this->iis_.valid); num_iis_col = this->iis_.col_index.size(); num_iis_row = this->iis_.row_index.size(); if (iis_col_index || iis_col_bound) { @@ -1859,17 +1864,21 @@ HighsStatus Highs::optionChangeAction() { dl_user_bound_scale_value = std::pow(2, dl_user_bound_scale); } // Now consider impact on primal feasibility of user bound scaling - // and/or primal_feasibility_tolerance change + // and/or primal_feasibility_tolerance change. + // double new_max_primal_infeasibility = info.max_primal_infeasibility * dl_user_bound_scale_value; if (new_max_primal_infeasibility > options.primal_feasibility_tolerance) { - // Not primal feasible - this->model_status_ = HighsModelStatus::kNotset; - if (info.primal_solution_status == kSolutionStatusFeasible) - highsLogUser(options_.log_options, HighsLogType::kInfo, - "Option change leads to loss of primal feasibility\n"); - info.primal_solution_status = kSolutionStatusInfeasible; - info.num_primal_infeasibilities = kHighsIllegalInfeasibilityCount; + // Not primal feasible: only act if the model is currently primal + // feasible or dl_user_bound_scale_value > 1 + if (info.num_primal_infeasibilities == 0 && dl_user_bound_scale_value > 1) { + this->model_status_ = HighsModelStatus::kNotset; + if (info.primal_solution_status == kSolutionStatusFeasible) + highsLogUser(options_.log_options, HighsLogType::kInfo, + "Option change leads to loss of primal feasibility\n"); + info.primal_solution_status = kSolutionStatusInfeasible; + info.num_primal_infeasibilities = kHighsIllegalInfeasibilityCount; + } } else if (!is_mip && info.primal_solution_status == kSolutionStatusInfeasible) { highsLogUser(options_.log_options, HighsLogType::kInfo, @@ -1891,7 +1900,10 @@ HighsStatus Highs::optionChangeAction() { } } if (dl_user_bound_scale) { - // Update info and solution with respect to non-trivial user bound scaling + // Update info and solution with respect to non-trivial user bound + // scaling + // + // max and sum of infeasibilities scales: num is handled later info.objective_function_value *= dl_user_bound_scale_value; info.max_primal_infeasibility *= dl_user_bound_scale_value; info.sum_primal_infeasibilities *= dl_user_bound_scale_value; @@ -1927,14 +1939,17 @@ HighsStatus Highs::optionChangeAction() { double new_max_dual_infeasibility = info.max_dual_infeasibility * dl_user_cost_scale_value; if (new_max_dual_infeasibility > options.dual_feasibility_tolerance) { - // Not dual feasible - this->model_status_ = HighsModelStatus::kNotset; - if (info.dual_solution_status == kSolutionStatusFeasible) { - highsLogUser(options_.log_options, HighsLogType::kInfo, - "Option change leads to loss of dual feasibility\n"); - info.dual_solution_status = kSolutionStatusInfeasible; + // Not dual feasible: only act if the model is currently dual + // feasible or dl_user_bound_scale_value > 1 + if (info.num_dual_infeasibilities == 0 && dl_user_cost_scale_value > 1) { + this->model_status_ = HighsModelStatus::kNotset; + if (info.dual_solution_status == kSolutionStatusFeasible) { + highsLogUser(options_.log_options, HighsLogType::kInfo, + "Option change leads to loss of dual feasibility\n"); + info.dual_solution_status = kSolutionStatusInfeasible; + } + info.num_dual_infeasibilities = kHighsIllegalInfeasibilityCount; } - info.num_dual_infeasibilities = kHighsIllegalInfeasibilityCount; } else if (info.dual_solution_status == kSolutionStatusInfeasible) { highsLogUser(options_.log_options, HighsLogType::kInfo, "Option change leads to gain of dual feasibility\n"); @@ -1949,6 +1964,8 @@ HighsStatus Highs::optionChangeAction() { } // Now update data and solution with respect to non-trivial user // cost scaling + // + // max and sum of infeasibilities scales: num is handled earlier info.objective_function_value *= dl_user_cost_scale_value; info.max_dual_infeasibility *= dl_user_cost_scale_value; info.sum_dual_infeasibilities *= dl_user_cost_scale_value; @@ -1967,6 +1984,8 @@ HighsStatus Highs::optionChangeAction() { } } if (!user_bound_scale_ok || !user_cost_scale_ok) return HighsStatus::kError; + if (this->iis_.valid && options_.iis_strategy != this->iis_.strategy) + this->iis_.invalidate(); return HighsStatus::kOk; } diff --git a/src/lp_data/HighsOptions.h b/src/lp_data/HighsOptions.h index 38be9b59ae..ebcbdfdd3f 100644 --- a/src/lp_data/HighsOptions.h +++ b/src/lp_data/HighsOptions.h @@ -342,6 +342,9 @@ struct HighsOptionsStruct { HighsInt qp_iteration_limit; HighsInt qp_nullspace_limit; + // Options for IIS calculation + HighsInt iis_strategy; + // Advanced options HighsInt log_dev_level; bool log_githash; @@ -947,6 +950,18 @@ class HighsOptions : public HighsOptionsStruct { &qp_nullspace_limit, 0, 4000, kHighsIInf); records.push_back(record_int); + record_int = new OptionRecordInt( + "iis_strategy", + "Strategy for IIS calculation: " + "Use unbounded dual ray and prioritise low number of rows (default) / " + "Use ray and prioritise low numbers of columns / " + "Use LP and prioritise rows / " + "Use LP and prioritise columns" + " (/0/1/2/3)", + advanced, &iis_strategy, kIisStrategyMin, + kIisStrategyFromRayRowPriority, kIisStrategyMax); + records.push_back(record_int); + // Fix the number of user settable options num_user_settable_options_ = static_cast(records.size()); From d8e5bdea0cd16e60f980fbcfb6a3a061a79d9e61 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 5 Jun 2024 13:04:09 +0100 Subject: [PATCH 007/194] Added row deletion pass: create HighsIis class --- check/TestIis.cpp | 46 ++++++++++ src/lp_data/Highs.cpp | 23 +++-- src/lp_data/HighsIis.cpp | 193 ++++++++++++++++++++++++++++++--------- src/lp_data/HighsIis.h | 3 + 4 files changed, 210 insertions(+), 55 deletions(-) diff --git a/check/TestIis.cpp b/check/TestIis.cpp index 055acd8f3d..ea73aa23d5 100644 --- a/check/TestIis.cpp +++ b/check/TestIis.cpp @@ -10,6 +10,11 @@ const bool dev_run = false; const double inf = kHighsInf; TEST_CASE("lp-incompatible-bounds", "[iis]") { + // LP has row0 and col2 with inconsistent bounds. + // + // When prioritising rows, row0 and its constituent columns (1, 2) should be found + // + // When prioritising columns, col2 and its constituent rows (0, 1) should be found HighsLp lp; lp.num_col_ = 3; lp.num_row_ = 2; @@ -66,6 +71,47 @@ TEST_CASE("lp-incompatible-bounds", "[iis]") { } TEST_CASE("lp-get-iis", "[iis]") { + HighsLp lp; + lp.num_col_ = 2; + lp.num_row_ = 3; + lp.col_cost_ = {0, 0}; + lp.col_lower_ = {0, 0}; + lp.col_upper_ = {inf, inf}; + lp.row_lower_ = {-inf, -inf, -inf}; + lp.row_upper_ = {8, 9, -2}; + lp.a_matrix_.format_ = MatrixFormat::kRowwise; + lp.a_matrix_.start_ = {0, 2, 4, 6}; + lp.a_matrix_.index_ = {0, 1, 0, 1, 0, 1}; + lp.a_matrix_.value_ = {2, 1, 1, 3, 1, 1}; + Highs highs; + highs.passModel(lp); + REQUIRE(highs.run() == HighsStatus::kOk); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); + HighsInt num_iis_col; + HighsInt num_iis_row; + std::vector iis_col_index; + std::vector iis_row_index; + std::vector iis_col_bound; + std::vector iis_row_bound; + highs.setOptionValue("iis_strategy", kIisStrategyFromLpRowPriority); + REQUIRE(highs.getIis(num_iis_col, num_iis_row) == HighsStatus::kOk); + REQUIRE(num_iis_col == 2); + REQUIRE(num_iis_row == 1); + iis_col_index.resize(num_iis_col); + iis_row_index.resize(num_iis_row); + iis_col_bound.resize(num_iis_col); + iis_row_bound.resize(num_iis_row); + REQUIRE(highs.getIis(num_iis_col, num_iis_row, iis_col_index.data(), + iis_row_index.data(), + // iis_col_bound.data(), iis_row_bound.data() + nullptr, nullptr) == HighsStatus::kOk); + REQUIRE(iis_col_index[0] == 0); + REQUIRE(iis_col_index[1] == 1); + REQUIRE(iis_row_index[0] == 2); + +} + +TEST_CASE("lp-get-iis-galenet", "[iis]") { std::string model = "galenet"; std::string model_file = std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index 3d54c48f8f..9d0c32a8a8 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -2339,6 +2339,7 @@ HighsStatus analyseSetCreateError(HighsLogOptions log_options, const HighsInt create_error, const bool ordered, const HighsInt num_set_entries, + const HighsInt* set, const HighsInt dimension) { if (create_error == kIndexCollectionCreateIllegalSetSize) { highsLogUser(log_options, HighsLogType::kError, @@ -2359,10 +2360,12 @@ HighsStatus analyseSetCreateError(HighsLogOptions log_options, "Set supplied to Highs::%s not ordered\n", method.c_str()); } } else if (create_error < 0) { + HighsInt illegal_set_index = -1 - create_error; + HighsInt illegal_set_entry = set[illegal_set_index]; highsLogUser( log_options, HighsLogType::kError, - "Set supplied to Highs::%s has entry %d out of range [0, %d)\n", - method.c_str(), int(-1 - create_error), int(dimension)); + "Set supplied to Highs::%s has entry %d of %d out of range [0, %d)\n", + method.c_str(), int(illegal_set_index), int(illegal_set_entry), int(dimension)); } assert(create_error != kIndexCollectionCreateIllegalSetDimension); return HighsStatus::kError; @@ -2384,7 +2387,7 @@ HighsStatus Highs::changeColsIntegrality(const HighsInt num_set_entries, local_set.data(), model_.lp_.num_col_); if (create_error) return analyseSetCreateError(options_.log_options, "changeColsIntegrality", - create_error, true, num_set_entries, + create_error, true, num_set_entries, local_set.data(), model_.lp_.num_col_); HighsStatus call_status = changeIntegralityInterface(index_collection, local_integrality.data()); @@ -2454,7 +2457,7 @@ HighsStatus Highs::changeColsCost(const HighsInt num_set_entries, local_set.data(), model_.lp_.num_col_); if (create_error) return analyseSetCreateError(options_.log_options, "changeColsCost", - create_error, true, num_set_entries, + create_error, true, num_set_entries, local_set.data(), model_.lp_.num_col_); HighsStatus call_status = changeCostsInterface(index_collection, local_cost.data()); @@ -2533,7 +2536,7 @@ HighsStatus Highs::changeColsBounds(const HighsInt num_set_entries, local_set.data(), model_.lp_.num_col_); if (create_error) return analyseSetCreateError(options_.log_options, "changeColsBounds", - create_error, true, num_set_entries, + create_error, true, num_set_entries, local_set.data(), model_.lp_.num_col_); HighsStatus call_status = changeColBoundsInterface( index_collection, local_lower.data(), local_upper.data()); @@ -2614,7 +2617,7 @@ HighsStatus Highs::changeRowsBounds(const HighsInt num_set_entries, local_set.data(), model_.lp_.num_row_); if (create_error) return analyseSetCreateError(options_.log_options, "changeRowsBounds", - create_error, true, num_set_entries, + create_error, true, num_set_entries, local_set.data(), model_.lp_.num_row_); HighsStatus call_status = changeRowBoundsInterface( index_collection, local_lower.data(), local_upper.data()); @@ -2720,7 +2723,7 @@ HighsStatus Highs::getCols(const HighsInt num_set_entries, const HighsInt* set, create(index_collection, num_set_entries, set, model_.lp_.num_col_); if (create_error) return analyseSetCreateError(options_.log_options, "getCols", create_error, - false, num_set_entries, model_.lp_.num_col_); + false, num_set_entries, set, model_.lp_.num_col_); getColsInterface(index_collection, num_col, costs, lower, upper, num_nz, start, index, value); return returnFromHighs(HighsStatus::kOk); @@ -2839,7 +2842,7 @@ HighsStatus Highs::getRows(const HighsInt num_set_entries, const HighsInt* set, create(index_collection, num_set_entries, set, model_.lp_.num_row_); if (create_error) return analyseSetCreateError(options_.log_options, "getRows", create_error, - false, num_set_entries, model_.lp_.num_row_); + false, num_set_entries, set, model_.lp_.num_row_); getRowsInterface(index_collection, num_row, lower, upper, num_nz, start, index, value); return returnFromHighs(HighsStatus::kOk); @@ -2947,7 +2950,7 @@ HighsStatus Highs::deleteCols(const HighsInt num_set_entries, create(index_collection, num_set_entries, set, model_.lp_.num_col_); if (create_error) return analyseSetCreateError(options_.log_options, "deleteCols", - create_error, false, num_set_entries, + create_error, false, num_set_entries, set, model_.lp_.num_col_); deleteColsInterface(index_collection); return returnFromHighs(HighsStatus::kOk); @@ -2991,7 +2994,7 @@ HighsStatus Highs::deleteRows(const HighsInt num_set_entries, create(index_collection, num_set_entries, set, model_.lp_.num_row_); if (create_error) return analyseSetCreateError(options_.log_options, "deleteRows", - create_error, false, num_set_entries, + create_error, false, num_set_entries, set, model_.lp_.num_row_); deleteRowsInterface(index_collection); return returnFromHighs(HighsStatus::kOk); diff --git a/src/lp_data/HighsIis.cpp b/src/lp_data/HighsIis.cpp index 1ddd9d6735..00d20a4840 100644 --- a/src/lp_data/HighsIis.cpp +++ b/src/lp_data/HighsIis.cpp @@ -25,6 +25,20 @@ void HighsIis::invalidate() { this->row_bound.clear(); } +void iisRemoveCddCol(const HighsInt cdd_col, HighsIis& iis) { + HighsInt num_cdd = iis.col_index.size(); + assert(cdd_col < num_cdd); + iis.col_index[cdd_col] = iis.col_index[num_cdd-1]; + iis.col_index.resize(num_cdd-1); +} + +void iisRemoveCddRow(const HighsInt cdd_row, HighsIis& iis) { + HighsInt num_cdd = iis.row_index.size(); + assert(cdd_row < num_cdd); + iis.row_index[cdd_row] = iis.row_index[num_cdd-1]; + iis.row_index.resize(num_cdd-1); +} + bool iisInconsistentBounds(const HighsLp& lp, const HighsOptions& options, HighsIis& iis) { iis.invalidate(); @@ -89,62 +103,151 @@ HighsStatus getIisData(const HighsLp& lp, const HighsOptions& options, // Check for inconsistent column and row bounds should have been // done earlier assert(!iisInconsistentBounds(lp, options, iis)); - std::vector from_row; - std::vector from_col; - std::vector to_row; - to_row.assign(lp.num_row_, -1); - assert(lp.a_matrix_.isColwise()); - for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { - if (dual_ray_value[iRow]) { - to_row[iRow] = from_row.size(); - from_row.push_back(iRow); + + if (options.iis_strategy == kIisStrategyFromRayRowPriority || + options.iis_strategy == kIisStrategyFromRayColPriority) { + // Identify the LP corresponding to the ray + std::vector from_row; + std::vector from_col; + std::vector to_row; + to_row.assign(lp.num_row_, -1); + assert(lp.a_matrix_.isColwise()); + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { + if (dual_ray_value[iRow]) { + to_row[iRow] = from_row.size(); + from_row.push_back(iRow); + } + printf("getIisData: dual_ray_value[%2d] = %g; to_row[%2d] = %d\n", + int(iRow), dual_ray_value[iRow], int(iRow), to_row[iRow]); } - printf("getIisData: dual_ray_value[%2d] = %g; to_row[%2d] = %d\n", - int(iRow), dual_ray_value[iRow], int(iRow), to_row[iRow]); - } - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { - bool use_col = false; - for (HighsInt iEl = lp.a_matrix_.start_[iCol]; - iEl < lp.a_matrix_.start_[iCol + 1]; iEl++) - use_col = use_col || to_row[lp.a_matrix_.index_[iEl]] >= 0; - if (use_col) from_col.push_back(iCol); - } - HighsLp to_lp; - HighsInt to_num_col = from_col.size(); - HighsInt to_num_row = from_row.size(); - to_lp.num_col_ = to_num_col; - to_lp.num_row_ = to_num_row; - for (HighsInt iCol = 0; iCol < to_num_col; iCol++) { - printf("getIisData: from_col[%2d] = %d\n", int(iCol), int(from_col[iCol])); - to_lp.col_cost_.push_back(0); - to_lp.col_lower_.push_back(lp.col_lower_[from_col[iCol]]); - to_lp.col_upper_.push_back(lp.col_upper_[from_col[iCol]]); - for (HighsInt iEl = lp.a_matrix_.start_[from_col[iCol]]; - iEl < lp.a_matrix_.start_[from_col[iCol] + 1]; iEl++) { - HighsInt iRow = lp.a_matrix_.index_[iEl]; - if (to_row[iRow] >= 0) { - to_lp.a_matrix_.index_.push_back(to_row[iRow]); - to_lp.a_matrix_.value_.push_back(lp.a_matrix_.value_[iEl]); + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + bool use_col = false; + for (HighsInt iEl = lp.a_matrix_.start_[iCol]; + iEl < lp.a_matrix_.start_[iCol + 1]; iEl++) + use_col = use_col || to_row[lp.a_matrix_.index_[iEl]] >= 0; + if (use_col) from_col.push_back(iCol); + } + HighsInt to_num_col = from_col.size(); + HighsInt to_num_row = from_row.size(); + HighsLp to_lp; + to_lp.num_col_ = to_num_col; + to_lp.num_row_ = to_num_row; + for (HighsInt iCol = 0; iCol < to_num_col; iCol++) { + printf("getIisData: from_col[%2d] = %d\n", int(iCol), int(from_col[iCol])); + to_lp.col_cost_.push_back(0); + to_lp.col_lower_.push_back(lp.col_lower_[from_col[iCol]]); + to_lp.col_upper_.push_back(lp.col_upper_[from_col[iCol]]); + for (HighsInt iEl = lp.a_matrix_.start_[from_col[iCol]]; + iEl < lp.a_matrix_.start_[from_col[iCol] + 1]; iEl++) { + HighsInt iRow = lp.a_matrix_.index_[iEl]; + if (to_row[iRow] >= 0) { + to_lp.a_matrix_.index_.push_back(to_row[iRow]); + to_lp.a_matrix_.value_.push_back(lp.a_matrix_.value_[iEl]); + } } + to_lp.a_matrix_.start_.push_back(to_lp.a_matrix_.index_.size()); } - to_lp.a_matrix_.start_.push_back(to_lp.a_matrix_.index_.size()); - } - for (HighsInt iRow = 0; iRow < to_num_row; iRow++) { - printf("getIisData: from_row[%2d] = %d\n", int(iRow), int(from_row[iRow])); - to_lp.row_lower_.push_back(lp.row_lower_[from_row[iRow]]); - to_lp.row_upper_.push_back(lp.row_upper_[from_row[iRow]]); + for (HighsInt iRow = 0; iRow < to_num_row; iRow++) { + printf("getIisData: from_row[%2d] = %d\n", int(iRow), int(from_row[iRow])); + to_lp.row_lower_.push_back(lp.row_lower_[from_row[iRow]]); + to_lp.row_upper_.push_back(lp.row_upper_[from_row[iRow]]); + } + if (computeIis(to_lp, options, iis) != HighsStatus::kOk) return HighsStatus::kError; + // IIS col/row information is for to_lp, so indirect the values + // into the original LP + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + iis.col_index[iCol] = from_col[iCol]; + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) + iis.row_index[iRow] = from_row[iRow]; + } else { + // Use the whole LP + if (computeIis(lp, options, iis) != HighsStatus::kOk) return HighsStatus::kError; } + return HighsStatus::kOk; +} + +HighsStatus computeIis(const HighsLp& lp, const HighsOptions& options, + HighsIis& iis) { + iis.invalidate(); + const HighsLogOptions& log_options = options.log_options; + // Initially all columns and rows are candidates for the IIS + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + iis.col_index.push_back(iCol); + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) + iis.row_index.push_back(iRow); Highs highs; highs.setOptionValue("presolve", kHighsOffString); - HighsStatus status = highs.passModel(to_lp); + highs.setOptionValue("output_flag", false); + HighsStatus status = highs.passModel(lp); assert(status == HighsStatus::kOk); status = highs.run(); if (status != HighsStatus::kOk) return status; assert(highs.getModelStatus() == HighsModelStatus::kInfeasible); + // Perform row-deletion pass + + HighsInt num_row_cdd = iis.row_index.size(); + for (HighsInt iCddRow = 0; iCddRow < num_row_cdd; iCddRow++) { + for (;;) { + HighsInt iRow = iis.row_index[iCddRow]; + const double row_lower = lp.row_lower_[iRow]; + const double row_upper = lp.row_upper_[iRow]; + // Record whether a bound can be dropped: by default it's + // possible, and only not possible if the bound is finite and the + // LP remains infeasible if the bound is dropped + bool drop_lower = true; + bool drop_upper = true; + if (row_lower > -kHighsInf) { + // Drop the lower bound temporarily + status = highs.changeRowBounds(iRow, -kHighsInf, row_upper); + assert(status == HighsStatus::kOk); + status = highs.run(); + assert(status == HighsStatus::kOk); + HighsModelStatus model_status = highs.getModelStatus(); + if (model_status == HighsModelStatus::kOptimal) { + // Now feasible, so restore the lower bound and indicate that + // it cannot be dropped permanently + status = highs.changeRowBounds(iRow, row_lower, row_upper); + assert(status == HighsStatus::kOk); + drop_lower = false; + } else { + assert(model_status == HighsModelStatus::kInfeasible); + // Bound can be dropped permanently + } + } + if (row_upper < kHighsInf) { + // Drop the upper bound temporarily + status = highs.changeRowBounds(iRow, row_lower, kHighsInf); + assert(status == HighsStatus::kOk); + status = highs.run(); + assert(status == HighsStatus::kOk); + HighsModelStatus model_status = highs.getModelStatus(); + if (model_status == HighsModelStatus::kOptimal) { + // Now feasible, so restore the upper bound and indicate that + // it cannot be dropped permanently + status = highs.changeRowBounds(iRow, row_lower, row_upper); + assert(status == HighsStatus::kOk); + drop_upper = false; + } else { + assert(model_status == HighsModelStatus::kInfeasible); + // Bound can be dropped permanently + } + } + if (drop_lower && drop_upper) { + // Both bounds can be dropped, so remove the row from the set of + // candidates + status = highs.changeRowBounds(iRow, -kHighsInf, kHighsInf); + assert(status == HighsStatus::kOk); + iisRemoveCddRow(iCddRow, iis); + num_row_cdd--; + highsLogUser(log_options, HighsLogType::kInfo, "Dropped row %d from candidate set\n", int(iRow)); + if (iCddRow >= num_row_cdd) break; + } else { + highsLogUser(log_options, HighsLogType::kInfo, "Retained row %d in candidate set\n", int(iRow)); + break; + } + } + } iis.valid = true; iis.strategy = options.iis_strategy; - iis.col_index = from_col; - iis.row_index = from_row; - return HighsStatus::kOk; } diff --git a/src/lp_data/HighsIis.h b/src/lp_data/HighsIis.h index e8ba9a7039..63fc136e31 100644 --- a/src/lp_data/HighsIis.h +++ b/src/lp_data/HighsIis.h @@ -30,6 +30,9 @@ HighsStatus getIisData(const HighsLp& lp, const HighsOptions& options, const std::vector& dual_ray_value, HighsIis& iis); +HighsStatus computeIis(const HighsLp& lp, const HighsOptions& options, + HighsIis& iis); + bool iisInconsistentBounds(const HighsLp& lp, const HighsOptions& options, HighsIis& iis); #endif // LP_DATA_HIGHSIIS_H_ From e6801e83f593b49ac3dcd7e55a093f14bfd51760 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 5 Jun 2024 14:13:05 +0100 Subject: [PATCH 008/194] Created HighsIis class --- src/lp_data/HighsIis.cpp | 97 ++++++++++++++++------------------ src/lp_data/HighsIis.h | 34 ++++++------ src/lp_data/HighsInterface.cpp | 22 ++++---- 3 files changed, 77 insertions(+), 76 deletions(-) diff --git a/src/lp_data/HighsIis.cpp b/src/lp_data/HighsIis.cpp index 00d20a4840..17a57b823e 100644 --- a/src/lp_data/HighsIis.cpp +++ b/src/lp_data/HighsIis.cpp @@ -17,31 +17,30 @@ #include "Highs.h" void HighsIis::invalidate() { - this->valid = false; - this->strategy = kIisStrategyMin; - this->col_index.clear(); - this->row_index.clear(); - this->col_bound.clear(); - this->row_bound.clear(); + this->valid_ = false; + this->strategy_ = kIisStrategyMin; + this->col_index_.clear(); + this->row_index_.clear(); + this->col_bound_.clear(); + this->row_bound_.clear(); } -void iisRemoveCddCol(const HighsInt cdd_col, HighsIis& iis) { - HighsInt num_cdd = iis.col_index.size(); +void HighsIis::removeCddCol(const HighsInt cdd_col) { + HighsInt num_cdd = this->col_index_.size(); assert(cdd_col < num_cdd); - iis.col_index[cdd_col] = iis.col_index[num_cdd-1]; - iis.col_index.resize(num_cdd-1); + this->col_index_[cdd_col] = this->col_index_[num_cdd-1]; + this->col_index_.resize(num_cdd-1); } -void iisRemoveCddRow(const HighsInt cdd_row, HighsIis& iis) { - HighsInt num_cdd = iis.row_index.size(); +void HighsIis::removeCddRow(const HighsInt cdd_row) { + HighsInt num_cdd = this->row_index_.size(); assert(cdd_row < num_cdd); - iis.row_index[cdd_row] = iis.row_index[num_cdd-1]; - iis.row_index.resize(num_cdd-1); + this->row_index_[cdd_row] = this->row_index_[num_cdd-1]; + this->row_index_.resize(num_cdd-1); } -bool iisInconsistentBounds(const HighsLp& lp, const HighsOptions& options, - HighsIis& iis) { - iis.invalidate(); +bool HighsIis::inconsistentBounds(const HighsLp& lp, const HighsOptions& options) { + this->invalidate(); const bool col_priority = options.iis_strategy == kIisStrategyFromRayColPriority || options.iis_strategy == kIisStrategyFromLpColPriority; @@ -51,25 +50,25 @@ bool iisInconsistentBounds(const HighsLp& lp, const HighsOptions& options, for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { if (lp.col_lower_[iCol] - lp.col_upper_[iCol] > 2 * options.primal_feasibility_tolerance) { - iis.col_index.push_back(iCol); + this->col_index_.push_back(iCol); break; } } - if (iis.col_index.size() > 0) break; + if (this->col_index_.size() > 0) break; } else { // Loop over rows first for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { if (lp.row_lower_[iRow] - lp.row_upper_[iRow] > 2 * options.primal_feasibility_tolerance) { - iis.row_index.push_back(iRow); + this->row_index_.push_back(iRow); break; } } - if (iis.row_index.size() > 0) break; + if (this->row_index_.size() > 0) break; } } - HighsInt num_iis_col = iis.col_index.size(); - HighsInt num_iis_row = iis.row_index.size(); + HighsInt num_iis_col = this->col_index_.size(); + HighsInt num_iis_row = this->row_index_.size(); // If none found then return false if (num_iis_col + num_iis_row == 0) return false; // Should have found exactly 1 @@ -78,31 +77,30 @@ bool iisInconsistentBounds(const HighsLp& lp, const HighsOptions& options, assert(lp.a_matrix_.isColwise()); if (num_iis_col > 0) { // Found inconsistent column - HighsInt iCol = iis.col_index[0]; + HighsInt iCol = this->col_index_[0]; for (HighsInt iEl = lp.a_matrix_.start_[iCol]; iEl < lp.a_matrix_.start_[iCol + 1]; iEl++) - iis.row_index.push_back(lp.a_matrix_.index_[iEl]); + this->row_index_.push_back(lp.a_matrix_.index_[iEl]); } else { // Found inconsistent row - HighsInt iRow = iis.row_index[0]; + HighsInt iRow = this->row_index_[0]; for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { for (HighsInt iEl = lp.a_matrix_.start_[iCol]; iEl < lp.a_matrix_.start_[iCol + 1]; iEl++) - if (lp.a_matrix_.index_[iEl] == iRow) iis.col_index.push_back(iCol); + if (lp.a_matrix_.index_[iEl] == iRow) this->col_index_.push_back(iCol); } } - iis.valid = true; - iis.strategy = options.iis_strategy; + this->valid_ = true; + this->strategy_ = options.iis_strategy; return true; } -HighsStatus getIisData(const HighsLp& lp, const HighsOptions& options, - const std::vector& dual_ray_value, - HighsIis& iis) { +HighsStatus HighsIis::getData(const HighsLp& lp, const HighsOptions& options, + const std::vector& dual_ray_value) { // Check for inconsistent column and row bounds should have been // done earlier - assert(!iisInconsistentBounds(lp, options, iis)); + assert(!this->inconsistentBounds(lp, options)); if (options.iis_strategy == kIisStrategyFromRayRowPriority || options.iis_strategy == kIisStrategyFromRayColPriority) { @@ -117,7 +115,7 @@ HighsStatus getIisData(const HighsLp& lp, const HighsOptions& options, to_row[iRow] = from_row.size(); from_row.push_back(iRow); } - printf("getIisData: dual_ray_value[%2d] = %g; to_row[%2d] = %d\n", + printf("HighsIis::getData: dual_ray_value[%2d] = %g; to_row[%2d] = %d\n", int(iRow), dual_ray_value[iRow], int(iRow), to_row[iRow]); } for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { @@ -152,29 +150,28 @@ HighsStatus getIisData(const HighsLp& lp, const HighsOptions& options, to_lp.row_lower_.push_back(lp.row_lower_[from_row[iRow]]); to_lp.row_upper_.push_back(lp.row_upper_[from_row[iRow]]); } - if (computeIis(to_lp, options, iis) != HighsStatus::kOk) return HighsStatus::kError; + if (this->compute(to_lp, options) != HighsStatus::kOk) return HighsStatus::kError; // IIS col/row information is for to_lp, so indirect the values // into the original LP - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) - iis.col_index[iCol] = from_col[iCol]; - for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) - iis.row_index[iRow] = from_row[iRow]; + for (HighsInt iCol = 0; iCol < HighsInt(this->col_index_.size()); iCol++) + this->col_index_[iCol] = from_col[iCol]; + for (HighsInt iRow = 0; iRow < HighsInt(this->row_index_.size()); iRow++) + this->row_index_[iRow] = from_row[iRow]; } else { // Use the whole LP - if (computeIis(lp, options, iis) != HighsStatus::kOk) return HighsStatus::kError; + if (this->compute(lp, options) != HighsStatus::kOk) return HighsStatus::kError; } return HighsStatus::kOk; } -HighsStatus computeIis(const HighsLp& lp, const HighsOptions& options, - HighsIis& iis) { - iis.invalidate(); +HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options) { + this->invalidate(); const HighsLogOptions& log_options = options.log_options; // Initially all columns and rows are candidates for the IIS for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) - iis.col_index.push_back(iCol); + this->col_index_.push_back(iCol); for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) - iis.row_index.push_back(iRow); + this->row_index_.push_back(iRow); Highs highs; highs.setOptionValue("presolve", kHighsOffString); highs.setOptionValue("output_flag", false); @@ -185,10 +182,10 @@ HighsStatus computeIis(const HighsLp& lp, const HighsOptions& options, assert(highs.getModelStatus() == HighsModelStatus::kInfeasible); // Perform row-deletion pass - HighsInt num_row_cdd = iis.row_index.size(); + HighsInt num_row_cdd = this->row_index_.size(); for (HighsInt iCddRow = 0; iCddRow < num_row_cdd; iCddRow++) { for (;;) { - HighsInt iRow = iis.row_index[iCddRow]; + HighsInt iRow = this->row_index_[iCddRow]; const double row_lower = lp.row_lower_[iRow]; const double row_upper = lp.row_upper_[iRow]; // Record whether a bound can be dropped: by default it's @@ -237,7 +234,7 @@ HighsStatus computeIis(const HighsLp& lp, const HighsOptions& options, // candidates status = highs.changeRowBounds(iRow, -kHighsInf, kHighsInf); assert(status == HighsStatus::kOk); - iisRemoveCddRow(iCddRow, iis); + this->removeCddRow(iCddRow); num_row_cdd--; highsLogUser(log_options, HighsLogType::kInfo, "Dropped row %d from candidate set\n", int(iRow)); if (iCddRow >= num_row_cdd) break; @@ -247,7 +244,7 @@ HighsStatus computeIis(const HighsLp& lp, const HighsOptions& options, } } } - iis.valid = true; - iis.strategy = options.iis_strategy; + this->valid_ = true; + this->strategy_ = options.iis_strategy; return HighsStatus::kOk; } diff --git a/src/lp_data/HighsIis.h b/src/lp_data/HighsIis.h index 63fc136e31..683638a10e 100644 --- a/src/lp_data/HighsIis.h +++ b/src/lp_data/HighsIis.h @@ -16,23 +16,27 @@ #include "lp_data/HighsLp.h" -struct HighsIis { - bool valid = false; - HighsInt strategy = kIisStrategyMin; - std::vector col_index; - std::vector row_index; - std::vector col_bound; - std::vector row_bound; +class HighsIis { + public: + HighsIis() {} + void invalidate(); -}; + void removeCddCol(const HighsInt cdd_col); + void removeCddRow(const HighsInt cdd_row); + HighsStatus getData(const HighsLp& lp, const HighsOptions& options, + const std::vector& dual_ray_value); -HighsStatus getIisData(const HighsLp& lp, const HighsOptions& options, - const std::vector& dual_ray_value, - HighsIis& iis); + HighsStatus compute(const HighsLp& lp, const HighsOptions& options); -HighsStatus computeIis(const HighsLp& lp, const HighsOptions& options, - HighsIis& iis); + bool inconsistentBounds(const HighsLp& lp, const HighsOptions& options); + + // Data members + bool valid_ = false; + HighsInt strategy_ = kIisStrategyMin; + std::vector col_index_; + std::vector row_index_; + std::vector col_bound_; + std::vector row_bound_; +}; -bool iisInconsistentBounds(const HighsLp& lp, const HighsOptions& options, - HighsIis& iis); #endif // LP_DATA_HIGHSIIS_H_ diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 494a258a11..57a83f0715 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -1550,10 +1550,10 @@ HighsStatus Highs::getRangingInterface() { } HighsStatus Highs::getIisInterface() { - if (this->iis_.valid) return HighsStatus::kOk; + if (this->iis_.valid_) return HighsStatus::kOk; HighsLp& lp = model_.lp_; // Check for inconsistent column and row bounds - if (iisInconsistentBounds(lp, options_, iis_)) return HighsStatus::kOk; + if (this->iis_.inconsistentBounds(lp, options_)) return HighsStatus::kOk; HighsInt num_row = lp.num_row_; // For an LP with no rows the dual ray is vacuous if (num_row == 0) return HighsStatus::kOk; @@ -1592,7 +1592,7 @@ HighsStatus Highs::getIisInterface() { return HighsStatus::kError; } } - return getIisData(lp, options_, dual_ray_value, this->iis_); + return this->iis_.getData(lp, options_, dual_ray_value); } HighsStatus Highs::extractIisData(HighsInt& num_iis_col, HighsInt& num_iis_row, @@ -1600,19 +1600,19 @@ HighsStatus Highs::extractIisData(HighsInt& num_iis_col, HighsInt& num_iis_row, HighsInt* iis_row_index, HighsInt* iis_col_bound, HighsInt* iis_row_bound) { - assert(this->iis_.valid); - num_iis_col = this->iis_.col_index.size(); - num_iis_row = this->iis_.row_index.size(); + assert(this->iis_.valid_); + num_iis_col = this->iis_.col_index_.size(); + num_iis_row = this->iis_.row_index_.size(); if (iis_col_index || iis_col_bound) { for (HighsInt iCol = 0; iCol < num_iis_col; iCol++) { - if (iis_col_index) iis_col_index[iCol] = this->iis_.col_index[iCol]; - if (iis_col_bound) iis_col_bound[iCol] = this->iis_.col_bound[iCol]; + if (iis_col_index) iis_col_index[iCol] = this->iis_.col_index_[iCol]; + if (iis_col_bound) iis_col_bound[iCol] = this->iis_.col_bound_[iCol]; } } if (iis_row_index || iis_row_bound) { for (HighsInt iRow = 0; iRow < num_iis_row; iRow++) { - if (iis_row_index) iis_row_index[iRow] = this->iis_.row_index[iRow]; - if (iis_row_bound) iis_row_bound[iRow] = this->iis_.row_bound[iRow]; + if (iis_row_index) iis_row_index[iRow] = this->iis_.row_index_[iRow]; + if (iis_row_bound) iis_row_bound[iRow] = this->iis_.row_bound_[iRow]; } } return HighsStatus::kOk; @@ -1984,7 +1984,7 @@ HighsStatus Highs::optionChangeAction() { } } if (!user_bound_scale_ok || !user_cost_scale_ok) return HighsStatus::kError; - if (this->iis_.valid && options_.iis_strategy != this->iis_.strategy) + if (this->iis_.valid_ && options_.iis_strategy != this->iis_.strategy_) this->iis_.invalidate(); return HighsStatus::kOk; } From 47cecca77296dac1a444d468cf161e2df6d7d2c4 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 5 Jun 2024 14:50:03 +0100 Subject: [PATCH 009/194] Added proper testing of IIS once found --- check/TestIis.cpp | 94 +++++++++++++++++++------- src/lp_data/HighsIis.cpp | 141 +++++++++++++++++++++------------------ src/lp_data/HighsIis.h | 4 +- 3 files changed, 148 insertions(+), 91 deletions(-) diff --git a/check/TestIis.cpp b/check/TestIis.cpp index ea73aa23d5..8dd0fe0e97 100644 --- a/check/TestIis.cpp +++ b/check/TestIis.cpp @@ -6,9 +6,75 @@ #include "catch.hpp" //#include "io/FilereaderLp.h" -const bool dev_run = false; +const bool dev_run = true; const double inf = kHighsInf; +void testIis(std::string& model, + HighsInt& num_iis_col, HighsInt& num_iis_row, + HighsInt* iis_col_index, + HighsInt* iis_row_index) { + std::string model_file = + std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; + Highs highs; + highs.setOptionValue("output_flag", false); + REQUIRE(highs.readModel(model_file) == HighsStatus::kOk); + HighsLp lp = highs.getLp(); + // Zero the objective + lp.col_cost_.assign(lp.num_col_, 0); + REQUIRE(highs.changeColsCost(0, lp.num_col_-1, lp.col_cost_.data()) == HighsStatus::kOk); + + for (HighsInt iisRow = 0; iisRow < num_iis_row; iisRow++) { + HighsInt iRow = iis_row_index[iisRow]; + const double lower = lp.row_lower_[iRow]; + const double upper = lp.row_upper_[iRow]; + REQUIRE(highs.changeRowBounds(iRow, -kHighsInf, kHighsInf) == HighsStatus::kOk); + REQUIRE(highs.run() == HighsStatus::kOk); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal); + REQUIRE(highs.changeRowBounds(iRow, lower, upper) == HighsStatus::kOk); + if (dev_run) + printf("Removing IIS Row %d (LP row %d) yields optimality\n", int(iisRow), int(iRow)); + } + for (HighsInt iisCol = 0; iisCol < num_iis_col; iisCol++) { + HighsInt iCol = iis_col_index[iisCol]; + const double lower = lp.col_lower_[iCol]; + const double upper = lp.col_upper_[iCol]; + REQUIRE(highs.changeColBounds(iCol, -kHighsInf, kHighsInf) == HighsStatus::kOk); + REQUIRE(highs.run() == HighsStatus::kOk); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal); + REQUIRE(highs.changeColBounds(iCol, lower, upper) == HighsStatus::kOk); + if (dev_run) + printf("Removing IIS Col %d (LP row %d) yields optimality\n", int(iisCol), int(iCol)); + } +} + +void testMps(std::string& model) { + std::string model_file = + std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; + Highs highs; + highs.setOptionValue("output_flag", dev_run); + + HighsInt num_iis_col; + HighsInt num_iis_row; + + REQUIRE(highs.readModel(model_file) == HighsStatus::kOk); + REQUIRE(highs.run() == HighsStatus::kOk); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); + REQUIRE(highs.getIis(num_iis_col, num_iis_row) == HighsStatus::kOk); + REQUIRE(num_iis_col > 0); + REQUIRE(num_iis_row > 0); + std::vector iis_col_index(num_iis_col); + std::vector iis_row_index(num_iis_row); + std::vector iis_col_bound(num_iis_col); + std::vector iis_row_bound(num_iis_row); + REQUIRE(highs.getIis(num_iis_col, num_iis_row, iis_col_index.data(), + iis_row_index.data(), + // iis_col_bound.data(), iis_row_bound.data() + nullptr, nullptr) == HighsStatus::kOk); + if (dev_run) + printf("Model %s has IIS with %d columns and %d rows\n", model.c_str(), int(num_iis_col), int(num_iis_row)); + testIis(model, num_iis_col, num_iis_row, iis_col_index.data(), iis_row_index.data()); +} + TEST_CASE("lp-incompatible-bounds", "[iis]") { // LP has row0 and col2 with inconsistent bounds. // @@ -28,7 +94,7 @@ TEST_CASE("lp-incompatible-bounds", "[iis]") { lp.a_matrix_.index_ = {1, 2, 0, 2}; lp.a_matrix_.value_ = {1, 1, 1, 1}; Highs highs; - // highs.setOptionValue("output_flag", dev_run); + highs.setOptionValue("output_flag", dev_run); highs.passModel(lp); REQUIRE(highs.run() == HighsStatus::kOk); REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); @@ -84,6 +150,7 @@ TEST_CASE("lp-get-iis", "[iis]") { lp.a_matrix_.index_ = {0, 1, 0, 1, 0, 1}; lp.a_matrix_.value_ = {2, 1, 1, 3, 1, 1}; Highs highs; + highs.setOptionValue("output_flag", dev_run); highs.passModel(lp); REQUIRE(highs.run() == HighsStatus::kOk); REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); @@ -113,26 +180,5 @@ TEST_CASE("lp-get-iis", "[iis]") { TEST_CASE("lp-get-iis-galenet", "[iis]") { std::string model = "galenet"; - std::string model_file = - std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; - Highs highs; - // highs.setOptionValue("output_flag", dev_run); - - HighsInt num_iis_col; - HighsInt num_iis_row; - - REQUIRE(highs.readModel(model_file) == HighsStatus::kOk); - REQUIRE(highs.run() == HighsStatus::kOk); - REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); - REQUIRE(highs.getIis(num_iis_col, num_iis_row) == HighsStatus::kOk); - REQUIRE(num_iis_col > 0); - REQUIRE(num_iis_row > 0); - std::vector iis_col_index(num_iis_col); - std::vector iis_row_index(num_iis_row); - std::vector iis_col_bound(num_iis_col); - std::vector iis_row_bound(num_iis_row); - REQUIRE(highs.getIis(num_iis_col, num_iis_row, iis_col_index.data(), - iis_row_index.data(), - // iis_col_bound.data(), iis_row_bound.data() - nullptr, nullptr) == HighsStatus::kOk); + testMps(model); } diff --git a/src/lp_data/HighsIis.cpp b/src/lp_data/HighsIis.cpp index 17a57b823e..9768896ff6 100644 --- a/src/lp_data/HighsIis.cpp +++ b/src/lp_data/HighsIis.cpp @@ -25,18 +25,18 @@ void HighsIis::invalidate() { this->row_bound_.clear(); } -void HighsIis::removeCddCol(const HighsInt cdd_col) { - HighsInt num_cdd = this->col_index_.size(); - assert(cdd_col < num_cdd); - this->col_index_[cdd_col] = this->col_index_[num_cdd-1]; - this->col_index_.resize(num_cdd-1); +void HighsIis::removeCol(const HighsInt col) { + HighsInt num_col = this->col_index_.size(); + assert(col < num_col); + this->col_index_[col] = this->col_index_[num_col-1]; + this->col_index_.resize(num_col-1); } -void HighsIis::removeCddRow(const HighsInt cdd_row) { - HighsInt num_cdd = this->row_index_.size(); - assert(cdd_row < num_cdd); - this->row_index_[cdd_row] = this->row_index_[num_cdd-1]; - this->row_index_.resize(num_cdd-1); +void HighsIis::removeRow(const HighsInt row) { + HighsInt num_row = this->row_index_.size(); + assert(row < num_row); + this->row_index_[row] = this->row_index_[num_row-1]; + this->row_index_.resize(num_row-1); } bool HighsIis::inconsistentBounds(const HighsLp& lp, const HighsOptions& options) { @@ -167,6 +167,10 @@ HighsStatus HighsIis::getData(const HighsLp& lp, const HighsOptions& options, HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options) { this->invalidate(); const HighsLogOptions& log_options = options.log_options; + const bool row_priority = + options.iis_strategy == kIisStrategyFromRayRowPriority || + options.iis_strategy == kIisStrategyFromLpRowPriority; + if (!row_priority) return HighsStatus::kError; // Initially all columns and rows are candidates for the IIS for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) this->col_index_.push_back(iCol); @@ -177,70 +181,77 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options) { highs.setOptionValue("output_flag", false); HighsStatus status = highs.passModel(lp); assert(status == HighsStatus::kOk); + // Zero the objective + std::vector cost; + cost.assign(lp.num_col_, 0); + status = highs.changeColsCost(0, lp.num_col_-1, cost.data()); + assert(status == HighsStatus::kOk); status = highs.run(); if (status != HighsStatus::kOk) return status; assert(highs.getModelStatus() == HighsModelStatus::kInfeasible); - // Perform row-deletion pass - HighsInt num_row_cdd = this->row_index_.size(); - for (HighsInt iCddRow = 0; iCddRow < num_row_cdd; iCddRow++) { - for (;;) { - HighsInt iRow = this->row_index_[iCddRow]; - const double row_lower = lp.row_lower_[iRow]; - const double row_upper = lp.row_upper_[iRow]; - // Record whether a bound can be dropped: by default it's - // possible, and only not possible if the bound is finite and the - // LP remains infeasible if the bound is dropped - bool drop_lower = true; - bool drop_upper = true; - if (row_lower > -kHighsInf) { - // Drop the lower bound temporarily - status = highs.changeRowBounds(iRow, -kHighsInf, row_upper); - assert(status == HighsStatus::kOk); - status = highs.run(); - assert(status == HighsStatus::kOk); - HighsModelStatus model_status = highs.getModelStatus(); - if (model_status == HighsModelStatus::kOptimal) { - // Now feasible, so restore the lower bound and indicate that - // it cannot be dropped permanently - status = highs.changeRowBounds(iRow, row_lower, row_upper); + if (row_priority) { + // Perform row-deletion pass + HighsInt num_row_cdd = this->row_index_.size(); + for (HighsInt iCddRow = 0; iCddRow < num_row_cdd; iCddRow++) { + for (;;) { + HighsInt iRow = this->row_index_[iCddRow]; + const double row_lower = lp.row_lower_[iRow]; + const double row_upper = lp.row_upper_[iRow]; + // Record whether a bound can be dropped: by default it's + // possible, and only not possible if the bound is finite and the + // LP remains infeasible if the bound is dropped + bool drop_lower = true; + bool drop_upper = true; + if (row_lower > -kHighsInf) { + // Drop the lower bound temporarily + status = highs.changeRowBounds(iRow, -kHighsInf, row_upper); assert(status == HighsStatus::kOk); - drop_lower = false; - } else { - assert(model_status == HighsModelStatus::kInfeasible); - // Bound can be dropped permanently - } - } - if (row_upper < kHighsInf) { - // Drop the upper bound temporarily - status = highs.changeRowBounds(iRow, row_lower, kHighsInf); - assert(status == HighsStatus::kOk); - status = highs.run(); - assert(status == HighsStatus::kOk); - HighsModelStatus model_status = highs.getModelStatus(); - if (model_status == HighsModelStatus::kOptimal) { - // Now feasible, so restore the upper bound and indicate that - // it cannot be dropped permanently - status = highs.changeRowBounds(iRow, row_lower, row_upper); + status = highs.run(); assert(status == HighsStatus::kOk); - drop_upper = false; - } else { - assert(model_status == HighsModelStatus::kInfeasible); - // Bound can be dropped permanently + HighsModelStatus model_status = highs.getModelStatus(); + if (model_status == HighsModelStatus::kOptimal) { + // Now feasible, so restore the lower bound and indicate that + // it cannot be dropped permanently + status = highs.changeRowBounds(iRow, row_lower, row_upper); + assert(status == HighsStatus::kOk); + drop_lower = false; + } else { + assert(model_status == HighsModelStatus::kInfeasible); + // Bound can be dropped permanently + } + } + if (row_upper < kHighsInf) { + // Drop the upper bound temporarily + status = highs.changeRowBounds(iRow, row_lower, kHighsInf); + assert(status == HighsStatus::kOk); + status = highs.run(); + assert(status == HighsStatus::kOk); + HighsModelStatus model_status = highs.getModelStatus(); + if (model_status == HighsModelStatus::kOptimal) { + // Now feasible, so restore the upper bound and indicate that + // it cannot be dropped permanently + status = highs.changeRowBounds(iRow, row_lower, row_upper); + assert(status == HighsStatus::kOk); + drop_upper = false; + } else { + assert(model_status == HighsModelStatus::kInfeasible); + // Bound can be dropped permanently + } } - } - if (drop_lower && drop_upper) { - // Both bounds can be dropped, so remove the row from the set of - // candidates + if (drop_lower && drop_upper) { + // Both bounds can be dropped, so remove the row from the set of + // candidates status = highs.changeRowBounds(iRow, -kHighsInf, kHighsInf); assert(status == HighsStatus::kOk); - this->removeCddRow(iCddRow); - num_row_cdd--; - highsLogUser(log_options, HighsLogType::kInfo, "Dropped row %d from candidate set\n", int(iRow)); - if (iCddRow >= num_row_cdd) break; - } else { - highsLogUser(log_options, HighsLogType::kInfo, "Retained row %d in candidate set\n", int(iRow)); - break; + this->removeRow(iCddRow); + num_row_cdd--; + highsLogUser(log_options, HighsLogType::kInfo, "Dropped row %d from candidate set\n", int(iRow)); + if (iCddRow >= num_row_cdd) break; + } else { + highsLogUser(log_options, HighsLogType::kInfo, "Retained row %d in candidate set\n", int(iRow)); + break; + } } } } diff --git a/src/lp_data/HighsIis.h b/src/lp_data/HighsIis.h index 683638a10e..5ec263fe28 100644 --- a/src/lp_data/HighsIis.h +++ b/src/lp_data/HighsIis.h @@ -21,8 +21,8 @@ class HighsIis { HighsIis() {} void invalidate(); - void removeCddCol(const HighsInt cdd_col); - void removeCddRow(const HighsInt cdd_row); + void removeCol(const HighsInt col); + void removeRow(const HighsInt row); HighsStatus getData(const HighsLp& lp, const HighsOptions& options, const std::vector& dual_ray_value); From f1700d38e7f5bf690ae8069e74c56b559a7a2429 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 5 Jun 2024 17:50:48 +0100 Subject: [PATCH 010/194] Analysis of galenet ray proves instructive --- check/TestIis.cpp | 40 +++++++++++++++++++++++++++++++ src/lp_data/HighsIis.cpp | 51 +++++++++++++++++++++++----------------- 2 files changed, 70 insertions(+), 21 deletions(-) diff --git a/check/TestIis.cpp b/check/TestIis.cpp index 8dd0fe0e97..8f539b9639 100644 --- a/check/TestIis.cpp +++ b/check/TestIis.cpp @@ -179,6 +179,46 @@ TEST_CASE("lp-get-iis", "[iis]") { } TEST_CASE("lp-get-iis-galenet", "[iis]") { + // Dual ray corresponds to constraints + // + // r0: 0 <= c0 + c1 - c3 - c4 <=0 + // + // r1: 20 <= c2 + c3 + // + // r2: 30 <= c4 + // + // Where + // + // 0 <= c0 <= 10 + // + // 0 <= c1 <= 10 + // + // 0 <= c2 <= 2 + // + // 0 <= c3 <= 20 + // + // 0 <= c4 <= 30 + // + // This is infeasible since c4 >= 30 and c4 <= 30 fices c4 = 30, + // then c0 + c1 >= c3 + c4 >= 30 cannot be satisfied due to the + // upper bounds of 10 on these variables + // + // r1 can be removed and infeasibility is retained, but not r0 or r2 + // + // The upper bound on r0 can be removed + // + // The lower bounds on c0 and c1 can be removed, but not their upper + // bounds + // + // c2 can be removed, as it is empty once r1 is removed + // + // c3 can be removed, as the value of c4 is sufficient to make r0 + // infeasible + // + // The bounds on c4 can be removed, since it's the lower bound from + // r2 that makes r0 infeasible + // + // Hence only empty columns can be removed std::string model = "galenet"; testMps(model); } diff --git a/src/lp_data/HighsIis.cpp b/src/lp_data/HighsIis.cpp index 9768896ff6..6d7b739ede 100644 --- a/src/lp_data/HighsIis.cpp +++ b/src/lp_data/HighsIis.cpp @@ -170,7 +170,6 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options) { const bool row_priority = options.iis_strategy == kIisStrategyFromRayRowPriority || options.iis_strategy == kIisStrategyFromLpRowPriority; - if (!row_priority) return HighsStatus::kError; // Initially all columns and rows are candidates for the IIS for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) this->col_index_.push_back(iCol); @@ -181,6 +180,9 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options) { highs.setOptionValue("output_flag", false); HighsStatus status = highs.passModel(lp); assert(status == HighsStatus::kOk); + highs.setOptionValue("output_flag", true); + highs.writeModel(""); + highs.setOptionValue("output_flag", false); // Zero the objective std::vector cost; cost.assign(lp.num_col_, 0); @@ -190,22 +192,25 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options) { if (status != HighsStatus::kOk) return status; assert(highs.getModelStatus() == HighsModelStatus::kInfeasible); - if (row_priority) { - // Perform row-deletion pass - HighsInt num_row_cdd = this->row_index_.size(); - for (HighsInt iCddRow = 0; iCddRow < num_row_cdd; iCddRow++) { + // Pass twice: rows before columns, or columns before rows, according to row_priority + for (HighsInt k = 0; k < 2; k++) { + const bool row_deletion = (row_priority && k == 0) || (!row_priority && k == 1); + std::string type = row_deletion ? "row" : "col"; + // Perform deletion pass + HighsInt num_cdd = row_deletion ? this->row_index_.size() : this->col_index_.size(); + for (HighsInt iCdd = 0; iCdd < num_cdd; iCdd++) { for (;;) { - HighsInt iRow = this->row_index_[iCddRow]; - const double row_lower = lp.row_lower_[iRow]; - const double row_upper = lp.row_upper_[iRow]; + const HighsInt iX = row_deletion ? this->row_index_[iCdd] : this->col_index_[iCdd]; + const double lower = row_deletion ? lp.row_lower_[iX] : lp.col_lower_[iX]; + const double upper = row_deletion ? lp.row_upper_[iX] : lp.col_upper_[iX]; // Record whether a bound can be dropped: by default it's // possible, and only not possible if the bound is finite and the // LP remains infeasible if the bound is dropped bool drop_lower = true; bool drop_upper = true; - if (row_lower > -kHighsInf) { + if (lower > -kHighsInf) { // Drop the lower bound temporarily - status = highs.changeRowBounds(iRow, -kHighsInf, row_upper); + status = row_deletion ? highs.changeRowBounds(iX, -kHighsInf, upper) : highs.changeColBounds(iX, -kHighsInf, upper); assert(status == HighsStatus::kOk); status = highs.run(); assert(status == HighsStatus::kOk); @@ -213,7 +218,7 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options) { if (model_status == HighsModelStatus::kOptimal) { // Now feasible, so restore the lower bound and indicate that // it cannot be dropped permanently - status = highs.changeRowBounds(iRow, row_lower, row_upper); + status = row_deletion ? highs.changeRowBounds(iX, lower, upper) : highs.changeColBounds(iX, lower, upper); assert(status == HighsStatus::kOk); drop_lower = false; } else { @@ -221,9 +226,9 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options) { // Bound can be dropped permanently } } - if (row_upper < kHighsInf) { + if (upper < kHighsInf) { // Drop the upper bound temporarily - status = highs.changeRowBounds(iRow, row_lower, kHighsInf); + status = row_deletion ? highs.changeRowBounds(iX, lower, kHighsInf) : highs.changeColBounds(iX, lower, kHighsInf); assert(status == HighsStatus::kOk); status = highs.run(); assert(status == HighsStatus::kOk); @@ -231,7 +236,7 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options) { if (model_status == HighsModelStatus::kOptimal) { // Now feasible, so restore the upper bound and indicate that // it cannot be dropped permanently - status = highs.changeRowBounds(iRow, row_lower, row_upper); + status = row_deletion ? highs.changeRowBounds(iX, lower, upper) : highs.changeColBounds(iX, lower, upper); assert(status == HighsStatus::kOk); drop_upper = false; } else { @@ -240,16 +245,20 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options) { } } if (drop_lower && drop_upper) { - // Both bounds can be dropped, so remove the row from the set of + // Both bounds can be dropped, so remove from the set of // candidates - status = highs.changeRowBounds(iRow, -kHighsInf, kHighsInf); + status = row_deletion ? highs.changeRowBounds(iX, -kHighsInf, kHighsInf) : highs.changeColBounds(iX, -kHighsInf, kHighsInf); assert(status == HighsStatus::kOk); - this->removeRow(iCddRow); - num_row_cdd--; - highsLogUser(log_options, HighsLogType::kInfo, "Dropped row %d from candidate set\n", int(iRow)); - if (iCddRow >= num_row_cdd) break; + if (row_deletion) { + this->removeRow(iCdd); + } else { + this->removeCol(iCdd); + } + num_cdd--; + highsLogUser(log_options, HighsLogType::kInfo, "Dropped %s %d from candidate set\n", type.c_str(), int(iX)); + if (iCdd >= num_cdd) break; } else { - highsLogUser(log_options, HighsLogType::kInfo, "Retained row %d in candidate set\n", int(iRow)); + highsLogUser(log_options, HighsLogType::kInfo, "Retained %s %d in candidate set\n", type.c_str(), int(iX)); break; } } From a32614672fe520f7f3ccf3d494c2464448886832 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 6 Jun 2024 12:10:28 +0100 Subject: [PATCH 011/194] Many algorithmic changes (simplifications) --- check/TestIis.cpp | 72 ++++++-- src/lp_data/HighsIis.cpp | 300 ++++++++++++++++++++++----------- src/lp_data/HighsIis.h | 12 +- src/lp_data/HighsInterface.cpp | 4 +- 4 files changed, 277 insertions(+), 111 deletions(-) diff --git a/check/TestIis.cpp b/check/TestIis.cpp index 8f539b9639..71d2cb1e4e 100644 --- a/check/TestIis.cpp +++ b/check/TestIis.cpp @@ -106,7 +106,7 @@ TEST_CASE("lp-incompatible-bounds", "[iis]") { std::vector iis_row_bound; highs.setOptionValue("iis_strategy", kIisStrategyFromLpRowPriority); REQUIRE(highs.getIis(num_iis_col, num_iis_row) == HighsStatus::kOk); - REQUIRE(num_iis_col == 2); + REQUIRE(num_iis_col == 0); REQUIRE(num_iis_row == 1); iis_col_index.resize(num_iis_col); iis_row_index.resize(num_iis_row); @@ -114,26 +114,78 @@ TEST_CASE("lp-incompatible-bounds", "[iis]") { iis_row_bound.resize(num_iis_row); REQUIRE(highs.getIis(num_iis_col, num_iis_row, iis_col_index.data(), iis_row_index.data(), - // iis_col_bound.data(), iis_row_bound.data() - nullptr, nullptr) == HighsStatus::kOk); - REQUIRE(iis_col_index[0] == 1); - REQUIRE(iis_col_index[1] == 2); + iis_col_bound.data(), iis_row_bound.data()) == HighsStatus::kOk); REQUIRE(iis_row_index[0] == 0); + REQUIRE(iis_row_bound[0] == kIisBoundStatusBoxed); highs.setOptionValue("iis_strategy", kIisStrategyFromLpColPriority); REQUIRE(highs.getIis(num_iis_col, num_iis_row) == HighsStatus::kOk); REQUIRE(num_iis_col == 1); - REQUIRE(num_iis_row == 2); + REQUIRE(num_iis_row == 0); iis_col_index.resize(num_iis_col); iis_row_index.resize(num_iis_row); iis_col_bound.resize(num_iis_col); iis_row_bound.resize(num_iis_row); REQUIRE(highs.getIis(num_iis_col, num_iis_row, iis_col_index.data(), iis_row_index.data(), - // iis_col_bound.data(), iis_row_bound.data() - nullptr, nullptr) == HighsStatus::kOk); + iis_col_bound.data(), iis_row_bound.data()) == HighsStatus::kOk); REQUIRE(iis_col_index[0] == 2); - REQUIRE(iis_row_index[0] == 0); - REQUIRE(iis_row_index[1] == 1); + REQUIRE(iis_col_bound[0] == kIisBoundStatusBoxed); +} + +TEST_CASE("lp-empty-infeasible-row", "[iis]") { + // Second row is empty, with bounds of [1, 2] + const HighsInt empty_row = 1; + HighsLp lp; + lp.num_col_ = 2; + lp.num_row_ = 3; + lp.col_cost_ = {0, 0}; + lp.col_lower_ = {0, 0}; + lp.col_upper_ = {inf, inf}; + lp.row_lower_ = {-inf, 1, -inf}; + lp.row_upper_ = {8, 2, 9}; + lp.a_matrix_.format_ = MatrixFormat::kRowwise; + lp.a_matrix_.start_ = {0, 2, 2, 4}; + lp.a_matrix_.index_ = {0, 1, 0, 1}; + lp.a_matrix_.value_ = {2, 1, 1, 3}; + Highs highs; + highs.setOptionValue("output_flag", dev_run); + highs.passModel(lp); + REQUIRE(highs.run() == HighsStatus::kOk); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); + HighsInt num_iis_col; + HighsInt num_iis_row; + std::vector iis_col_index; + std::vector iis_row_index; + std::vector iis_col_bound; + std::vector iis_row_bound; + REQUIRE(highs.getIis(num_iis_col, num_iis_row) == HighsStatus::kOk); + REQUIRE(num_iis_col == 0); + REQUIRE(num_iis_row == 1); + iis_col_index.resize(num_iis_col); + iis_row_index.resize(num_iis_row); + iis_col_bound.resize(num_iis_col); + iis_row_bound.resize(num_iis_row); + REQUIRE(highs.getIis(num_iis_col, num_iis_row, iis_col_index.data(), + iis_row_index.data(), + iis_col_bound.data(), iis_row_bound.data()) == HighsStatus::kOk); + REQUIRE(iis_row_index[0] == empty_row); + REQUIRE(iis_row_bound[0] == kIisBoundStatusLower); + REQUIRE(highs.changeRowBounds(empty_row, -2, -1) == HighsStatus::kOk); + REQUIRE(highs.run() == HighsStatus::kOk); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); + REQUIRE(highs.getIis(num_iis_col, num_iis_row) == HighsStatus::kOk); + REQUIRE(num_iis_col == 0); + REQUIRE(num_iis_row == 1); + iis_col_index.resize(num_iis_col); + iis_row_index.resize(num_iis_row); + iis_col_bound.resize(num_iis_col); + iis_row_bound.resize(num_iis_row); + REQUIRE(highs.getIis(num_iis_col, num_iis_row, iis_col_index.data(), + iis_row_index.data(), + iis_col_bound.data(), iis_row_bound.data()) == HighsStatus::kOk); + REQUIRE(iis_row_index[0] == empty_row); + REQUIRE(iis_row_bound[0] == kIisBoundStatusUpper); + } TEST_CASE("lp-get-iis", "[iis]") { diff --git a/src/lp_data/HighsIis.cpp b/src/lp_data/HighsIis.cpp index 6d7b739ede..e8f86ecb66 100644 --- a/src/lp_data/HighsIis.cpp +++ b/src/lp_data/HighsIis.cpp @@ -25,6 +25,16 @@ void HighsIis::invalidate() { this->row_bound_.clear(); } +void HighsIis::addCol(const HighsInt col, const HighsInt status) { + this->col_index_.push_back(col); + this->col_bound_.push_back(status); +} + +void HighsIis::addRow(const HighsInt row, const HighsInt status) { + this->row_index_.push_back(row); + this->row_bound_.push_back(status); +} + void HighsIis::removeCol(const HighsInt col) { HighsInt num_col = this->col_index_.size(); assert(col < num_col); @@ -39,7 +49,7 @@ void HighsIis::removeRow(const HighsInt row) { this->row_index_.resize(num_row-1); } -bool HighsIis::inconsistentBounds(const HighsLp& lp, const HighsOptions& options) { +bool HighsIis::trivial(const HighsLp& lp, const HighsOptions& options) { this->invalidate(); const bool col_priority = options.iis_strategy == kIisStrategyFromRayColPriority || @@ -50,7 +60,7 @@ bool HighsIis::inconsistentBounds(const HighsLp& lp, const HighsOptions& options for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { if (lp.col_lower_[iCol] - lp.col_upper_[iCol] > 2 * options.primal_feasibility_tolerance) { - this->col_index_.push_back(iCol); + this->addCol(iCol, kIisBoundStatusBoxed); break; } } @@ -60,7 +70,7 @@ bool HighsIis::inconsistentBounds(const HighsLp& lp, const HighsOptions& options for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { if (lp.row_lower_[iRow] - lp.row_upper_[iRow] > 2 * options.primal_feasibility_tolerance) { - this->row_index_.push_back(iRow); + this->addRow(iRow, kIisBoundStatusBoxed); break; } } @@ -69,38 +79,44 @@ bool HighsIis::inconsistentBounds(const HighsLp& lp, const HighsOptions& options } HighsInt num_iis_col = this->col_index_.size(); HighsInt num_iis_row = this->row_index_.size(); - // If none found then return false - if (num_iis_col + num_iis_row == 0) return false; - // Should have found exactly 1 - assert((num_iis_col == 1 || num_iis_row == 1) && - num_iis_col + num_iis_row < 2); - assert(lp.a_matrix_.isColwise()); - if (num_iis_col > 0) { - // Found inconsistent column - HighsInt iCol = this->col_index_[0]; + // If one is found then we're done + if (num_iis_col + num_iis_row > 0) { + // Should have found exactly 1 + assert((num_iis_col == 1 || num_iis_row == 1) && + num_iis_col + num_iis_row < 2); + this->valid_ = true; + this->strategy_ = options.iis_strategy; + return true; + } + // Now look for empty rows that cannot have zero activity + std::vector count; + count.assign(lp.num_row_, 0); + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { for (HighsInt iEl = lp.a_matrix_.start_[iCol]; - iEl < lp.a_matrix_.start_[iCol + 1]; iEl++) - this->row_index_.push_back(lp.a_matrix_.index_[iEl]); - - } else { - // Found inconsistent row - HighsInt iRow = this->row_index_[0]; - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { - for (HighsInt iEl = lp.a_matrix_.start_[iCol]; - iEl < lp.a_matrix_.start_[iCol + 1]; iEl++) - if (lp.a_matrix_.index_[iEl] == iRow) this->col_index_.push_back(iCol); + iEl < lp.a_matrix_.start_[iCol + 1]; iEl++) + count[lp.a_matrix_.index_[iEl]]++; + } + assert(this->row_index_.size() == 0); + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { + if (count[iRow] > 0) continue; + if (lp.row_lower_[iRow] > options.primal_feasibility_tolerance) { + this->addRow(iRow, kIisBoundStatusLower); + } else if (lp.row_upper_[iRow] < -options.primal_feasibility_tolerance) { + this->addRow(iRow, kIisBoundStatusUpper); + } + if (this->row_index_.size() > 0) { + this->valid_ = true; + this->strategy_ = options.iis_strategy; + return true; } } - this->valid_ = true; - this->strategy_ = options.iis_strategy; - return true; + return false; } HighsStatus HighsIis::getData(const HighsLp& lp, const HighsOptions& options, const std::vector& dual_ray_value) { - // Check for inconsistent column and row bounds should have been - // done earlier - assert(!this->inconsistentBounds(lp, options)); + // Check for trivial IIS should have been done earlier + assert(!this->trivial(lp, options)); if (options.iis_strategy == kIisStrategyFromRayRowPriority || options.iis_strategy == kIisStrategyFromRayColPriority) { @@ -171,25 +187,23 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options) { options.iis_strategy == kIisStrategyFromRayRowPriority || options.iis_strategy == kIisStrategyFromLpRowPriority; // Initially all columns and rows are candidates for the IIS - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) - this->col_index_.push_back(iCol); - for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) - this->row_index_.push_back(iRow); + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) this->addCol(iCol); + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) this->addRow(iRow); Highs highs; highs.setOptionValue("presolve", kHighsOffString); highs.setOptionValue("output_flag", false); - HighsStatus status = highs.passModel(lp); - assert(status == HighsStatus::kOk); + HighsStatus run_status = highs.passModel(lp); + assert(run_status == HighsStatus::kOk); highs.setOptionValue("output_flag", true); highs.writeModel(""); highs.setOptionValue("output_flag", false); // Zero the objective std::vector cost; cost.assign(lp.num_col_, 0); - status = highs.changeColsCost(0, lp.num_col_-1, cost.data()); - assert(status == HighsStatus::kOk); - status = highs.run(); - if (status != HighsStatus::kOk) return status; + run_status = highs.changeColsCost(0, lp.num_col_-1, cost.data()); + assert(run_status == HighsStatus::kOk); + run_status = highs.run(); + if (run_status != HighsStatus::kOk) return run_status; assert(highs.getModelStatus() == HighsModelStatus::kInfeasible); // Pass twice: rows before columns, or columns before rows, according to row_priority @@ -197,73 +211,163 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options) { const bool row_deletion = (row_priority && k == 0) || (!row_priority && k == 1); std::string type = row_deletion ? "row" : "col"; // Perform deletion pass - HighsInt num_cdd = row_deletion ? this->row_index_.size() : this->col_index_.size(); - for (HighsInt iCdd = 0; iCdd < num_cdd; iCdd++) { - for (;;) { - const HighsInt iX = row_deletion ? this->row_index_[iCdd] : this->col_index_[iCdd]; - const double lower = row_deletion ? lp.row_lower_[iX] : lp.col_lower_[iX]; - const double upper = row_deletion ? lp.row_upper_[iX] : lp.col_upper_[iX]; - // Record whether a bound can be dropped: by default it's - // possible, and only not possible if the bound is finite and the - // LP remains infeasible if the bound is dropped - bool drop_lower = true; - bool drop_upper = true; - if (lower > -kHighsInf) { - // Drop the lower bound temporarily - status = row_deletion ? highs.changeRowBounds(iX, -kHighsInf, upper) : highs.changeColBounds(iX, -kHighsInf, upper); - assert(status == HighsStatus::kOk); - status = highs.run(); - assert(status == HighsStatus::kOk); - HighsModelStatus model_status = highs.getModelStatus(); - if (model_status == HighsModelStatus::kOptimal) { - // Now feasible, so restore the lower bound and indicate that - // it cannot be dropped permanently - status = row_deletion ? highs.changeRowBounds(iX, lower, upper) : highs.changeColBounds(iX, lower, upper); - assert(status == HighsStatus::kOk); - drop_lower = false; - } else { - assert(model_status == HighsModelStatus::kInfeasible); - // Bound can be dropped permanently - } - } - if (upper < kHighsInf) { - // Drop the upper bound temporarily - status = row_deletion ? highs.changeRowBounds(iX, lower, kHighsInf) : highs.changeColBounds(iX, lower, kHighsInf); - assert(status == HighsStatus::kOk); - status = highs.run(); - assert(status == HighsStatus::kOk); - HighsModelStatus model_status = highs.getModelStatus(); - if (model_status == HighsModelStatus::kOptimal) { - // Now feasible, so restore the upper bound and indicate that - // it cannot be dropped permanently - status = row_deletion ? highs.changeRowBounds(iX, lower, upper) : highs.changeColBounds(iX, lower, upper); - assert(status == HighsStatus::kOk); - drop_upper = false; - } else { - assert(model_status == HighsModelStatus::kInfeasible); - // Bound can be dropped permanently + HighsInt num_index = row_deletion ? lp.num_row_ : lp.num_col_; + for (HighsInt iX = 0; iX < num_index; iX++) { + const HighsInt ix_status = row_deletion ? this->row_bound_[iX] : this->col_bound_[iX]; + if (ix_status == kIisBoundStatusFree) continue; + double lower = row_deletion ? lp.row_lower_[iX] : lp.col_lower_[iX]; + double upper = row_deletion ? lp.row_upper_[iX] : lp.col_upper_[iX]; + // Record whether the upper bound has been dropped due to the lower bound being kept + bool drop_upper = false; + if (lower > -kHighsInf) { + // Drop the lower bound temporarily + run_status = row_deletion ? highs.changeRowBounds(iX, -kHighsInf, upper) : highs.changeColBounds(iX, -kHighsInf, upper); + assert(run_status == HighsStatus::kOk); + run_status = highs.run(); + assert(run_status == HighsStatus::kOk); + HighsModelStatus model_status = highs.getModelStatus(); + if (model_status == HighsModelStatus::kOptimal) { + // Now feasible, so restore the lower bound + run_status = row_deletion ? highs.changeRowBounds(iX, lower, upper) : highs.changeColBounds(iX, lower, upper); + assert(run_status == HighsStatus::kOk); + // If the lower bound must be kept, then any finite upper bound + // must be dropped + const bool apply_reciprocal_rule = false; + if (apply_reciprocal_rule) { + if (upper < kHighsInf) { + // Drop the upper bound permanently + upper = kHighsInf; + run_status = row_deletion ? highs.changeRowBounds(iX, lower, kHighsInf) : highs.changeColBounds(iX, lower, upper); + assert(run_status == HighsStatus::kOk); + drop_upper = true; + } + // continue; } + } else { + // Bound can be dropped permanently + assert(model_status == HighsModelStatus::kInfeasible); + lower = -kHighsInf; + } + } + if (upper < kHighsInf) { + // Drop the upper bound temporarily + run_status = row_deletion ? highs.changeRowBounds(iX, lower, kHighsInf) : highs.changeColBounds(iX, lower, kHighsInf); + assert(run_status == HighsStatus::kOk); + run_status = highs.run(); + assert(run_status == HighsStatus::kOk); + HighsModelStatus model_status = highs.getModelStatus(); + // If the upper bound has been dropped due to the reciprical + // rule, the LP must be infeasible + if (drop_upper) assert(model_status == HighsModelStatus::kInfeasible); + if (model_status == HighsModelStatus::kOptimal) { + // Now feasible, so restore the upper bound + run_status = row_deletion ? highs.changeRowBounds(iX, lower, upper) : highs.changeColBounds(iX, lower, upper); + assert(run_status == HighsStatus::kOk); + } else { + // Bound can be dropped permanently + assert(model_status == HighsModelStatus::kInfeasible); + upper = kHighsInf; + } + } + const bool debug_bound_change = true; + if (debug_bound_change) { + // Check bounds have been changed correctly + double check_lower; + double check_upper; + double check_cost; + HighsInt check_num_ix; + HighsInt check_num_nz; + run_status = row_deletion ? + highs.getRows(iX, iX, check_num_ix, &check_lower, &check_upper, check_num_nz, nullptr, nullptr, nullptr) : + highs.getCols(iX, iX, check_num_ix, &check_cost, &check_lower, &check_upper, check_num_nz, nullptr, nullptr, nullptr); + assert(run_status == HighsStatus::kOk); + assert(check_lower == lower); + assert(check_upper == upper); + } + HighsInt iss_bound_status = kIisBoundStatusNull; + if (lower <= -kHighsInf) { + if (upper >= kHighsInf) { + iss_bound_status = kIisBoundStatusFree; + } else { + iss_bound_status = kIisBoundStatusUpper; } - if (drop_lower && drop_upper) { - // Both bounds can be dropped, so remove from the set of - // candidates - status = row_deletion ? highs.changeRowBounds(iX, -kHighsInf, kHighsInf) : highs.changeColBounds(iX, -kHighsInf, kHighsInf); - assert(status == HighsStatus::kOk); - if (row_deletion) { - this->removeRow(iCdd); - } else { - this->removeCol(iCdd); - } - num_cdd--; - highsLogUser(log_options, HighsLogType::kInfo, "Dropped %s %d from candidate set\n", type.c_str(), int(iX)); - if (iCdd >= num_cdd) break; + } else { + if (upper >= kHighsInf) { + iss_bound_status = kIisBoundStatusLower; } else { - highsLogUser(log_options, HighsLogType::kInfo, "Retained %s %d in candidate set\n", type.c_str(), int(iX)); - break; + // FX or BX: shouldn't happen + iss_bound_status = kIisBoundStatusBoxed; + } + } + assert(iss_bound_status != kIisBoundStatusNull); + assert(iss_bound_status != kIisBoundStatusBoxed); + if (row_deletion) { + this->row_bound_[iX] = iss_bound_status; + } else { + this->col_bound_[iX] = iss_bound_status; + } + if (iss_bound_status == kIisBoundStatusFree) { + highsLogUser(log_options, HighsLogType::kInfo, "Dropped %s %d from candidate set\n", type.c_str(), int(iX)); + } else { + highsLogUser(log_options, HighsLogType::kInfo, "Retained %s %d in candidate set\n", type.c_str(), int(iX)); + } + } + if (k == 1) continue; + // End of first pass: look to simplify second pass + if (row_deletion) { + // Mark empty columns as free + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + bool empty_col = false; + for (HighsInt iEl = lp.a_matrix_.start_[iCol]; + iEl < lp.a_matrix_.start_[iCol + 1]; iEl++) { + if (this->row_bound_[lp.a_matrix_.index_[iEl]] != kIisBoundStatusFree) { + empty_col = true; + break; + } } + if (empty_col) this->col_bound_[iCol] = kIisBoundStatusFree; } + } else { + // Look for empty rows - which should be feasible for zero activity - and mark them as free + std::vector col_count; + col_count.assign(lp.num_row_, 0); + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + for (HighsInt iEl = lp.a_matrix_.start_[iCol]; + iEl < lp.a_matrix_.start_[iCol + 1]; iEl++) { + HighsInt iRow = lp.a_matrix_.index_[iEl]; + if (this->row_bound_[iRow] != kIisBoundStatusFree) col_count[iRow]++; + } + } + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { + if (col_count[iRow] > 0) continue; + double lower = lp.row_lower_[iRow]; + double upper = lp.row_upper_[iRow]; + bool trivially_feasible = !(lower > options.primal_feasibility_tolerance && upper < -options.primal_feasibility_tolerance); + assert(trivially_feasible); + this->row_bound_[iRow] = kIisBoundStatusFree; + } + } + } + HighsInt iss_num_col = 0; + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + if (this->col_bound_[iCol] != kIisBoundStatusFree) { + this->col_index_[iss_num_col] = this->col_index_[iCol]; + this->col_bound_[iss_num_col] = this->col_bound_[iCol]; + iss_num_col++; + } + } + this->col_index_.resize(iss_num_col); + this->col_bound_.resize(iss_num_col); + HighsInt iss_num_row = 0; + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { + if (this->row_bound_[iRow] != kIisBoundStatusFree) { + this->row_index_[iss_num_row] = this->row_index_[iRow]; + this->row_bound_[iss_num_row] = this->row_bound_[iRow]; + iss_num_row++; } } + this->row_index_.resize(iss_num_row); + this->row_bound_.resize(iss_num_row); this->valid_ = true; this->strategy_ = options.iis_strategy; return HighsStatus::kOk; diff --git a/src/lp_data/HighsIis.h b/src/lp_data/HighsIis.h index 5ec263fe28..bd66f12b00 100644 --- a/src/lp_data/HighsIis.h +++ b/src/lp_data/HighsIis.h @@ -16,11 +16,21 @@ #include "lp_data/HighsLp.h" +enum IisBoundStatus { + kIisBoundStatusNull = 0, + kIisBoundStatusFree, // 1 + kIisBoundStatusLower, // 2 + kIisBoundStatusUpper, // 3 + kIisBoundStatusBoxed // 4 +}; + class HighsIis { public: HighsIis() {} void invalidate(); + void addCol(const HighsInt col, const HighsInt status = kIisBoundStatusNull); + void addRow(const HighsInt row, const HighsInt status = kIisBoundStatusNull); void removeCol(const HighsInt col); void removeRow(const HighsInt row); HighsStatus getData(const HighsLp& lp, const HighsOptions& options, @@ -28,7 +38,7 @@ class HighsIis { HighsStatus compute(const HighsLp& lp, const HighsOptions& options); - bool inconsistentBounds(const HighsLp& lp, const HighsOptions& options); + bool trivial(const HighsLp& lp, const HighsOptions& options); // Data members bool valid_ = false; diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 57a83f0715..aee713b2d6 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -1552,8 +1552,8 @@ HighsStatus Highs::getRangingInterface() { HighsStatus Highs::getIisInterface() { if (this->iis_.valid_) return HighsStatus::kOk; HighsLp& lp = model_.lp_; - // Check for inconsistent column and row bounds - if (this->iis_.inconsistentBounds(lp, options_)) return HighsStatus::kOk; + // Check for trivial IIS: empty infeasible row or inconsistent bounds + if (this->iis_.trivial(lp, options_)) return HighsStatus::kOk; HighsInt num_row = lp.num_row_; // For an LP with no rows the dual ray is vacuous if (num_row == 0) return HighsStatus::kOk; From aefdb60aa57fd03c5b3c8c1e17a8e3a01b52bdd2 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 6 Jun 2024 15:35:10 +0100 Subject: [PATCH 012/194] Many algorithmic changes (simplifications) --- src/lp_data/HighsIis.cpp | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/src/lp_data/HighsIis.cpp b/src/lp_data/HighsIis.cpp index e8f86ecb66..82a7d3221f 100644 --- a/src/lp_data/HighsIis.cpp +++ b/src/lp_data/HighsIis.cpp @@ -25,6 +25,29 @@ void HighsIis::invalidate() { this->row_bound_.clear(); } +std::string iisBoundStatusToString(HighsInt bound_status) { + if (bound_status == kIisBoundStatusNull) return " Null"; + if (bound_status == kIisBoundStatusFree) return " Free"; + if (bound_status == kIisBoundStatusLower) return "Lower"; + if (bound_status == kIisBoundStatusUpper) return "Upper"; + if (bound_status == kIisBoundStatusBoxed) return "Boxed"; + return "*****"; +} + +void HighsIis::report(const HighsLp& lp) { + printf("\nIIS\n===\n"); + printf("Status: "); + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + printf("%9s ", iisBoundStatusToString(this->col_bound_[iCol]).c_str()); + printf("\nLower: "); + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + printf("%9.2g ", lp.col_lower_[iCol]); + printf("\nUpper: "); + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + printf("%9.2g ", lp.col_lower_[iCol]); + printf("\n"); +} + void HighsIis::addCol(const HighsInt col, const HighsInt status) { this->col_index_.push_back(col); this->col_bound_.push_back(status); @@ -190,13 +213,17 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options) { for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) this->addCol(iCol); for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) this->addRow(iRow); Highs highs; + const HighsLp& incumbent_lp; highs.setOptionValue("presolve", kHighsOffString); highs.setOptionValue("output_flag", false); HighsStatus run_status = highs.passModel(lp); assert(run_status == HighsStatus::kOk); - highs.setOptionValue("output_flag", true); - highs.writeModel(""); - highs.setOptionValue("output_flag", false); + const bool write_model = false; + if (write_model) { + highs.setOptionValue("output_flag", true); + highs.writeModel(""); + highs.setOptionValue("output_flag", false); + } // Zero the objective std::vector cost; cost.assign(lp.num_col_, 0); @@ -314,6 +341,7 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options) { } if (k == 1) continue; // End of first pass: look to simplify second pass + this->report(incumbent_lp); if (row_deletion) { // Mark empty columns as free for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { @@ -356,8 +384,6 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options) { iss_num_col++; } } - this->col_index_.resize(iss_num_col); - this->col_bound_.resize(iss_num_col); HighsInt iss_num_row = 0; for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { if (this->row_bound_[iRow] != kIisBoundStatusFree) { @@ -366,6 +392,8 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options) { iss_num_row++; } } + this->col_index_.resize(iss_num_col); + this->col_bound_.resize(iss_num_col); this->row_index_.resize(iss_num_row); this->row_bound_.resize(iss_num_row); this->valid_ = true; From 654fd2a62198f7846cb5da5b94a3934b240b2753 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 6 Jun 2024 17:56:03 +0100 Subject: [PATCH 013/194] Where do LPs differ? --- check/TestIis.cpp | 74 +++++++++++++++++++++++----- src/lp_data/HighsIis.cpp | 101 ++++++++++++++++++++------------------- src/lp_data/HighsIis.h | 4 +- 3 files changed, 117 insertions(+), 62 deletions(-) diff --git a/check/TestIis.cpp b/check/TestIis.cpp index 71d2cb1e4e..8f9a59b6f5 100644 --- a/check/TestIis.cpp +++ b/check/TestIis.cpp @@ -12,38 +12,88 @@ const double inf = kHighsInf; void testIis(std::string& model, HighsInt& num_iis_col, HighsInt& num_iis_row, HighsInt* iis_col_index, - HighsInt* iis_row_index) { + HighsInt* iis_row_index, + HighsInt* iis_col_bound, + HighsInt* iis_row_bound) { std::string model_file = std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; Highs highs; highs.setOptionValue("output_flag", false); + highs.setOptionValue("threads", 1); REQUIRE(highs.readModel(model_file) == HighsStatus::kOk); + const HighsLp& incumbent_lp = highs.getLp(); HighsLp lp = highs.getLp(); // Zero the objective lp.col_cost_.assign(lp.num_col_, 0); REQUIRE(highs.changeColsCost(0, lp.num_col_-1, lp.col_cost_.data()) == HighsStatus::kOk); + highs.run(); + lp = highs.getLp(); for (HighsInt iisRow = 0; iisRow < num_iis_row; iisRow++) { HighsInt iRow = iis_row_index[iisRow]; + HighsInt iis_bound = iis_row_bound[iisRow]; const double lower = lp.row_lower_[iRow]; const double upper = lp.row_upper_[iRow]; - REQUIRE(highs.changeRowBounds(iRow, -kHighsInf, kHighsInf) == HighsStatus::kOk); + double to_lower = lower; + double to_upper = upper; + REQUIRE(iis_bound != kIisBoundStatusDropped); + REQUIRE(iis_bound != kIisBoundStatusNull); + REQUIRE(iis_bound != kIisBoundStatusFree); + REQUIRE(iis_bound != kIisBoundStatusBoxed); + if (iis_bound == kIisBoundStatusLower) { + to_lower = -inf; + } else if (iis_bound == kIisBoundStatusUpper) { + to_upper = inf; + } + REQUIRE(highs.changeRowBounds(iRow, to_lower, to_upper) == HighsStatus::kOk); REQUIRE(highs.run() == HighsStatus::kOk); - REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal); - REQUIRE(highs.changeRowBounds(iRow, lower, upper) == HighsStatus::kOk); + HighsModelStatus model_status = highs.getModelStatus(); if (dev_run) - printf("Removing IIS Row %d (LP row %d) yields optimality\n", int(iisRow), int(iRow)); + printf("Removing IIS Row %d (LP row %d) status %d yields model status %s\n", + int(iisRow), int(iRow), int(iis_bound), highs.modelStatusToString(model_status).c_str()); + if (model_status != HighsModelStatus::kOptimal) + highs.writeSolution("", kSolutionStylePretty); + REQUIRE(model_status == HighsModelStatus::kOptimal); + REQUIRE(highs.changeRowBounds(iRow, lower, upper) == HighsStatus::kOk); } + REQUIRE(lp == incumbent_lp); for (HighsInt iisCol = 0; iisCol < num_iis_col; iisCol++) { HighsInt iCol = iis_col_index[iisCol]; + HighsInt iis_bound = iis_col_bound[iisCol]; const double lower = lp.col_lower_[iCol]; const double upper = lp.col_upper_[iCol]; - REQUIRE(highs.changeColBounds(iCol, -kHighsInf, kHighsInf) == HighsStatus::kOk); + double to_lower = lower; + double to_upper = upper; + REQUIRE(iis_bound != kIisBoundStatusDropped); + REQUIRE(iis_bound != kIisBoundStatusNull); + REQUIRE(iis_bound != kIisBoundStatusBoxed); + if (iis_bound == kIisBoundStatusLower) { + to_lower = -inf; + } else if (iis_bound == kIisBoundStatusUpper) { + to_upper = inf; + } else if (iis_bound == kIisBoundStatusFree) { + continue; + } + REQUIRE(highs.changeColBounds(iCol, to_lower, to_upper) == HighsStatus::kOk); REQUIRE(highs.run() == HighsStatus::kOk); - REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal); - REQUIRE(highs.changeColBounds(iCol, lower, upper) == HighsStatus::kOk); + HighsModelStatus model_status = highs.getModelStatus(); if (dev_run) - printf("Removing IIS Col %d (LP row %d) yields optimality\n", int(iisCol), int(iCol)); + printf("Removing IIS Col %d (LP col %d) status %d yields model status %s\n", + int(iisCol), int(iCol), int(iis_bound), highs.modelStatusToString(model_status).c_str()); + /* + if (model_status != HighsModelStatus::kOptimal) { + highs.writeSolution("", kSolutionStylePretty); + Highs ck_highs; + ck_highs.setOptionValue("threads", 1); + ck_highs.passModel(incumbent_lp); + HighsInt ck_num_iis_col; + HighsInt ck_num_iis_row; + ck_highs.run(); + ck_highs.getIis(num_iis_col, num_iis_row, nullptr, nullptr, nullptr, nullptr); + } + */ + REQUIRE(model_status == HighsModelStatus::kOptimal); + REQUIRE(highs.changeColBounds(iCol, lower, upper) == HighsStatus::kOk); } } @@ -52,6 +102,7 @@ void testMps(std::string& model) { std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; Highs highs; highs.setOptionValue("output_flag", dev_run); + highs.setOptionValue("threads", 1); HighsInt num_iis_col; HighsInt num_iis_row; @@ -68,11 +119,10 @@ void testMps(std::string& model) { std::vector iis_row_bound(num_iis_row); REQUIRE(highs.getIis(num_iis_col, num_iis_row, iis_col_index.data(), iis_row_index.data(), - // iis_col_bound.data(), iis_row_bound.data() - nullptr, nullptr) == HighsStatus::kOk); + iis_col_bound.data(), iis_row_bound.data()) == HighsStatus::kOk); if (dev_run) printf("Model %s has IIS with %d columns and %d rows\n", model.c_str(), int(num_iis_col), int(num_iis_row)); - testIis(model, num_iis_col, num_iis_row, iis_col_index.data(), iis_row_index.data()); + testIis(model, num_iis_col, num_iis_row, iis_col_index.data(), iis_row_index.data(), iis_col_bound.data(), iis_row_bound.data()); } TEST_CASE("lp-incompatible-bounds", "[iis]") { diff --git a/src/lp_data/HighsIis.cpp b/src/lp_data/HighsIis.cpp index 82a7d3221f..2687b1ea04 100644 --- a/src/lp_data/HighsIis.cpp +++ b/src/lp_data/HighsIis.cpp @@ -26,25 +26,35 @@ void HighsIis::invalidate() { } std::string iisBoundStatusToString(HighsInt bound_status) { - if (bound_status == kIisBoundStatusNull) return " Null"; - if (bound_status == kIisBoundStatusFree) return " Free"; - if (bound_status == kIisBoundStatusLower) return "Lower"; - if (bound_status == kIisBoundStatusUpper) return "Upper"; - if (bound_status == kIisBoundStatusBoxed) return "Boxed"; + if (bound_status == kIisBoundStatusDropped) return "Dropped"; + if (bound_status == kIisBoundStatusNull) return " Null"; + if (bound_status == kIisBoundStatusFree) return " Free"; + if (bound_status == kIisBoundStatusLower) return " Lower"; + if (bound_status == kIisBoundStatusUpper) return " Upper"; + if (bound_status == kIisBoundStatusBoxed) return " Boxed"; return "*****"; } -void HighsIis::report(const HighsLp& lp) { - printf("\nIIS\n===\n"); - printf("Status: "); +void HighsIis::report(const std::string message, const HighsLp& lp) { + printf("\nIIS %s\n===\n", message.c_str()); + printf("Column: "); + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + printf("%9d ", iCol); + printf("\nStatus: "); for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) printf("%9s ", iisBoundStatusToString(this->col_bound_[iCol]).c_str()); - printf("\nLower: "); + printf("\nLower: "); for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) printf("%9.2g ", lp.col_lower_[iCol]); - printf("\nUpper: "); + printf("\nUpper: "); for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) - printf("%9.2g ", lp.col_lower_[iCol]); + printf("%9.2g ", lp.col_upper_[iCol]); + printf("\n"); + printf("Row: Status Lower Upper\n"); + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) + printf("%2d %9s %9.2g %9.2g\n", int(iRow), + iisBoundStatusToString(this->row_bound_[iRow]).c_str(), + lp.row_lower_[iRow], lp.row_upper_[iRow]); printf("\n"); } @@ -193,9 +203,9 @@ HighsStatus HighsIis::getData(const HighsLp& lp, const HighsOptions& options, // IIS col/row information is for to_lp, so indirect the values // into the original LP for (HighsInt iCol = 0; iCol < HighsInt(this->col_index_.size()); iCol++) - this->col_index_[iCol] = from_col[iCol]; + this->col_index_[iCol] = from_col[this->col_index_[iCol]]; for (HighsInt iRow = 0; iRow < HighsInt(this->row_index_.size()); iRow++) - this->row_index_[iRow] = from_row[iRow]; + this->row_index_[iRow] = from_row[this->row_index_[iRow]]; } else { // Use the whole LP if (this->compute(lp, options) != HighsStatus::kOk) return HighsStatus::kError; @@ -213,9 +223,10 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options) { for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) this->addCol(iCol); for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) this->addRow(iRow); Highs highs; - const HighsLp& incumbent_lp; - highs.setOptionValue("presolve", kHighsOffString); highs.setOptionValue("output_flag", false); + highs.setOptionValue("threads", 1); + highs.setOptionValue("presolve", kHighsOffString); + const HighsLp& incumbent_lp = highs.getLp(); HighsStatus run_status = highs.passModel(lp); assert(run_status == HighsStatus::kOk); const bool write_model = false; @@ -230,18 +241,19 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options) { run_status = highs.changeColsCost(0, lp.num_col_-1, cost.data()); assert(run_status == HighsStatus::kOk); run_status = highs.run(); + assert(run_status == HighsStatus::kOk); if (run_status != HighsStatus::kOk) return run_status; assert(highs.getModelStatus() == HighsModelStatus::kInfeasible); // Pass twice: rows before columns, or columns before rows, according to row_priority for (HighsInt k = 0; k < 2; k++) { const bool row_deletion = (row_priority && k == 0) || (!row_priority && k == 1); - std::string type = row_deletion ? "row" : "col"; + std::string type = row_deletion ? "Row" : "Col"; // Perform deletion pass HighsInt num_index = row_deletion ? lp.num_row_ : lp.num_col_; for (HighsInt iX = 0; iX < num_index; iX++) { const HighsInt ix_status = row_deletion ? this->row_bound_[iX] : this->col_bound_[iX]; - if (ix_status == kIisBoundStatusFree) continue; + if (ix_status == kIisBoundStatusDropped || ix_status == kIisBoundStatusFree) continue; double lower = row_deletion ? lp.row_lower_[iX] : lp.col_lower_[iX]; double upper = row_deletion ? lp.row_upper_[iX] : lp.col_upper_[iX]; // Record whether the upper bound has been dropped due to the lower bound being kept @@ -314,7 +326,13 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options) { HighsInt iss_bound_status = kIisBoundStatusNull; if (lower <= -kHighsInf) { if (upper >= kHighsInf) { - iss_bound_status = kIisBoundStatusFree; + if (row_deletion) { + // Free rows can be dropped + iss_bound_status = kIisBoundStatusDropped; + } else { + // Free columns can only be dropped if they are empty + iss_bound_status = kIisBoundStatusFree; + } } else { iss_bound_status = kIisBoundStatusUpper; } @@ -333,52 +351,37 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options) { } else { this->col_bound_[iX] = iss_bound_status; } - if (iss_bound_status == kIisBoundStatusFree) { - highsLogUser(log_options, HighsLogType::kInfo, "Dropped %s %d from candidate set\n", type.c_str(), int(iX)); - } else { - highsLogUser(log_options, HighsLogType::kInfo, "Retained %s %d in candidate set\n", type.c_str(), int(iX)); - } + highsLogUser(log_options, HighsLogType::kInfo, "%s %d has status %s\n", + type.c_str(), int(iX), iisBoundStatusToString(iss_bound_status).c_str()); } if (k == 1) continue; // End of first pass: look to simplify second pass - this->report(incumbent_lp); + this->report("End of deletion", incumbent_lp); if (row_deletion) { - // Mark empty columns as free + // Mark empty columns as dropped for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { - bool empty_col = false; + bool empty_col = true; for (HighsInt iEl = lp.a_matrix_.start_[iCol]; iEl < lp.a_matrix_.start_[iCol + 1]; iEl++) { - if (this->row_bound_[lp.a_matrix_.index_[iEl]] != kIisBoundStatusFree) { - empty_col = true; + if (this->row_bound_[lp.a_matrix_.index_[iEl]] != kIisBoundStatusDropped) { + empty_col = false; break; } } - if (empty_col) this->col_bound_[iCol] = kIisBoundStatusFree; - } - } else { - // Look for empty rows - which should be feasible for zero activity - and mark them as free - std::vector col_count; - col_count.assign(lp.num_row_, 0); - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { - for (HighsInt iEl = lp.a_matrix_.start_[iCol]; - iEl < lp.a_matrix_.start_[iCol + 1]; iEl++) { - HighsInt iRow = lp.a_matrix_.index_[iEl]; - if (this->row_bound_[iRow] != kIisBoundStatusFree) col_count[iRow]++; + if (empty_col) { + highsLogUser(log_options, HighsLogType::kInfo, "Col %d has status Dropped: Empty\n", int(iCol)); + this->col_bound_[iCol] = kIisBoundStatusDropped; + run_status = highs.changeColBounds(iCol, -kHighsInf, kHighsInf); + assert(run_status == HighsStatus::kOk); } } - for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { - if (col_count[iRow] > 0) continue; - double lower = lp.row_lower_[iRow]; - double upper = lp.row_upper_[iRow]; - bool trivially_feasible = !(lower > options.primal_feasibility_tolerance && upper < -options.primal_feasibility_tolerance); - assert(trivially_feasible); - this->row_bound_[iRow] = kIisBoundStatusFree; - } } + this->report("End of pass 1", incumbent_lp); } + this->report("End of pass 2", incumbent_lp); HighsInt iss_num_col = 0; for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { - if (this->col_bound_[iCol] != kIisBoundStatusFree) { + if (this->col_bound_[iCol] != kIisBoundStatusDropped) { this->col_index_[iss_num_col] = this->col_index_[iCol]; this->col_bound_[iss_num_col] = this->col_bound_[iCol]; iss_num_col++; @@ -386,7 +389,7 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options) { } HighsInt iss_num_row = 0; for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { - if (this->row_bound_[iRow] != kIisBoundStatusFree) { + if (this->row_bound_[iRow] != kIisBoundStatusDropped) { this->row_index_[iss_num_row] = this->row_index_[iRow]; this->row_bound_[iss_num_row] = this->row_bound_[iRow]; iss_num_row++; diff --git a/src/lp_data/HighsIis.h b/src/lp_data/HighsIis.h index bd66f12b00..96adce9ac6 100644 --- a/src/lp_data/HighsIis.h +++ b/src/lp_data/HighsIis.h @@ -17,7 +17,8 @@ #include "lp_data/HighsLp.h" enum IisBoundStatus { - kIisBoundStatusNull = 0, + kIisBoundStatusDropped = -1, + kIisBoundStatusNull, // 0 kIisBoundStatusFree, // 1 kIisBoundStatusLower, // 2 kIisBoundStatusUpper, // 3 @@ -29,6 +30,7 @@ class HighsIis { HighsIis() {} void invalidate(); + void report(const std::string message, const HighsLp& lp); void addCol(const HighsInt col, const HighsInt status = kIisBoundStatusNull); void addRow(const HighsInt row, const HighsInt status = kIisBoundStatusNull); void removeCol(const HighsInt col); From 4ead6e16632f086992f1f2273aadd8860b51ccf3 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 6 Jun 2024 18:32:53 +0100 Subject: [PATCH 014/194] Debugging galenet looks hard, so add woodinfe --- check/TestIis.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/check/TestIis.cpp b/check/TestIis.cpp index 8f9a59b6f5..9d11149f10 100644 --- a/check/TestIis.cpp +++ b/check/TestIis.cpp @@ -15,6 +15,7 @@ void testIis(std::string& model, HighsInt* iis_row_index, HighsInt* iis_col_bound, HighsInt* iis_row_bound) { + HighsModelStatus model_status; std::string model_file = std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; Highs highs; @@ -56,7 +57,6 @@ void testIis(std::string& model, REQUIRE(model_status == HighsModelStatus::kOptimal); REQUIRE(highs.changeRowBounds(iRow, lower, upper) == HighsStatus::kOk); } - REQUIRE(lp == incumbent_lp); for (HighsInt iisCol = 0; iisCol < num_iis_col; iisCol++) { HighsInt iCol = iis_col_index[iisCol]; HighsInt iis_bound = iis_col_bound[iisCol]; @@ -76,13 +76,13 @@ void testIis(std::string& model, } REQUIRE(highs.changeColBounds(iCol, to_lower, to_upper) == HighsStatus::kOk); REQUIRE(highs.run() == HighsStatus::kOk); - HighsModelStatus model_status = highs.getModelStatus(); + model_status = highs.getModelStatus(); if (dev_run) printf("Removing IIS Col %d (LP col %d) status %d yields model status %s\n", int(iisCol), int(iCol), int(iis_bound), highs.modelStatusToString(model_status).c_str()); - /* if (model_status != HighsModelStatus::kOptimal) { highs.writeSolution("", kSolutionStylePretty); + /* Highs ck_highs; ck_highs.setOptionValue("threads", 1); ck_highs.passModel(incumbent_lp); @@ -90,8 +90,8 @@ void testIis(std::string& model, HighsInt ck_num_iis_row; ck_highs.run(); ck_highs.getIis(num_iis_col, num_iis_row, nullptr, nullptr, nullptr, nullptr); - } */ + } REQUIRE(model_status == HighsModelStatus::kOptimal); REQUIRE(highs.changeColBounds(iCol, lower, upper) == HighsStatus::kOk); } @@ -108,8 +108,10 @@ void testMps(std::string& model) { HighsInt num_iis_row; REQUIRE(highs.readModel(model_file) == HighsStatus::kOk); + highs.writeModel(""); REQUIRE(highs.run() == HighsStatus::kOk); REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); + // highs.setOptionValue("iis_strategy", kIisStrategyFromLpRowPriority); REQUIRE(highs.getIis(num_iis_col, num_iis_row) == HighsStatus::kOk); REQUIRE(num_iis_col > 0); REQUIRE(num_iis_row > 0); @@ -280,6 +282,11 @@ TEST_CASE("lp-get-iis", "[iis]") { } +TEST_CASE("lp-get-iis-woodinfe", "[iis]") { + std::string model = "woodinfe"; + testMps(model); +} + TEST_CASE("lp-get-iis-galenet", "[iis]") { // Dual ray corresponds to constraints // From b9849f8299236e40edc54243b78af4aca76a856b Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 7 Jun 2024 14:40:51 +0100 Subject: [PATCH 015/194] Added report to HighsIis --- src/lp_data/HighsIis.cpp | 21 ++++++++++++--------- src/lp_data/HighsIis.h | 3 ++- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/lp_data/HighsIis.cpp b/src/lp_data/HighsIis.cpp index 2687b1ea04..6a610682b2 100644 --- a/src/lp_data/HighsIis.cpp +++ b/src/lp_data/HighsIis.cpp @@ -25,7 +25,7 @@ void HighsIis::invalidate() { this->row_bound_.clear(); } -std::string iisBoundStatusToString(HighsInt bound_status) { +std::string HighsIis::iisBoundStatusToString(HighsInt bound_status) const { if (bound_status == kIisBoundStatusDropped) return "Dropped"; if (bound_status == kIisBoundStatusNull) return " Null"; if (bound_status == kIisBoundStatusFree) return " Free"; @@ -35,23 +35,26 @@ std::string iisBoundStatusToString(HighsInt bound_status) { return "*****"; } -void HighsIis::report(const std::string message, const HighsLp& lp) { +void HighsIis::report(const std::string message, const HighsLp& lp) const { + HighsInt num_iis_col = this->col_index_.size(); + HighsInt num_iis_row = this->row_index_.size(); + if (num_iis_col > 10 || num_iis_row > 10) return; printf("\nIIS %s\n===\n", message.c_str()); printf("Column: "); - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + for (HighsInt iCol = 0; iCol < num_iis_col; iCol++) printf("%9d ", iCol); printf("\nStatus: "); - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + for (HighsInt iCol = 0; iCol < num_iis_col; iCol++) printf("%9s ", iisBoundStatusToString(this->col_bound_[iCol]).c_str()); printf("\nLower: "); - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + for (HighsInt iCol = 0; iCol < num_iis_col; iCol++) printf("%9.2g ", lp.col_lower_[iCol]); printf("\nUpper: "); - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + for (HighsInt iCol = 0; iCol < num_iis_col; iCol++) printf("%9.2g ", lp.col_upper_[iCol]); printf("\n"); printf("Row: Status Lower Upper\n"); - for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) + for (HighsInt iRow = 0; iRow < num_iis_row; iRow++) printf("%2d %9s %9.2g %9.2g\n", int(iRow), iisBoundStatusToString(this->row_bound_[iRow]).c_str(), lp.row_lower_[iRow], lp.row_upper_[iRow]); @@ -210,6 +213,7 @@ HighsStatus HighsIis::getData(const HighsLp& lp, const HighsOptions& options, // Use the whole LP if (this->compute(lp, options) != HighsStatus::kOk) return HighsStatus::kError; } + this->report("On exit", lp); return HighsStatus::kOk; } @@ -223,8 +227,7 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options) { for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) this->addCol(iCol); for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) this->addRow(iRow); Highs highs; - highs.setOptionValue("output_flag", false); - highs.setOptionValue("threads", 1); + // highs.setOptionValue("output_flag", false); highs.setOptionValue("presolve", kHighsOffString); const HighsLp& incumbent_lp = highs.getLp(); HighsStatus run_status = highs.passModel(lp); diff --git a/src/lp_data/HighsIis.h b/src/lp_data/HighsIis.h index 96adce9ac6..353c32520c 100644 --- a/src/lp_data/HighsIis.h +++ b/src/lp_data/HighsIis.h @@ -30,7 +30,8 @@ class HighsIis { HighsIis() {} void invalidate(); - void report(const std::string message, const HighsLp& lp); + std::string iisBoundStatusToString(HighsInt bound_status) const; + void report(const std::string message, const HighsLp& lp) const; void addCol(const HighsInt col, const HighsInt status = kIisBoundStatusNull); void addRow(const HighsInt row, const HighsInt status = kIisBoundStatusNull); void removeCol(const HighsInt col); From d45a2add2f39892e94f76d292a18080920c7c48e Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 7 Jun 2024 15:39:38 +0100 Subject: [PATCH 016/194] Added Highs::computeIis() and const HighsIis& Highs::getIis() --- src/Highs.h | 11 +++++++---- src/lp_data/Highs.cpp | 13 +++++++++---- src/lp_data/HighsIis.cpp | 4 ---- src/lp_data/HighsInterface.cpp | 12 ++++++------ 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/Highs.h b/src/Highs.h index 9a97dba925..669860e7be 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -513,6 +513,9 @@ class Highs { HighsInt* iis_row_index = nullptr, HighsInt* iis_col_bound = nullptr, HighsInt* iis_row_bound = nullptr); + HighsStatus computeIis(); + const HighsIis& getIis() const {return iis_; } + /** * @brief Get the current model objective value */ @@ -1511,10 +1514,10 @@ class Highs { double* primal_ray_value); HighsStatus getRangingInterface(); - HighsStatus getIisInterface(); - HighsStatus extractIisData(HighsInt& num_iis_col, HighsInt& num_iis_row, - HighsInt* iis_col_index, HighsInt* iis_row_index, - HighsInt* iis_col_bound, HighsInt* iis_row_bound); + HighsStatus computeIisInterface(); + HighsStatus extractIis(HighsInt& num_iis_col, HighsInt& num_iis_row, + HighsInt* iis_col_index, HighsInt* iis_row_index, + HighsInt* iis_col_bound, HighsInt* iis_row_bound); bool aFormatOk(const HighsInt num_nz, const HighsInt format); bool qFormatOk(const HighsInt num_nz, const HighsInt format); diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index 9d0c32a8a8..36ee0e907b 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -1682,17 +1682,22 @@ HighsStatus Highs::getIllConditioning(HighsIllConditioning& ill_conditioning, HighsStatus Highs::getIis(HighsInt& num_iis_col, HighsInt& num_iis_row, HighsInt* iis_col_index, HighsInt* iis_row_index, HighsInt* iis_col_bound, HighsInt* iis_row_bound) { + HighsStatus return_status = this->computeIis(); + if (return_status != HighsStatus::kOk) return return_status; + return this->extractIis(num_iis_col, num_iis_row, iis_col_index, + iis_row_index, iis_col_bound, iis_row_bound); +} + +HighsStatus Highs::computeIis() { if (model_status_ != HighsModelStatus::kInfeasible) { highsLogUser(options_.log_options, HighsLogType::kError, "getIis: model status is not infeasible\n"); return HighsStatus::kError; } - HighsStatus return_status = this->getIisInterface(); - if (return_status != HighsStatus::kOk) return return_status; - return this->extractIisData(num_iis_col, num_iis_row, iis_col_index, - iis_row_index, iis_col_bound, iis_row_bound); + return this->computeIisInterface(); } + bool Highs::hasInvert() const { return ekk_instance_.status_.has_invert; } const HighsInt* Highs::getBasicVariablesArray() const { diff --git a/src/lp_data/HighsIis.cpp b/src/lp_data/HighsIis.cpp index 6a610682b2..527e3cdebd 100644 --- a/src/lp_data/HighsIis.cpp +++ b/src/lp_data/HighsIis.cpp @@ -167,8 +167,6 @@ HighsStatus HighsIis::getData(const HighsLp& lp, const HighsOptions& options, to_row[iRow] = from_row.size(); from_row.push_back(iRow); } - printf("HighsIis::getData: dual_ray_value[%2d] = %g; to_row[%2d] = %d\n", - int(iRow), dual_ray_value[iRow], int(iRow), to_row[iRow]); } for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { bool use_col = false; @@ -183,7 +181,6 @@ HighsStatus HighsIis::getData(const HighsLp& lp, const HighsOptions& options, to_lp.num_col_ = to_num_col; to_lp.num_row_ = to_num_row; for (HighsInt iCol = 0; iCol < to_num_col; iCol++) { - printf("getIisData: from_col[%2d] = %d\n", int(iCol), int(from_col[iCol])); to_lp.col_cost_.push_back(0); to_lp.col_lower_.push_back(lp.col_lower_[from_col[iCol]]); to_lp.col_upper_.push_back(lp.col_upper_[from_col[iCol]]); @@ -198,7 +195,6 @@ HighsStatus HighsIis::getData(const HighsLp& lp, const HighsOptions& options, to_lp.a_matrix_.start_.push_back(to_lp.a_matrix_.index_.size()); } for (HighsInt iRow = 0; iRow < to_num_row; iRow++) { - printf("getIisData: from_row[%2d] = %d\n", int(iRow), int(from_row[iRow])); to_lp.row_lower_.push_back(lp.row_lower_[from_row[iRow]]); to_lp.row_upper_.push_back(lp.row_upper_[from_row[iRow]]); } diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index aee713b2d6..5c728e5e63 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -1549,7 +1549,7 @@ HighsStatus Highs::getRangingInterface() { return getRangingData(this->ranging_, solver_object); } -HighsStatus Highs::getIisInterface() { +HighsStatus Highs::computeIisInterface() { if (this->iis_.valid_) return HighsStatus::kOk; HighsLp& lp = model_.lp_; // Check for trivial IIS: empty infeasible row or inconsistent bounds @@ -1595,11 +1595,11 @@ HighsStatus Highs::getIisInterface() { return this->iis_.getData(lp, options_, dual_ray_value); } -HighsStatus Highs::extractIisData(HighsInt& num_iis_col, HighsInt& num_iis_row, - HighsInt* iis_col_index, - HighsInt* iis_row_index, - HighsInt* iis_col_bound, - HighsInt* iis_row_bound) { +HighsStatus Highs::extractIis(HighsInt& num_iis_col, HighsInt& num_iis_row, + HighsInt* iis_col_index, + HighsInt* iis_row_index, + HighsInt* iis_col_bound, + HighsInt* iis_row_bound) { assert(this->iis_.valid_); num_iis_col = this->iis_.col_index_.size(); num_iis_row = this->iis_.row_index_.size(); From 4e3f5a4a312e25b8230a0e2b22ee30bbcc09faeb Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 7 Jun 2024 22:19:16 +0100 Subject: [PATCH 017/194] Corrected testIis in check/TestIis.cpp; formatted --- check/TestIis.cpp | 179 ++++++++++------- src/Highs.h | 6 +- src/lp_data/Highs.cpp | 32 +-- src/lp_data/HighsIis.cpp | 347 +++++++++++++++++++++------------ src/lp_data/HighsIis.h | 15 +- src/lp_data/HighsInterface.cpp | 9 +- 6 files changed, 355 insertions(+), 233 deletions(-) diff --git a/check/TestIis.cpp b/check/TestIis.cpp index 9d11149f10..d69790d2c2 100644 --- a/check/TestIis.cpp +++ b/check/TestIis.cpp @@ -9,91 +9,127 @@ const bool dev_run = true; const double inf = kHighsInf; -void testIis(std::string& model, - HighsInt& num_iis_col, HighsInt& num_iis_row, - HighsInt* iis_col_index, - HighsInt* iis_row_index, - HighsInt* iis_col_bound, - HighsInt* iis_row_bound) { +void testIis(const std::string& model, const HighsIis& iis) { HighsModelStatus model_status; std::string model_file = - std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; + std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; + HighsInt num_iis_col = iis.col_index_.size(); + HighsInt num_iis_row = iis.row_index_.size(); + Highs highs; highs.setOptionValue("output_flag", false); - highs.setOptionValue("threads", 1); REQUIRE(highs.readModel(model_file) == HighsStatus::kOk); const HighsLp& incumbent_lp = highs.getLp(); HighsLp lp = highs.getLp(); // Zero the objective lp.col_cost_.assign(lp.num_col_, 0); - REQUIRE(highs.changeColsCost(0, lp.num_col_-1, lp.col_cost_.data()) == HighsStatus::kOk); + REQUIRE(highs.changeColsCost(0, lp.num_col_ - 1, lp.col_cost_.data()) == + HighsStatus::kOk); - highs.run(); - lp = highs.getLp(); + // Save the bounds + std::vector iis_col_lower; + std::vector iis_col_upper; + std::vector iis_row_lower; + std::vector iis_row_upper; + for (HighsInt iisCol = 0; iisCol < num_iis_col; iisCol++) { + HighsInt iCol = iis.col_index_[iisCol]; + iis_col_lower.push_back(lp.col_lower_[iCol]); + iis_col_upper.push_back(lp.col_upper_[iCol]); + } for (HighsInt iisRow = 0; iisRow < num_iis_row; iisRow++) { - HighsInt iRow = iis_row_index[iisRow]; - HighsInt iis_bound = iis_row_bound[iisRow]; - const double lower = lp.row_lower_[iRow]; - const double upper = lp.row_upper_[iRow]; + HighsInt iRow = iis.row_index_[iisRow]; + iis_row_lower.push_back(lp.row_lower_[iRow]); + iis_row_upper.push_back(lp.row_upper_[iRow]); + } + + // Free all the columns and rows + lp.col_lower_.assign(lp.num_col_, -inf); + lp.col_upper_.assign(lp.num_col_, inf); + lp.row_lower_.assign(lp.num_row_, -inf); + lp.row_upper_.assign(lp.num_row_, inf); + // Restore the bounds for the IIS columns and rows + for (HighsInt iisCol = 0; iisCol < num_iis_col; iisCol++) { + HighsInt iCol = iis.col_index_[iisCol]; + lp.col_lower_[iCol] = iis_col_lower[iisCol]; + lp.col_upper_[iCol] = iis_col_upper[iisCol]; + } + for (HighsInt iisRow = 0; iisRow < num_iis_row; iisRow++) { + HighsInt iRow = iis.row_index_[iisRow]; + lp.row_lower_[iRow] = iis_row_lower[iisRow]; + lp.row_upper_[iRow] = iis_row_upper[iisRow]; + } + + highs.passModel(lp); + highs.run(); + model_status = highs.getModelStatus(); + printf("IIS LP yields model status %s\n", + highs.modelStatusToString(model_status).c_str()); + REQUIRE(model_status == HighsModelStatus::kInfeasible); + + for (HighsInt iisCol = 0; iisCol < num_iis_col; iisCol++) { + HighsInt iCol = iis.col_index_[iisCol]; + HighsInt iis_bound = iis.col_bound_[iisCol]; + const double lower = lp.col_lower_[iCol]; + const double upper = lp.col_upper_[iCol]; double to_lower = lower; double to_upper = upper; REQUIRE(iis_bound != kIisBoundStatusDropped); REQUIRE(iis_bound != kIisBoundStatusNull); - REQUIRE(iis_bound != kIisBoundStatusFree); REQUIRE(iis_bound != kIisBoundStatusBoxed); if (iis_bound == kIisBoundStatusLower) { to_lower = -inf; } else if (iis_bound == kIisBoundStatusUpper) { to_upper = inf; + } else if (iis_bound == kIisBoundStatusFree) { + printf("IIS Col %2d (LP col %6d) status %s\n", int(iisCol), int(iCol), + iis.iisBoundStatusToString(iis_bound).c_str()); + continue; } - REQUIRE(highs.changeRowBounds(iRow, to_lower, to_upper) == HighsStatus::kOk); + REQUIRE(highs.changeColBounds(iCol, to_lower, to_upper) == + HighsStatus::kOk); REQUIRE(highs.run() == HighsStatus::kOk); - HighsModelStatus model_status = highs.getModelStatus(); + model_status = highs.getModelStatus(); if (dev_run) - printf("Removing IIS Row %d (LP row %d) status %d yields model status %s\n", - int(iisRow), int(iRow), int(iis_bound), highs.modelStatusToString(model_status).c_str()); - if (model_status != HighsModelStatus::kOptimal) - highs.writeSolution("", kSolutionStylePretty); + printf( + "IIS Col %2d (LP col %6d) status %s: removal yields model status " + "%s\n", + int(iisCol), int(iCol), iis.iisBoundStatusToString(iis_bound).c_str(), + highs.modelStatusToString(model_status).c_str()); + // if (model_status != HighsModelStatus::kOptimal) + // highs.writeSolution("", kSolutionStylePretty); REQUIRE(model_status == HighsModelStatus::kOptimal); - REQUIRE(highs.changeRowBounds(iRow, lower, upper) == HighsStatus::kOk); + REQUIRE(highs.changeColBounds(iCol, lower, upper) == HighsStatus::kOk); } - for (HighsInt iisCol = 0; iisCol < num_iis_col; iisCol++) { - HighsInt iCol = iis_col_index[iisCol]; - HighsInt iis_bound = iis_col_bound[iisCol]; - const double lower = lp.col_lower_[iCol]; - const double upper = lp.col_upper_[iCol]; + for (HighsInt iisRow = 0; iisRow < num_iis_row; iisRow++) { + HighsInt iRow = iis.row_index_[iisRow]; + HighsInt iis_bound = iis.row_bound_[iisRow]; + const double lower = lp.row_lower_[iRow]; + const double upper = lp.row_upper_[iRow]; double to_lower = lower; double to_upper = upper; REQUIRE(iis_bound != kIisBoundStatusDropped); REQUIRE(iis_bound != kIisBoundStatusNull); + REQUIRE(iis_bound != kIisBoundStatusFree); REQUIRE(iis_bound != kIisBoundStatusBoxed); if (iis_bound == kIisBoundStatusLower) { to_lower = -inf; } else if (iis_bound == kIisBoundStatusUpper) { to_upper = inf; - } else if (iis_bound == kIisBoundStatusFree) { - continue; } - REQUIRE(highs.changeColBounds(iCol, to_lower, to_upper) == HighsStatus::kOk); + REQUIRE(highs.changeRowBounds(iRow, to_lower, to_upper) == + HighsStatus::kOk); REQUIRE(highs.run() == HighsStatus::kOk); model_status = highs.getModelStatus(); if (dev_run) - printf("Removing IIS Col %d (LP col %d) status %d yields model status %s\n", - int(iisCol), int(iCol), int(iis_bound), highs.modelStatusToString(model_status).c_str()); - if (model_status != HighsModelStatus::kOptimal) { - highs.writeSolution("", kSolutionStylePretty); - /* - Highs ck_highs; - ck_highs.setOptionValue("threads", 1); - ck_highs.passModel(incumbent_lp); - HighsInt ck_num_iis_col; - HighsInt ck_num_iis_row; - ck_highs.run(); - ck_highs.getIis(num_iis_col, num_iis_row, nullptr, nullptr, nullptr, nullptr); - */ - } + printf( + "IIS Row %2d (LP row %6d) status %s: removal yields model status " + "%s\n", + int(iisRow), int(iRow), iis.iisBoundStatusToString(iis_bound).c_str(), + highs.modelStatusToString(model_status).c_str()); + // if (model_status != HighsModelStatus::kOptimal) + // highs.writeSolution("", kSolutionStylePretty); REQUIRE(model_status == HighsModelStatus::kOptimal); - REQUIRE(highs.changeColBounds(iCol, lower, upper) == HighsStatus::kOk); + REQUIRE(highs.changeRowBounds(iRow, lower, upper) == HighsStatus::kOk); } } @@ -102,37 +138,32 @@ void testMps(std::string& model) { std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; Highs highs; highs.setOptionValue("output_flag", dev_run); - highs.setOptionValue("threads", 1); - - HighsInt num_iis_col; - HighsInt num_iis_row; REQUIRE(highs.readModel(model_file) == HighsStatus::kOk); highs.writeModel(""); REQUIRE(highs.run() == HighsStatus::kOk); REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); // highs.setOptionValue("iis_strategy", kIisStrategyFromLpRowPriority); - REQUIRE(highs.getIis(num_iis_col, num_iis_row) == HighsStatus::kOk); + REQUIRE(highs.computeIis() == HighsStatus::kOk); + const HighsIis& iis = highs.getIis(); + HighsInt num_iis_col = iis.col_index_.size(); + HighsInt num_iis_row = iis.row_index_.size(); REQUIRE(num_iis_col > 0); REQUIRE(num_iis_row > 0); - std::vector iis_col_index(num_iis_col); - std::vector iis_row_index(num_iis_row); - std::vector iis_col_bound(num_iis_col); - std::vector iis_row_bound(num_iis_row); - REQUIRE(highs.getIis(num_iis_col, num_iis_row, iis_col_index.data(), - iis_row_index.data(), - iis_col_bound.data(), iis_row_bound.data()) == HighsStatus::kOk); if (dev_run) - printf("Model %s has IIS with %d columns and %d rows\n", model.c_str(), int(num_iis_col), int(num_iis_row)); - testIis(model, num_iis_col, num_iis_row, iis_col_index.data(), iis_row_index.data(), iis_col_bound.data(), iis_row_bound.data()); + printf("Model %s has IIS with %d columns and %d rows\n", model.c_str(), + int(num_iis_col), int(num_iis_row)); + testIis(model, iis); } TEST_CASE("lp-incompatible-bounds", "[iis]") { // LP has row0 and col2 with inconsistent bounds. // - // When prioritising rows, row0 and its constituent columns (1, 2) should be found + // When prioritising rows, row0 and its constituent columns (1, 2) should be + // found // - // When prioritising columns, col2 and its constituent rows (0, 1) should be found + // When prioritising columns, col2 and its constituent rows (0, 1) should be + // found HighsLp lp; lp.num_col_ = 3; lp.num_row_ = 2; @@ -165,8 +196,8 @@ TEST_CASE("lp-incompatible-bounds", "[iis]") { iis_col_bound.resize(num_iis_col); iis_row_bound.resize(num_iis_row); REQUIRE(highs.getIis(num_iis_col, num_iis_row, iis_col_index.data(), - iis_row_index.data(), - iis_col_bound.data(), iis_row_bound.data()) == HighsStatus::kOk); + iis_row_index.data(), iis_col_bound.data(), + iis_row_bound.data()) == HighsStatus::kOk); REQUIRE(iis_row_index[0] == 0); REQUIRE(iis_row_bound[0] == kIisBoundStatusBoxed); highs.setOptionValue("iis_strategy", kIisStrategyFromLpColPriority); @@ -178,8 +209,8 @@ TEST_CASE("lp-incompatible-bounds", "[iis]") { iis_col_bound.resize(num_iis_col); iis_row_bound.resize(num_iis_row); REQUIRE(highs.getIis(num_iis_col, num_iis_row, iis_col_index.data(), - iis_row_index.data(), - iis_col_bound.data(), iis_row_bound.data()) == HighsStatus::kOk); + iis_row_index.data(), iis_col_bound.data(), + iis_row_bound.data()) == HighsStatus::kOk); REQUIRE(iis_col_index[0] == 2); REQUIRE(iis_col_bound[0] == kIisBoundStatusBoxed); } @@ -218,11 +249,11 @@ TEST_CASE("lp-empty-infeasible-row", "[iis]") { iis_col_bound.resize(num_iis_col); iis_row_bound.resize(num_iis_row); REQUIRE(highs.getIis(num_iis_col, num_iis_row, iis_col_index.data(), - iis_row_index.data(), - iis_col_bound.data(), iis_row_bound.data()) == HighsStatus::kOk); + iis_row_index.data(), iis_col_bound.data(), + iis_row_bound.data()) == HighsStatus::kOk); REQUIRE(iis_row_index[0] == empty_row); REQUIRE(iis_row_bound[0] == kIisBoundStatusLower); - REQUIRE(highs.changeRowBounds(empty_row, -2, -1) == HighsStatus::kOk); + REQUIRE(highs.changeRowBounds(empty_row, -2, -1) == HighsStatus::kOk); REQUIRE(highs.run() == HighsStatus::kOk); REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); REQUIRE(highs.getIis(num_iis_col, num_iis_row) == HighsStatus::kOk); @@ -233,11 +264,10 @@ TEST_CASE("lp-empty-infeasible-row", "[iis]") { iis_col_bound.resize(num_iis_col); iis_row_bound.resize(num_iis_row); REQUIRE(highs.getIis(num_iis_col, num_iis_row, iis_col_index.data(), - iis_row_index.data(), - iis_col_bound.data(), iis_row_bound.data()) == HighsStatus::kOk); + iis_row_index.data(), iis_col_bound.data(), + iis_row_bound.data()) == HighsStatus::kOk); REQUIRE(iis_row_index[0] == empty_row); REQUIRE(iis_row_bound[0] == kIisBoundStatusUpper); - } TEST_CASE("lp-get-iis", "[iis]") { @@ -279,7 +309,6 @@ TEST_CASE("lp-get-iis", "[iis]") { REQUIRE(iis_col_index[0] == 0); REQUIRE(iis_col_index[1] == 1); REQUIRE(iis_row_index[0] == 2); - } TEST_CASE("lp-get-iis-woodinfe", "[iis]") { @@ -292,7 +321,7 @@ TEST_CASE("lp-get-iis-galenet", "[iis]") { // // r0: 0 <= c0 + c1 - c3 - c4 <=0 // - // r1: 20 <= c2 + c3 + // r1: 20 <= c2 + c3 // // r2: 30 <= c4 // @@ -307,7 +336,7 @@ TEST_CASE("lp-get-iis-galenet", "[iis]") { // 0 <= c3 <= 20 // // 0 <= c4 <= 30 - // + // // This is infeasible since c4 >= 30 and c4 <= 30 fices c4 = 30, // then c0 + c1 >= c3 + c4 >= 30 cannot be satisfied due to the // upper bounds of 10 on these variables diff --git a/src/Highs.h b/src/Highs.h index 669860e7be..1923d32dc9 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -514,7 +514,7 @@ class Highs { HighsInt* iis_col_bound = nullptr, HighsInt* iis_row_bound = nullptr); HighsStatus computeIis(); - const HighsIis& getIis() const {return iis_; } + const HighsIis& getIis() const { return iis_; } /** * @brief Get the current model objective value @@ -1516,8 +1516,8 @@ class Highs { HighsStatus computeIisInterface(); HighsStatus extractIis(HighsInt& num_iis_col, HighsInt& num_iis_row, - HighsInt* iis_col_index, HighsInt* iis_row_index, - HighsInt* iis_col_bound, HighsInt* iis_row_bound); + HighsInt* iis_col_index, HighsInt* iis_row_index, + HighsInt* iis_col_bound, HighsInt* iis_row_bound); bool aFormatOk(const HighsInt num_nz, const HighsInt format); bool qFormatOk(const HighsInt num_nz, const HighsInt format); diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index 36ee0e907b..bb0b7177c5 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -1685,7 +1685,7 @@ HighsStatus Highs::getIis(HighsInt& num_iis_col, HighsInt& num_iis_row, HighsStatus return_status = this->computeIis(); if (return_status != HighsStatus::kOk) return return_status; return this->extractIis(num_iis_col, num_iis_row, iis_col_index, - iis_row_index, iis_col_bound, iis_row_bound); + iis_row_index, iis_col_bound, iis_row_bound); } HighsStatus Highs::computeIis() { @@ -1697,7 +1697,6 @@ HighsStatus Highs::computeIis() { return this->computeIisInterface(); } - bool Highs::hasInvert() const { return ekk_instance_.status_.has_invert; } const HighsInt* Highs::getBasicVariablesArray() const { @@ -2344,7 +2343,7 @@ HighsStatus analyseSetCreateError(HighsLogOptions log_options, const HighsInt create_error, const bool ordered, const HighsInt num_set_entries, - const HighsInt* set, + const HighsInt* set, const HighsInt dimension) { if (create_error == kIndexCollectionCreateIllegalSetSize) { highsLogUser(log_options, HighsLogType::kError, @@ -2370,7 +2369,8 @@ HighsStatus analyseSetCreateError(HighsLogOptions log_options, highsLogUser( log_options, HighsLogType::kError, "Set supplied to Highs::%s has entry %d of %d out of range [0, %d)\n", - method.c_str(), int(illegal_set_index), int(illegal_set_entry), int(dimension)); + method.c_str(), int(illegal_set_index), int(illegal_set_entry), + int(dimension)); } assert(create_error != kIndexCollectionCreateIllegalSetDimension); return HighsStatus::kError; @@ -2392,8 +2392,8 @@ HighsStatus Highs::changeColsIntegrality(const HighsInt num_set_entries, local_set.data(), model_.lp_.num_col_); if (create_error) return analyseSetCreateError(options_.log_options, "changeColsIntegrality", - create_error, true, num_set_entries, local_set.data(), - model_.lp_.num_col_); + create_error, true, num_set_entries, + local_set.data(), model_.lp_.num_col_); HighsStatus call_status = changeIntegralityInterface(index_collection, local_integrality.data()); HighsStatus return_status = HighsStatus::kOk; @@ -2462,8 +2462,8 @@ HighsStatus Highs::changeColsCost(const HighsInt num_set_entries, local_set.data(), model_.lp_.num_col_); if (create_error) return analyseSetCreateError(options_.log_options, "changeColsCost", - create_error, true, num_set_entries, local_set.data(), - model_.lp_.num_col_); + create_error, true, num_set_entries, + local_set.data(), model_.lp_.num_col_); HighsStatus call_status = changeCostsInterface(index_collection, local_cost.data()); HighsStatus return_status = HighsStatus::kOk; @@ -2541,8 +2541,8 @@ HighsStatus Highs::changeColsBounds(const HighsInt num_set_entries, local_set.data(), model_.lp_.num_col_); if (create_error) return analyseSetCreateError(options_.log_options, "changeColsBounds", - create_error, true, num_set_entries, local_set.data(), - model_.lp_.num_col_); + create_error, true, num_set_entries, + local_set.data(), model_.lp_.num_col_); HighsStatus call_status = changeColBoundsInterface( index_collection, local_lower.data(), local_upper.data()); HighsStatus return_status = HighsStatus::kOk; @@ -2622,8 +2622,8 @@ HighsStatus Highs::changeRowsBounds(const HighsInt num_set_entries, local_set.data(), model_.lp_.num_row_); if (create_error) return analyseSetCreateError(options_.log_options, "changeRowsBounds", - create_error, true, num_set_entries, local_set.data(), - model_.lp_.num_row_); + create_error, true, num_set_entries, + local_set.data(), model_.lp_.num_row_); HighsStatus call_status = changeRowBoundsInterface( index_collection, local_lower.data(), local_upper.data()); HighsStatus return_status = HighsStatus::kOk; @@ -2728,7 +2728,8 @@ HighsStatus Highs::getCols(const HighsInt num_set_entries, const HighsInt* set, create(index_collection, num_set_entries, set, model_.lp_.num_col_); if (create_error) return analyseSetCreateError(options_.log_options, "getCols", create_error, - false, num_set_entries, set, model_.lp_.num_col_); + false, num_set_entries, set, + model_.lp_.num_col_); getColsInterface(index_collection, num_col, costs, lower, upper, num_nz, start, index, value); return returnFromHighs(HighsStatus::kOk); @@ -2847,7 +2848,8 @@ HighsStatus Highs::getRows(const HighsInt num_set_entries, const HighsInt* set, create(index_collection, num_set_entries, set, model_.lp_.num_row_); if (create_error) return analyseSetCreateError(options_.log_options, "getRows", create_error, - false, num_set_entries, set, model_.lp_.num_row_); + false, num_set_entries, set, + model_.lp_.num_row_); getRowsInterface(index_collection, num_row, lower, upper, num_nz, start, index, value); return returnFromHighs(HighsStatus::kOk); @@ -2955,7 +2957,7 @@ HighsStatus Highs::deleteCols(const HighsInt num_set_entries, create(index_collection, num_set_entries, set, model_.lp_.num_col_); if (create_error) return analyseSetCreateError(options_.log_options, "deleteCols", - create_error, false, num_set_entries, set, + create_error, false, num_set_entries, set, model_.lp_.num_col_); deleteColsInterface(index_collection); return returnFromHighs(HighsStatus::kOk); diff --git a/src/lp_data/HighsIis.cpp b/src/lp_data/HighsIis.cpp index 527e3cdebd..548019c543 100644 --- a/src/lp_data/HighsIis.cpp +++ b/src/lp_data/HighsIis.cpp @@ -41,23 +41,22 @@ void HighsIis::report(const std::string message, const HighsLp& lp) const { if (num_iis_col > 10 || num_iis_row > 10) return; printf("\nIIS %s\n===\n", message.c_str()); printf("Column: "); - for (HighsInt iCol = 0; iCol < num_iis_col; iCol++) - printf("%9d ", iCol); + for (HighsInt iCol = 0; iCol < num_iis_col; iCol++) printf("%9d ", iCol); printf("\nStatus: "); - for (HighsInt iCol = 0; iCol < num_iis_col; iCol++) + for (HighsInt iCol = 0; iCol < num_iis_col; iCol++) printf("%9s ", iisBoundStatusToString(this->col_bound_[iCol]).c_str()); printf("\nLower: "); - for (HighsInt iCol = 0; iCol < num_iis_col; iCol++) + for (HighsInt iCol = 0; iCol < num_iis_col; iCol++) printf("%9.2g ", lp.col_lower_[iCol]); printf("\nUpper: "); - for (HighsInt iCol = 0; iCol < num_iis_col; iCol++) + for (HighsInt iCol = 0; iCol < num_iis_col; iCol++) printf("%9.2g ", lp.col_upper_[iCol]); printf("\n"); printf("Row: Status Lower Upper\n"); for (HighsInt iRow = 0; iRow < num_iis_row; iRow++) printf("%2d %9s %9.2g %9.2g\n", int(iRow), - iisBoundStatusToString(this->row_bound_[iRow]).c_str(), - lp.row_lower_[iRow], lp.row_upper_[iRow]); + iisBoundStatusToString(this->row_bound_[iRow]).c_str(), + lp.row_lower_[iRow], lp.row_upper_[iRow]); printf("\n"); } @@ -74,15 +73,15 @@ void HighsIis::addRow(const HighsInt row, const HighsInt status) { void HighsIis::removeCol(const HighsInt col) { HighsInt num_col = this->col_index_.size(); assert(col < num_col); - this->col_index_[col] = this->col_index_[num_col-1]; - this->col_index_.resize(num_col-1); + this->col_index_[col] = this->col_index_[num_col - 1]; + this->col_index_.resize(num_col - 1); } void HighsIis::removeRow(const HighsInt row) { HighsInt num_row = this->row_index_.size(); assert(row < num_row); - this->row_index_[row] = this->row_index_[num_row-1]; - this->row_index_.resize(num_row-1); + this->row_index_[row] = this->row_index_[num_row - 1]; + this->row_index_.resize(num_row - 1); } bool HighsIis::trivial(const HighsLp& lp, const HighsOptions& options) { @@ -119,7 +118,7 @@ bool HighsIis::trivial(const HighsLp& lp, const HighsOptions& options) { if (num_iis_col + num_iis_row > 0) { // Should have found exactly 1 assert((num_iis_col == 1 || num_iis_row == 1) && - num_iis_col + num_iis_row < 2); + num_iis_col + num_iis_row < 2); this->valid_ = true; this->strategy_ = options.iis_strategy; return true; @@ -129,7 +128,7 @@ bool HighsIis::trivial(const HighsLp& lp, const HighsOptions& options) { count.assign(lp.num_row_, 0); for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { for (HighsInt iEl = lp.a_matrix_.start_[iCol]; - iEl < lp.a_matrix_.start_[iCol + 1]; iEl++) + iEl < lp.a_matrix_.start_[iCol + 1]; iEl++) count[lp.a_matrix_.index_[iEl]]++; } assert(this->row_index_.size() == 0); @@ -150,7 +149,8 @@ bool HighsIis::trivial(const HighsLp& lp, const HighsOptions& options) { } HighsStatus HighsIis::getData(const HighsLp& lp, const HighsOptions& options, - const std::vector& dual_ray_value) { + const HighsBasis& basis, + const double* dual_ray_value) { // Check for trivial IIS should have been done earlier assert(!this->trivial(lp, options)); @@ -164,15 +164,15 @@ HighsStatus HighsIis::getData(const HighsLp& lp, const HighsOptions& options, assert(lp.a_matrix_.isColwise()); for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { if (dual_ray_value[iRow]) { - to_row[iRow] = from_row.size(); - from_row.push_back(iRow); + to_row[iRow] = from_row.size(); + from_row.push_back(iRow); } } for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { bool use_col = false; for (HighsInt iEl = lp.a_matrix_.start_[iCol]; - iEl < lp.a_matrix_.start_[iCol + 1]; iEl++) - use_col = use_col || to_row[lp.a_matrix_.index_[iEl]] >= 0; + iEl < lp.a_matrix_.start_[iCol + 1]; iEl++) + use_col = use_col || to_row[lp.a_matrix_.index_[iEl]] >= 0; if (use_col) from_col.push_back(iCol); } HighsInt to_num_col = from_col.size(); @@ -185,12 +185,12 @@ HighsStatus HighsIis::getData(const HighsLp& lp, const HighsOptions& options, to_lp.col_lower_.push_back(lp.col_lower_[from_col[iCol]]); to_lp.col_upper_.push_back(lp.col_upper_[from_col[iCol]]); for (HighsInt iEl = lp.a_matrix_.start_[from_col[iCol]]; - iEl < lp.a_matrix_.start_[from_col[iCol] + 1]; iEl++) { - HighsInt iRow = lp.a_matrix_.index_[iEl]; - if (to_row[iRow] >= 0) { - to_lp.a_matrix_.index_.push_back(to_row[iRow]); - to_lp.a_matrix_.value_.push_back(lp.a_matrix_.value_[iEl]); - } + iEl < lp.a_matrix_.start_[from_col[iCol] + 1]; iEl++) { + HighsInt iRow = lp.a_matrix_.index_[iEl]; + if (to_row[iRow] >= 0) { + to_lp.a_matrix_.index_.push_back(to_row[iRow]); + to_lp.a_matrix_.value_.push_back(lp.a_matrix_.value_[iEl]); + } } to_lp.a_matrix_.start_.push_back(to_lp.a_matrix_.index_.size()); } @@ -198,7 +198,8 @@ HighsStatus HighsIis::getData(const HighsLp& lp, const HighsOptions& options, to_lp.row_lower_.push_back(lp.row_lower_[from_row[iRow]]); to_lp.row_upper_.push_back(lp.row_upper_[from_row[iRow]]); } - if (this->compute(to_lp, options) != HighsStatus::kOk) return HighsStatus::kError; + if (this->compute(to_lp, options) != HighsStatus::kOk) + return HighsStatus::kError; // IIS col/row information is for to_lp, so indirect the values // into the original LP for (HighsInt iCol = 0; iCol < HighsInt(this->col_index_.size()); iCol++) @@ -207,18 +208,20 @@ HighsStatus HighsIis::getData(const HighsLp& lp, const HighsOptions& options, this->row_index_[iRow] = from_row[this->row_index_[iRow]]; } else { // Use the whole LP - if (this->compute(lp, options) != HighsStatus::kOk) return HighsStatus::kError; + if (this->compute(lp, options, &basis) != HighsStatus::kOk) + return HighsStatus::kError; } this->report("On exit", lp); return HighsStatus::kOk; } -HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options) { +HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, + const HighsBasis* basis) { this->invalidate(); const HighsLogOptions& log_options = options.log_options; const bool row_priority = - options.iis_strategy == kIisStrategyFromRayRowPriority || - options.iis_strategy == kIisStrategyFromLpRowPriority; + options.iis_strategy == kIisStrategyFromRayRowPriority || + options.iis_strategy == kIisStrategyFromLpRowPriority; // Initially all columns and rows are candidates for the IIS for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) this->addCol(iCol); for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) this->addRow(iRow); @@ -228,6 +231,60 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options) { const HighsLp& incumbent_lp = highs.getLp(); HighsStatus run_status = highs.passModel(lp); assert(run_status == HighsStatus::kOk); + + /* + // Elasticity filter + // + // Constraints L <= Ax <= U; l <= x <= u + // + // Transformed to + // + // L <= + // Determine the number of lower and upper elastic variables for + // columns and rows + std::vector col_lower_evar; + std::vector col_upper_evar; + std::vector row_lower_evar; + std::vector row_upper_evar; + std::vector erow_lower; + std::vector erow_upper; + std::vector erow_start; + std::vector erow_index; + std::vector erow_value; + HighsInt nz_offset = 0; + erow_start.push_back(0); + col_offset.assign(lp.num_col_, 0); + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + if (lp.col_lower_[iCol] > -kHighsInf) { + col_lower_evar.push_back(iCol); + erow_index.push_back(iCol); + erow_value.push_back(1); + erow_index.push_back(col_lower_evar.size()); + erow_value.push_back(1); + erow_lower + } + } + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + if (lp.col_upper_[iCol] < kHighsInf) { + col_upper_evar.push_back(iCol); + + } + } + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { + if (lp.row_lower_[iRow] > -kHighsInf) { + row_lower_evar.push_back(iRow); + + } + } + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { + if (lp.row_lower_[iRow] > -kHighsInf) { + row_lower_evar.push_back(iRow); + + } + } + for ( + */ + const bool write_model = false; if (write_model) { highs.setOptionValue("output_flag", true); @@ -237,121 +294,153 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options) { // Zero the objective std::vector cost; cost.assign(lp.num_col_, 0); - run_status = highs.changeColsCost(0, lp.num_col_-1, cost.data()); + run_status = highs.changeColsCost(0, lp.num_col_ - 1, cost.data()); assert(run_status == HighsStatus::kOk); + // Solve the LP + if (basis) highs.setBasis(*basis); run_status = highs.run(); assert(run_status == HighsStatus::kOk); if (run_status != HighsStatus::kOk) return run_status; assert(highs.getModelStatus() == HighsModelStatus::kInfeasible); - // Pass twice: rows before columns, or columns before rows, according to row_priority + const bool use_sensitivity_filter = false; + if (use_sensitivity_filter) { + highs.setOptionValue("simplex_strategy", kSimplexStrategyPrimal); + // + highs.setOptionValue("output_flag", true); + // Solve the LP + run_status = highs.run(); + highs.writeSolution("", kSolutionStylePretty); + } + + // assert(666==999); + // Pass twice: rows before columns, or columns before rows, according to + // row_priority for (HighsInt k = 0; k < 2; k++) { - const bool row_deletion = (row_priority && k == 0) || (!row_priority && k == 1); + const bool row_deletion = + (row_priority && k == 0) || (!row_priority && k == 1); std::string type = row_deletion ? "Row" : "Col"; // Perform deletion pass HighsInt num_index = row_deletion ? lp.num_row_ : lp.num_col_; for (HighsInt iX = 0; iX < num_index; iX++) { - const HighsInt ix_status = row_deletion ? this->row_bound_[iX] : this->col_bound_[iX]; - if (ix_status == kIisBoundStatusDropped || ix_status == kIisBoundStatusFree) continue; + const HighsInt ix_status = + row_deletion ? this->row_bound_[iX] : this->col_bound_[iX]; + if (ix_status == kIisBoundStatusDropped || + ix_status == kIisBoundStatusFree) + continue; double lower = row_deletion ? lp.row_lower_[iX] : lp.col_lower_[iX]; double upper = row_deletion ? lp.row_upper_[iX] : lp.col_upper_[iX]; - // Record whether the upper bound has been dropped due to the lower bound being kept + // Record whether the upper bound has been dropped due to the lower bound + // being kept bool drop_upper = false; if (lower > -kHighsInf) { - // Drop the lower bound temporarily - run_status = row_deletion ? highs.changeRowBounds(iX, -kHighsInf, upper) : highs.changeColBounds(iX, -kHighsInf, upper); - assert(run_status == HighsStatus::kOk); - run_status = highs.run(); - assert(run_status == HighsStatus::kOk); - HighsModelStatus model_status = highs.getModelStatus(); - if (model_status == HighsModelStatus::kOptimal) { - // Now feasible, so restore the lower bound - run_status = row_deletion ? highs.changeRowBounds(iX, lower, upper) : highs.changeColBounds(iX, lower, upper); - assert(run_status == HighsStatus::kOk); - // If the lower bound must be kept, then any finite upper bound - // must be dropped - const bool apply_reciprocal_rule = false; - if (apply_reciprocal_rule) { - if (upper < kHighsInf) { - // Drop the upper bound permanently - upper = kHighsInf; - run_status = row_deletion ? highs.changeRowBounds(iX, lower, kHighsInf) : highs.changeColBounds(iX, lower, upper); - assert(run_status == HighsStatus::kOk); - drop_upper = true; - } - // continue; - } - } else { - // Bound can be dropped permanently - assert(model_status == HighsModelStatus::kInfeasible); - lower = -kHighsInf; - } - } + // Drop the lower bound temporarily + run_status = row_deletion + ? highs.changeRowBounds(iX, -kHighsInf, upper) + : highs.changeColBounds(iX, -kHighsInf, upper); + assert(run_status == HighsStatus::kOk); + run_status = highs.run(); + assert(run_status == HighsStatus::kOk); + HighsModelStatus model_status = highs.getModelStatus(); + if (model_status == HighsModelStatus::kOptimal) { + // Now feasible, so restore the lower bound + run_status = row_deletion ? highs.changeRowBounds(iX, lower, upper) + : highs.changeColBounds(iX, lower, upper); + assert(run_status == HighsStatus::kOk); + // If the lower bound must be kept, then any finite upper bound + // must be dropped + const bool apply_reciprocal_rule = false; + if (apply_reciprocal_rule) { + if (upper < kHighsInf) { + // Drop the upper bound permanently + upper = kHighsInf; + run_status = row_deletion + ? highs.changeRowBounds(iX, lower, kHighsInf) + : highs.changeColBounds(iX, lower, upper); + assert(run_status == HighsStatus::kOk); + drop_upper = true; + } + // continue; + } + } else { + // Bound can be dropped permanently + assert(model_status == HighsModelStatus::kInfeasible); + lower = -kHighsInf; + } + } if (upper < kHighsInf) { - // Drop the upper bound temporarily - run_status = row_deletion ? highs.changeRowBounds(iX, lower, kHighsInf) : highs.changeColBounds(iX, lower, kHighsInf); - assert(run_status == HighsStatus::kOk); - run_status = highs.run(); - assert(run_status == HighsStatus::kOk); - HighsModelStatus model_status = highs.getModelStatus(); - // If the upper bound has been dropped due to the reciprical - // rule, the LP must be infeasible - if (drop_upper) assert(model_status == HighsModelStatus::kInfeasible); - if (model_status == HighsModelStatus::kOptimal) { - // Now feasible, so restore the upper bound - run_status = row_deletion ? highs.changeRowBounds(iX, lower, upper) : highs.changeColBounds(iX, lower, upper); - assert(run_status == HighsStatus::kOk); - } else { - // Bound can be dropped permanently - assert(model_status == HighsModelStatus::kInfeasible); - upper = kHighsInf; - } + // Drop the upper bound temporarily + run_status = row_deletion ? highs.changeRowBounds(iX, lower, kHighsInf) + : highs.changeColBounds(iX, lower, kHighsInf); + assert(run_status == HighsStatus::kOk); + run_status = highs.run(); + assert(run_status == HighsStatus::kOk); + HighsModelStatus model_status = highs.getModelStatus(); + // If the upper bound has been dropped due to the reciprical + // rule, the LP must be infeasible + if (drop_upper) assert(model_status == HighsModelStatus::kInfeasible); + if (model_status == HighsModelStatus::kOptimal) { + // Now feasible, so restore the upper bound + run_status = row_deletion ? highs.changeRowBounds(iX, lower, upper) + : highs.changeColBounds(iX, lower, upper); + assert(run_status == HighsStatus::kOk); + } else { + // Bound can be dropped permanently + assert(model_status == HighsModelStatus::kInfeasible); + upper = kHighsInf; + } } const bool debug_bound_change = true; if (debug_bound_change) { - // Check bounds have been changed correctly - double check_lower; - double check_upper; - double check_cost; - HighsInt check_num_ix; - HighsInt check_num_nz; - run_status = row_deletion ? - highs.getRows(iX, iX, check_num_ix, &check_lower, &check_upper, check_num_nz, nullptr, nullptr, nullptr) : - highs.getCols(iX, iX, check_num_ix, &check_cost, &check_lower, &check_upper, check_num_nz, nullptr, nullptr, nullptr); - assert(run_status == HighsStatus::kOk); - assert(check_lower == lower); - assert(check_upper == upper); + // Check bounds have been changed correctly + double check_lower; + double check_upper; + double check_cost; + HighsInt check_num_ix; + HighsInt check_num_nz; + run_status = + row_deletion + ? highs.getRows(iX, iX, check_num_ix, &check_lower, + &check_upper, check_num_nz, nullptr, nullptr, + nullptr) + : highs.getCols(iX, iX, check_num_ix, &check_cost, &check_lower, + &check_upper, check_num_nz, nullptr, nullptr, + nullptr); + assert(run_status == HighsStatus::kOk); + assert(check_lower == lower); + assert(check_upper == upper); } HighsInt iss_bound_status = kIisBoundStatusNull; if (lower <= -kHighsInf) { - if (upper >= kHighsInf) { - if (row_deletion) { - // Free rows can be dropped - iss_bound_status = kIisBoundStatusDropped; - } else { - // Free columns can only be dropped if they are empty - iss_bound_status = kIisBoundStatusFree; - } - } else { - iss_bound_status = kIisBoundStatusUpper; - } + if (upper >= kHighsInf) { + if (row_deletion) { + // Free rows can be dropped + iss_bound_status = kIisBoundStatusDropped; + } else { + // Free columns can only be dropped if they are empty + iss_bound_status = kIisBoundStatusFree; + } + } else { + iss_bound_status = kIisBoundStatusUpper; + } } else { - if (upper >= kHighsInf) { - iss_bound_status = kIisBoundStatusLower; - } else { - // FX or BX: shouldn't happen - iss_bound_status = kIisBoundStatusBoxed; - } + if (upper >= kHighsInf) { + iss_bound_status = kIisBoundStatusLower; + } else { + // FX or BX: shouldn't happen + iss_bound_status = kIisBoundStatusBoxed; + } } assert(iss_bound_status != kIisBoundStatusNull); assert(iss_bound_status != kIisBoundStatusBoxed); if (row_deletion) { - this->row_bound_[iX] = iss_bound_status; + this->row_bound_[iX] = iss_bound_status; } else { - this->col_bound_[iX] = iss_bound_status; + this->col_bound_[iX] = iss_bound_status; } highsLogUser(log_options, HighsLogType::kInfo, "%s %d has status %s\n", - type.c_str(), int(iX), iisBoundStatusToString(iss_bound_status).c_str()); + type.c_str(), int(iX), + iisBoundStatusToString(iss_bound_status).c_str()); } if (k == 1) continue; // End of first pass: look to simplify second pass @@ -359,20 +448,22 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options) { if (row_deletion) { // Mark empty columns as dropped for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { - bool empty_col = true; - for (HighsInt iEl = lp.a_matrix_.start_[iCol]; - iEl < lp.a_matrix_.start_[iCol + 1]; iEl++) { - if (this->row_bound_[lp.a_matrix_.index_[iEl]] != kIisBoundStatusDropped) { - empty_col = false; - break; - } - } - if (empty_col) { - highsLogUser(log_options, HighsLogType::kInfo, "Col %d has status Dropped: Empty\n", int(iCol)); - this->col_bound_[iCol] = kIisBoundStatusDropped; - run_status = highs.changeColBounds(iCol, -kHighsInf, kHighsInf); - assert(run_status == HighsStatus::kOk); - } + bool empty_col = true; + for (HighsInt iEl = lp.a_matrix_.start_[iCol]; + iEl < lp.a_matrix_.start_[iCol + 1]; iEl++) { + if (this->row_bound_[lp.a_matrix_.index_[iEl]] != + kIisBoundStatusDropped) { + empty_col = false; + break; + } + } + if (empty_col) { + highsLogUser(log_options, HighsLogType::kInfo, + "Col %d has status Dropped: Empty\n", int(iCol)); + this->col_bound_[iCol] = kIisBoundStatusDropped; + run_status = highs.changeColBounds(iCol, -kHighsInf, kHighsInf); + assert(run_status == HighsStatus::kOk); + } } } this->report("End of pass 1", incumbent_lp); diff --git a/src/lp_data/HighsIis.h b/src/lp_data/HighsIis.h index 353c32520c..f9f418c131 100644 --- a/src/lp_data/HighsIis.h +++ b/src/lp_data/HighsIis.h @@ -18,11 +18,11 @@ enum IisBoundStatus { kIisBoundStatusDropped = -1, - kIisBoundStatusNull, // 0 - kIisBoundStatusFree, // 1 - kIisBoundStatusLower, // 2 - kIisBoundStatusUpper, // 3 - kIisBoundStatusBoxed // 4 + kIisBoundStatusNull, // 0 + kIisBoundStatusFree, // 1 + kIisBoundStatusLower, // 2 + kIisBoundStatusUpper, // 3 + kIisBoundStatusBoxed // 4 }; class HighsIis { @@ -37,9 +37,10 @@ class HighsIis { void removeCol(const HighsInt col); void removeRow(const HighsInt row); HighsStatus getData(const HighsLp& lp, const HighsOptions& options, - const std::vector& dual_ray_value); + const HighsBasis& basis, const double* dual_ray_value); - HighsStatus compute(const HighsLp& lp, const HighsOptions& options); + HighsStatus compute(const HighsLp& lp, const HighsOptions& options, + const HighsBasis* basis = nullptr); bool trivial(const HighsLp& lp, const HighsOptions& options); diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 5c728e5e63..df68c14cdc 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -1592,14 +1592,13 @@ HighsStatus Highs::computeIisInterface() { return HighsStatus::kError; } } - return this->iis_.getData(lp, options_, dual_ray_value); + return this->iis_.getData(lp, options_, basis_, dual_ray_value.data()); } HighsStatus Highs::extractIis(HighsInt& num_iis_col, HighsInt& num_iis_row, - HighsInt* iis_col_index, - HighsInt* iis_row_index, - HighsInt* iis_col_bound, - HighsInt* iis_row_bound) { + HighsInt* iis_col_index, HighsInt* iis_row_index, + HighsInt* iis_col_bound, + HighsInt* iis_row_bound) { assert(this->iis_.valid_); num_iis_col = this->iis_.col_index_.size(); num_iis_row = this->iis_.row_index_.size(); From 2dfca52bce7d185ca6188b68527379f42b0080eb Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sun, 9 Jun 2024 00:38:57 +0100 Subject: [PATCH 018/194] Added the columns and rows for the elasticity filter - coresponding to the original columns --- src/lp_data/Highs.cpp | 4 +- src/lp_data/HighsIis.cpp | 98 +++++++++++++++++++++++++++++++--------- 2 files changed, 79 insertions(+), 23 deletions(-) diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index bb0b7177c5..8b37de5320 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -4240,7 +4240,9 @@ HighsStatus Highs::returnFromHighs(HighsStatus highs_return_status) { const bool dimensions_ok = lpDimensionsOk("returnFromHighs", model_.lp_, options_.log_options); if (!dimensions_ok) { - printf("LP Dimension error in returnFromHighs()\n"); + highsLogDev(options_.log_options, HighsLogType::kError, + "LP Dimension error in returnFromHighs()\n"); + return_status = HighsStatus::kError; } assert(dimensions_ok); if (ekk_instance_.status_.has_nla) { diff --git a/src/lp_data/HighsIis.cpp b/src/lp_data/HighsIis.cpp index 548019c543..0dc8baaa6d 100644 --- a/src/lp_data/HighsIis.cpp +++ b/src/lp_data/HighsIis.cpp @@ -232,44 +232,97 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, HighsStatus run_status = highs.passModel(lp); assert(run_status == HighsStatus::kOk); - /* // Elasticity filter // // Constraints L <= Ax <= U; l <= x <= u // // Transformed to // - // L <= + // L <= Ax + e_L - e_U <= U, + // + // l <= x + e_l - e_u <= u, + // + // where the elastic variables are not used if the corresponding bound is infinite. + // + // x is free, and the objective is the sum of the elastic variables. + // // Determine the number of lower and upper elastic variables for // columns and rows - std::vector col_lower_evar; - std::vector col_upper_evar; - std::vector row_lower_evar; - std::vector row_upper_evar; + // + // col_evar lists the column indices corresponding to the entries in + // erow_bound so that the results can be interpreted + std::vector col_evar; + std::vector row_evar; + std::vector erow_bound; std::vector erow_lower; std::vector erow_upper; std::vector erow_start; std::vector erow_index; std::vector erow_value; - HighsInt nz_offset = 0; erow_start.push_back(0); - col_offset.assign(lp.num_col_, 0); + HighsInt evar_ix = lp.num_col_; for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { - if (lp.col_lower_[iCol] > -kHighsInf) { - col_lower_evar.push_back(iCol); - erow_index.push_back(iCol); + // Free columns have no erow + const double lower = lp.col_lower_[iCol]; + const double upper = lp.col_upper_[iCol]; + if (lower <= -kHighsInf && upper >= kHighsInf) continue; + erow_lower.push_back(lower); + erow_upper.push_back(upper); + // Define the entry for x[iCol] + erow_index.push_back(iCol); + erow_value.push_back(1); + if (lower > -kHighsInf) { + // New e_l variable + col_evar.push_back(iCol); + erow_bound.push_back(lower); + erow_index.push_back(evar_ix); erow_value.push_back(1); - erow_index.push_back(col_lower_evar.size()); - erow_value.push_back(1); - erow_lower + evar_ix++; } - } - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { - if (lp.col_upper_[iCol] < kHighsInf) { - col_upper_evar.push_back(iCol); - + if (upper < kHighsInf) { + // New e_u variable + col_evar.push_back(iCol); + erow_bound.push_back(upper); + erow_index.push_back(evar_ix); + erow_value.push_back(-1); + evar_ix++; } + erow_start.push_back(erow_index.size()); + HighsInt row_nz = erow_start[erow_start.size()-1] - erow_start[erow_start.size()-2]; + printf("eRow for column %d has %d nonzeros\n", int(iCol), int(row_nz)); + assert(row_nz == 2 || row_nz == 3); } + HighsInt num_new_col = col_evar.size(); + HighsInt num_new_row = erow_start.size()-1; + HighsInt num_new_nz = erow_start[num_new_row]; + printf("Elasticity filter: For columns there are %d variables and %d constraints\n", int(num_new_col), int(num_new_row)); + const bool write_model = true; + // Free the original columns + std::vector col_lower; + std::vector col_upper; + col_lower.assign(lp.num_col_, -kHighsInf); + col_upper.assign(lp.num_col_, kHighsInf); + run_status = highs.changeColsBounds(0, lp.num_col_-1, col_lower.data(), col_upper.data()); + assert(run_status == HighsStatus::kOk); + // Add the new columns + std::vector ecol_cost; + std::vector ecol_lower; + std::vector ecol_upper; + ecol_cost.assign(num_new_col, 1); + ecol_lower.assign(num_new_col, 0); + ecol_upper.assign(num_new_col, kHighsInf); + run_status = highs.addCols(num_new_col, ecol_cost.data(), ecol_lower.data(), ecol_upper.data(), + 0, nullptr, nullptr, nullptr); + assert(run_status == HighsStatus::kOk); + + // Add the new rows + run_status = highs.addRows(num_new_row, erow_lower.data(), erow_upper.data(), + num_new_nz, erow_start.data(), erow_index.data(), erow_value.data()); + assert(run_status == HighsStatus::kOk); + // Add the columns corresponding to the e_L and e_U variables for + // the constraints + + /* for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { if (lp.row_lower_[iRow] > -kHighsInf) { row_lower_evar.push_back(iRow); @@ -285,12 +338,14 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, for ( */ - const bool write_model = false; if (write_model) { + bool output_flag; + run_status = highs.getOptionValue("output_flag", output_flag); highs.setOptionValue("output_flag", true); highs.writeModel(""); - highs.setOptionValue("output_flag", false); + highs.setOptionValue("output_flag", output_flag); } + assert(666==999); // Zero the objective std::vector cost; cost.assign(lp.num_col_, 0); @@ -313,7 +368,6 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, highs.writeSolution("", kSolutionStylePretty); } - // assert(666==999); // Pass twice: rows before columns, or columns before rows, according to // row_priority for (HighsInt k = 0; k < 2; k++) { From 4db5e14693812a870892c418b13d7726afce65f3 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sun, 9 Jun 2024 11:09:38 +0100 Subject: [PATCH 019/194] Now formulating and solving the e-LP with helpful names for new cols and rows --- src/lp_data/HighsIis.cpp | 81 ++++++++++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 12 deletions(-) diff --git a/src/lp_data/HighsIis.cpp b/src/lp_data/HighsIis.cpp index 0dc8baaa6d..eb6b3300f3 100644 --- a/src/lp_data/HighsIis.cpp +++ b/src/lp_data/HighsIis.cpp @@ -184,6 +184,7 @@ HighsStatus HighsIis::getData(const HighsLp& lp, const HighsOptions& options, to_lp.col_cost_.push_back(0); to_lp.col_lower_.push_back(lp.col_lower_[from_col[iCol]]); to_lp.col_upper_.push_back(lp.col_upper_[from_col[iCol]]); + to_lp.col_names_.push_back(lp.col_names_[from_col[iCol]]); for (HighsInt iEl = lp.a_matrix_.start_[from_col[iCol]]; iEl < lp.a_matrix_.start_[from_col[iCol] + 1]; iEl++) { HighsInt iRow = lp.a_matrix_.index_[iEl]; @@ -197,6 +198,7 @@ HighsStatus HighsIis::getData(const HighsLp& lp, const HighsOptions& options, for (HighsInt iRow = 0; iRow < to_num_row; iRow++) { to_lp.row_lower_.push_back(lp.row_lower_[from_row[iRow]]); to_lp.row_upper_.push_back(lp.row_upper_[from_row[iRow]]); + to_lp.row_names_.push_back(lp.row_names_[from_row[iRow]]); } if (this->compute(to_lp, options) != HighsStatus::kOk) return HighsStatus::kError; @@ -231,6 +233,7 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, const HighsLp& incumbent_lp = highs.getLp(); HighsStatus run_status = highs.passModel(lp); assert(run_status == HighsStatus::kOk); + if (basis) highs.setBasis(*basis); // Elasticity filter // @@ -253,27 +256,33 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, // erow_bound so that the results can be interpreted std::vector col_evar; std::vector row_evar; + std::vector erow_name; std::vector erow_bound; std::vector erow_lower; std::vector erow_upper; std::vector erow_start; std::vector erow_index; std::vector erow_value; + std::vector ecol_name; + HighsInt previous_num_col = highs.getNumCol(); + HighsInt previous_num_row = highs.getNumRow(); erow_start.push_back(0); HighsInt evar_ix = lp.num_col_; for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { - // Free columns have no erow const double lower = lp.col_lower_[iCol]; const double upper = lp.col_upper_[iCol]; + // Free columns have no erow if (lower <= -kHighsInf && upper >= kHighsInf) continue; erow_lower.push_back(lower); erow_upper.push_back(upper); + erow_name.push_back(lp.col_names_[iCol]+"_erow"); // Define the entry for x[iCol] erow_index.push_back(iCol); erow_value.push_back(1); if (lower > -kHighsInf) { // New e_l variable col_evar.push_back(iCol); + ecol_name.push_back(lp.col_names_[iCol]+"_lower"); erow_bound.push_back(lower); erow_index.push_back(evar_ix); erow_value.push_back(1); @@ -282,6 +291,7 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, if (upper < kHighsInf) { // New e_u variable col_evar.push_back(iCol); + ecol_name.push_back(lp.col_names_[iCol]+"_upper"); erow_bound.push_back(upper); erow_index.push_back(evar_ix); erow_value.push_back(-1); @@ -319,32 +329,79 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, run_status = highs.addRows(num_new_row, erow_lower.data(), erow_upper.data(), num_new_nz, erow_start.data(), erow_index.data(), erow_value.data()); assert(run_status == HighsStatus::kOk); + for (HighsInt iCol = 0; iCol < num_new_col; iCol++) { + highs.passColName(previous_num_col+iCol, ecol_name[iCol]); + } + for (HighsInt iRow = 0; iRow < num_new_row; iRow++) { + highs.passRowName(previous_num_row+iRow, erow_name[iRow]); + } + if (write_model) { + printf("\nAfter adding e-rows\n=============\n"); + bool output_flag; + run_status = highs.getOptionValue("output_flag", output_flag); + highs.setOptionValue("output_flag", true); + highs.writeModel(""); + highs.setOptionValue("output_flag", output_flag); + } + // Add the columns corresponding to the e_L and e_U variables for // the constraints - /* + ecol_name.clear(); + std::vector ecol_start; + std::vector ecol_index; + std::vector ecol_value; + ecol_start.push_back(0); for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { - if (lp.row_lower_[iRow] > -kHighsInf) { - row_lower_evar.push_back(iRow); - + const double lower = lp.row_lower_[iRow]; + const double upper = lp.row_upper_[iRow]; + if (lower > -kHighsInf) { + ecol_index.push_back(iRow); + ecol_value.push_back(1); + ecol_start.push_back(ecol_index.size()); + std::string name = lp.row_names_[iRow]+"_lower"; + printf("E-column for row %d lower has name %s\n", int(iRow), name.c_str()); + ecol_name.push_back(name); + evar_ix++; } - } - for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { - if (lp.row_lower_[iRow] > -kHighsInf) { - row_lower_evar.push_back(iRow); - + if (upper < kHighsInf) { + ecol_index.push_back(iRow); + ecol_value.push_back(-1); + ecol_start.push_back(ecol_index.size()); + std::string name = lp.row_names_[iRow]+"_upper"; + printf("E-column for row %d lower has name %s\n", int(iRow), name.c_str()); + ecol_name.push_back(name); + evar_ix++; } } - for ( - */ + num_new_col = ecol_start.size()-1; + num_new_nz = ecol_start[num_new_col]; + ecol_cost.assign(num_new_col, 1); + ecol_lower.assign(num_new_col, 0); + ecol_upper.assign(num_new_col, kHighsInf); + previous_num_col = highs.getNumCol(); + run_status = highs.addCols(num_new_col, ecol_cost.data(), ecol_lower.data(), ecol_upper.data(), + num_new_nz, ecol_start.data(), ecol_index.data(), ecol_value.data()); + assert(run_status == HighsStatus::kOk); + for (HighsInt iCol = 0; iCol < num_new_col; iCol++) { + highs.passColName(previous_num_col+iCol, ecol_name[iCol]); + } if (write_model) { bool output_flag; + printf("\nAfter adding e-cols\n=============\n"); run_status = highs.getOptionValue("output_flag", output_flag); highs.setOptionValue("output_flag", true); highs.writeModel(""); highs.setOptionValue("output_flag", output_flag); } + run_status = highs.run(); + assert(run_status == HighsStatus::kOk); + highs.writeSolution("", kSolutionStylePretty); + + + + assert(666==999); // Zero the objective std::vector cost; From 6894e8984f59f4858426dd6a18ba7497763a6a48 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sun, 9 Jun 2024 12:28:25 +0100 Subject: [PATCH 020/194] Time for some re-name of identifiers --- src/lp_data/HighsIis.cpp | 47 ++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/src/lp_data/HighsIis.cpp b/src/lp_data/HighsIis.cpp index eb6b3300f3..48c0872902 100644 --- a/src/lp_data/HighsIis.cpp +++ b/src/lp_data/HighsIis.cpp @@ -234,9 +234,11 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, HighsStatus run_status = highs.passModel(lp); assert(run_status == HighsStatus::kOk); if (basis) highs.setBasis(*basis); - + // // Elasticity filter // + // Construct the e-LP: + // // Constraints L <= Ax <= U; l <= x <= u // // Transformed to @@ -254,9 +256,13 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, // // col_evar lists the column indices corresponding to the entries in // erow_bound so that the results can be interpreted + // + // row_evar lists the row indices corresponding to the entries in + // ecol_bound so that the results can be interpreted std::vector col_evar; std::vector row_evar; std::vector erow_name; + std::vector ecol_bound; std::vector erow_bound; std::vector erow_lower; std::vector erow_upper; @@ -266,6 +272,7 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, std::vector ecol_name; HighsInt previous_num_col = highs.getNumCol(); HighsInt previous_num_row = highs.getNumRow(); + HighsInt col_ecol_offset = previous_num_col; erow_start.push_back(0); HighsInt evar_ix = lp.num_col_; for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { @@ -324,7 +331,6 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, run_status = highs.addCols(num_new_col, ecol_cost.data(), ecol_lower.data(), ecol_upper.data(), 0, nullptr, nullptr, nullptr); assert(run_status == HighsStatus::kOk); - // Add the new rows run_status = highs.addRows(num_new_row, erow_lower.data(), erow_upper.data(), num_new_nz, erow_start.data(), erow_index.data(), erow_value.data()); @@ -343,10 +349,8 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, highs.writeModel(""); highs.setOptionValue("output_flag", output_flag); } - // Add the columns corresponding to the e_L and e_U variables for // the constraints - ecol_name.clear(); std::vector ecol_start; std::vector ecol_index; @@ -356,21 +360,25 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, const double lower = lp.row_lower_[iRow]; const double upper = lp.row_upper_[iRow]; if (lower > -kHighsInf) { + // Create an e-var for the row lower bound + row_evar.push_back(iRow); + ecol_name.push_back(lp.row_names_[iRow]+"_lower"); + ecol_bound.push_back(lower); + // Define the sub-matrix column ecol_index.push_back(iRow); ecol_value.push_back(1); ecol_start.push_back(ecol_index.size()); - std::string name = lp.row_names_[iRow]+"_lower"; - printf("E-column for row %d lower has name %s\n", int(iRow), name.c_str()); - ecol_name.push_back(name); evar_ix++; } if (upper < kHighsInf) { + // Create an e-var for the row upper bound + row_evar.push_back(iRow); + ecol_name.push_back(lp.row_names_[iRow]+"_upper"); + ecol_bound.push_back(upper); + // Define the sub-matrix column ecol_index.push_back(iRow); ecol_value.push_back(-1); ecol_start.push_back(ecol_index.size()); - std::string name = lp.row_names_[iRow]+"_upper"; - printf("E-column for row %d lower has name %s\n", int(iRow), name.c_str()); - ecol_name.push_back(name); evar_ix++; } } @@ -380,6 +388,7 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, ecol_lower.assign(num_new_col, 0); ecol_upper.assign(num_new_col, kHighsInf); previous_num_col = highs.getNumCol(); + HighsInt row_ecol_offset = previous_num_col; run_status = highs.addCols(num_new_col, ecol_cost.data(), ecol_lower.data(), ecol_upper.data(), num_new_nz, ecol_start.data(), ecol_index.data(), ecol_value.data()); assert(run_status == HighsStatus::kOk); @@ -398,8 +407,24 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, run_status = highs.run(); assert(run_status == HighsStatus::kOk); highs.writeSolution("", kSolutionStylePretty); + HighsModelStatus model_status = highs.getModelStatus(); + assert(model_status == HighsModelStatus::kOptimal); - + const HighsSolution& solution = highs.getSolution(); + // Now fix e-variables that are positive and re-solve until e-LP is infeasible + for (;;) { + for (HighsInt eCol = 0; eCol < col_evar.size(); eCol++) { + HighsInt iCol = col_evar[eCol]; + printf("E-col %d corresponds to column %d with bound %g and has solution value %g\n", + int(eCol), int(iCol), erow_bound[eCol], solution.col_value[col_ecol_offset+eCol]); + } + for (HighsInt eRow = 0; eRow < row_evar.size(); eRow++) { + HighsInt iRow = row_evar[eRow]; + printf("E-row %d corresponds to row %d with bound %g and has solution value %g\n", + int(eRow), int(iRow), ecol_bound[eRow], solution.col_value[row_ecol_offset+eRow]); + } + break; + } assert(666==999); From dac3562690a9953ae4adc2156e86632764e48753 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sun, 9 Jun 2024 12:36:38 +0100 Subject: [PATCH 021/194] Time for some re-name of identifiers --- src/lp_data/HighsIis.cpp | 46 ++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/lp_data/HighsIis.cpp b/src/lp_data/HighsIis.cpp index 48c0872902..fe859373e7 100644 --- a/src/lp_data/HighsIis.cpp +++ b/src/lp_data/HighsIis.cpp @@ -254,16 +254,16 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, // Determine the number of lower and upper elastic variables for // columns and rows // - // col_evar lists the column indices corresponding to the entries in - // erow_bound so that the results can be interpreted + // col_of_ecol lists the column indices corresponding to the entries in + // bound_of_col_of_ecol so that the results can be interpreted // - // row_evar lists the row indices corresponding to the entries in - // ecol_bound so that the results can be interpreted - std::vector col_evar; - std::vector row_evar; + // row_of_ecol lists the row indices corresponding to the entries in + // bound_of_row_of_ecol so that the results can be interpreted + std::vector col_of_ecol; + std::vector row_of_ecol; std::vector erow_name; - std::vector ecol_bound; - std::vector erow_bound; + std::vector bound_of_row_of_ecol; + std::vector bound_of_col_of_ecol; std::vector erow_lower; std::vector erow_upper; std::vector erow_start; @@ -288,18 +288,18 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, erow_value.push_back(1); if (lower > -kHighsInf) { // New e_l variable - col_evar.push_back(iCol); + col_of_ecol.push_back(iCol); ecol_name.push_back(lp.col_names_[iCol]+"_lower"); - erow_bound.push_back(lower); + bound_of_col_of_ecol.push_back(lower); erow_index.push_back(evar_ix); erow_value.push_back(1); evar_ix++; } if (upper < kHighsInf) { // New e_u variable - col_evar.push_back(iCol); + col_of_ecol.push_back(iCol); ecol_name.push_back(lp.col_names_[iCol]+"_upper"); - erow_bound.push_back(upper); + bound_of_col_of_ecol.push_back(upper); erow_index.push_back(evar_ix); erow_value.push_back(-1); evar_ix++; @@ -309,7 +309,7 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, printf("eRow for column %d has %d nonzeros\n", int(iCol), int(row_nz)); assert(row_nz == 2 || row_nz == 3); } - HighsInt num_new_col = col_evar.size(); + HighsInt num_new_col = col_of_ecol.size(); HighsInt num_new_row = erow_start.size()-1; HighsInt num_new_nz = erow_start[num_new_row]; printf("Elasticity filter: For columns there are %d variables and %d constraints\n", int(num_new_col), int(num_new_row)); @@ -361,9 +361,9 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, const double upper = lp.row_upper_[iRow]; if (lower > -kHighsInf) { // Create an e-var for the row lower bound - row_evar.push_back(iRow); + row_of_ecol.push_back(iRow); ecol_name.push_back(lp.row_names_[iRow]+"_lower"); - ecol_bound.push_back(lower); + bound_of_row_of_ecol.push_back(lower); // Define the sub-matrix column ecol_index.push_back(iRow); ecol_value.push_back(1); @@ -372,9 +372,9 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, } if (upper < kHighsInf) { // Create an e-var for the row upper bound - row_evar.push_back(iRow); + row_of_ecol.push_back(iRow); ecol_name.push_back(lp.row_names_[iRow]+"_upper"); - ecol_bound.push_back(upper); + bound_of_row_of_ecol.push_back(upper); // Define the sub-matrix column ecol_index.push_back(iRow); ecol_value.push_back(-1); @@ -413,15 +413,15 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, const HighsSolution& solution = highs.getSolution(); // Now fix e-variables that are positive and re-solve until e-LP is infeasible for (;;) { - for (HighsInt eCol = 0; eCol < col_evar.size(); eCol++) { - HighsInt iCol = col_evar[eCol]; + for (HighsInt eCol = 0; eCol < col_of_ecol.size(); eCol++) { + HighsInt iCol = col_of_ecol[eCol]; printf("E-col %d corresponds to column %d with bound %g and has solution value %g\n", - int(eCol), int(iCol), erow_bound[eCol], solution.col_value[col_ecol_offset+eCol]); + int(eCol), int(iCol), bound_of_col_of_ecol[eCol], solution.col_value[col_ecol_offset+eCol]); } - for (HighsInt eRow = 0; eRow < row_evar.size(); eRow++) { - HighsInt iRow = row_evar[eRow]; + for (HighsInt eCol = 0; eCol < row_of_ecol.size(); eCol++) { + HighsInt iRow = row_of_ecol[eCol]; printf("E-row %d corresponds to row %d with bound %g and has solution value %g\n", - int(eRow), int(iRow), ecol_bound[eRow], solution.col_value[row_ecol_offset+eRow]); + int(eCol), int(iRow), bound_of_row_of_ecol[eCol], solution.col_value[row_ecol_offset+eCol]); } break; } From 55cc84e256ea7ff5809508f87996630d9aa55e86 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sun, 9 Jun 2024 14:27:51 +0100 Subject: [PATCH 022/194] Now refactor so that elasticity filter is performed as part of method that also computes min sum infeasibility solution --- check/TestIis.cpp | 9 +++--- src/lp_data/HighsIis.cpp | 62 +++++++++++++++++++++++++++++++++------- 2 files changed, 56 insertions(+), 15 deletions(-) diff --git a/check/TestIis.cpp b/check/TestIis.cpp index d69790d2c2..4bebd229db 100644 --- a/check/TestIis.cpp +++ b/check/TestIis.cpp @@ -133,7 +133,7 @@ void testIis(const std::string& model, const HighsIis& iis) { } } -void testMps(std::string& model) { +void testMps(std::string& model, const HighsInt iis_strategy) { std::string model_file = std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; Highs highs; @@ -143,7 +143,7 @@ void testMps(std::string& model) { highs.writeModel(""); REQUIRE(highs.run() == HighsStatus::kOk); REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); - // highs.setOptionValue("iis_strategy", kIisStrategyFromLpRowPriority); + highs.setOptionValue("iis_strategy", iis_strategy); REQUIRE(highs.computeIis() == HighsStatus::kOk); const HighsIis& iis = highs.getIis(); HighsInt num_iis_col = iis.col_index_.size(); @@ -313,7 +313,7 @@ TEST_CASE("lp-get-iis", "[iis]") { TEST_CASE("lp-get-iis-woodinfe", "[iis]") { std::string model = "woodinfe"; - testMps(model); + testMps(model, kIisStrategyFromRayRowPriority); } TEST_CASE("lp-get-iis-galenet", "[iis]") { @@ -358,5 +358,6 @@ TEST_CASE("lp-get-iis-galenet", "[iis]") { // // Hence only empty columns can be removed std::string model = "galenet"; - testMps(model); + testMps(model, kIisStrategyFromLpRowPriority); + testMps(model, kIisStrategyFromRayRowPriority); } diff --git a/src/lp_data/HighsIis.cpp b/src/lp_data/HighsIis.cpp index fe859373e7..3a428ed97d 100644 --- a/src/lp_data/HighsIis.cpp +++ b/src/lp_data/HighsIis.cpp @@ -261,7 +261,6 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, // bound_of_row_of_ecol so that the results can be interpreted std::vector col_of_ecol; std::vector row_of_ecol; - std::vector erow_name; std::vector bound_of_row_of_ecol; std::vector bound_of_col_of_ecol; std::vector erow_lower; @@ -269,7 +268,11 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, std::vector erow_start; std::vector erow_index; std::vector erow_value; + // Accumulate names for ecols and erows, re-using ecol_name for the + // names of row ecols after defining the names of col ecols std::vector ecol_name; + std::vector erow_name; + // When defining names, need to know the column number HighsInt previous_num_col = highs.getNumCol(); HighsInt previous_num_row = highs.getNumRow(); HighsInt col_ecol_offset = previous_num_col; @@ -282,14 +285,14 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, if (lower <= -kHighsInf && upper >= kHighsInf) continue; erow_lower.push_back(lower); erow_upper.push_back(upper); - erow_name.push_back(lp.col_names_[iCol]+"_erow"); + erow_name.push_back("row_"+std::to_string(iCol)+"_"+lp.col_names_[iCol]+"_erow"); // Define the entry for x[iCol] erow_index.push_back(iCol); erow_value.push_back(1); if (lower > -kHighsInf) { // New e_l variable col_of_ecol.push_back(iCol); - ecol_name.push_back(lp.col_names_[iCol]+"_lower"); + ecol_name.push_back("col_"+std::to_string(iCol)+"_"+lp.col_names_[iCol]+"_lower"); bound_of_col_of_ecol.push_back(lower); erow_index.push_back(evar_ix); erow_value.push_back(1); @@ -298,7 +301,7 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, if (upper < kHighsInf) { // New e_u variable col_of_ecol.push_back(iCol); - ecol_name.push_back(lp.col_names_[iCol]+"_upper"); + ecol_name.push_back("col_"+std::to_string(iCol)+"_"+lp.col_names_[iCol]+"_upper"); bound_of_col_of_ecol.push_back(upper); erow_index.push_back(evar_ix); erow_value.push_back(-1); @@ -362,7 +365,7 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, if (lower > -kHighsInf) { // Create an e-var for the row lower bound row_of_ecol.push_back(iRow); - ecol_name.push_back(lp.row_names_[iRow]+"_lower"); + ecol_name.push_back("row_"+std::to_string(iRow)+"_"+lp.row_names_[iRow]+"_lower"); bound_of_row_of_ecol.push_back(lower); // Define the sub-matrix column ecol_index.push_back(iRow); @@ -373,7 +376,7 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, if (upper < kHighsInf) { // Create an e-var for the row upper bound row_of_ecol.push_back(iRow); - ecol_name.push_back(lp.row_names_[iRow]+"_upper"); + ecol_name.push_back("row_"+std::to_string(iRow)+"_"+lp.row_names_[iRow]+"_upper"); bound_of_row_of_ecol.push_back(upper); // Define the sub-matrix column ecol_index.push_back(iRow); @@ -412,20 +415,57 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, const HighsSolution& solution = highs.getSolution(); // Now fix e-variables that are positive and re-solve until e-LP is infeasible + HighsInt loop_k = 0; for (;;) { + printf("\nElasticity filter pass %d\n==============\n", int(loop_k)); + HighsInt num_fixed = 0; for (HighsInt eCol = 0; eCol < col_of_ecol.size(); eCol++) { HighsInt iCol = col_of_ecol[eCol]; - printf("E-col %d corresponds to column %d with bound %g and has solution value %g\n", - int(eCol), int(iCol), bound_of_col_of_ecol[eCol], solution.col_value[col_ecol_offset+eCol]); + if (solution.col_value[col_ecol_offset+eCol] > options.primal_feasibility_tolerance) { + printf("E-col %2d (column %2d) corresponds to column %2d with bound %g and has solution value %g\n", + int(eCol), int(col_ecol_offset+eCol), int(iCol), bound_of_col_of_ecol[eCol], solution.col_value[col_ecol_offset+eCol]); + highs.changeColBounds(col_ecol_offset+eCol, 0, 0); + num_fixed++; + } } for (HighsInt eCol = 0; eCol < row_of_ecol.size(); eCol++) { HighsInt iRow = row_of_ecol[eCol]; - printf("E-row %d corresponds to row %d with bound %g and has solution value %g\n", - int(eCol), int(iRow), bound_of_row_of_ecol[eCol], solution.col_value[row_ecol_offset+eCol]); + if (solution.col_value[row_ecol_offset+eCol] > options.primal_feasibility_tolerance) { + printf("E-row %2d (column %2d) corresponds to row %2d with bound %g and has solution value %g\n", + int(eCol), int(row_ecol_offset+eCol), int(iRow), bound_of_row_of_ecol[eCol], solution.col_value[row_ecol_offset+eCol]); + highs.changeColBounds(row_ecol_offset+eCol, 0, 0); + num_fixed++; + } } - break; + assert(num_fixed>0); + run_status = highs.run(); + assert(run_status == HighsStatus::kOk); + highs.writeSolution("", kSolutionStylePretty); + HighsModelStatus model_status = highs.getModelStatus(); + if (model_status == HighsModelStatus::kInfeasible) break; + loop_k++; + if (loop_k>10) assert(1666==1999); } + HighsInt num_enforced_col_ecol = 0; + HighsInt num_enforced_row_ecol = 0; + for (HighsInt eCol = 0; eCol < col_of_ecol.size(); eCol++) { + HighsInt iCol = col_of_ecol[eCol]; + if (incumbent_lp.col_upper_[col_ecol_offset+eCol] == 0) { + num_enforced_col_ecol++; + printf("Col e-col %2d (column %2d) corresponds to column %2d with bound %g and is enforced\n", + int(eCol), int(col_ecol_offset+eCol), int(iCol), bound_of_col_of_ecol[eCol]); + } + } + for (HighsInt eCol = 0; eCol < row_of_ecol.size(); eCol++) { + HighsInt iRow = row_of_ecol[eCol]; + if (incumbent_lp.col_upper_[row_ecol_offset+eCol] == 0) { + num_enforced_row_ecol++; + printf("Row e-col %2d (column %2d) corresponds to row %2d with bound %g and is enforced\n", + int(eCol), int(row_ecol_offset+eCol), int(iRow), bound_of_row_of_ecol[eCol]); + } + } + printf("\nElasticity filter after %d passes enforces bounds on %d cols and %d rows\n", int(loop_k), int(num_enforced_col_ecol), int(num_enforced_row_ecol)); assert(666==999); // Zero the objective From 53c23b7f9010b8250e1e93ab049a5958a5c1fb50 Mon Sep 17 00:00:00 2001 From: jajhall Date: Mon, 10 Jun 2024 09:45:03 +0100 Subject: [PATCH 023/194] Reproduced elastic filter in computeInfeasibleRows --- src/Highs.h | 2 + src/lp_data/HighsInterface.cpp | 246 +++++++++++++++++++++++++++++++++ 2 files changed, 248 insertions(+) diff --git a/src/Highs.h b/src/Highs.h index 1923d32dc9..27c5a936b8 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -1515,6 +1515,8 @@ class Highs { HighsStatus getRangingInterface(); HighsStatus computeIisInterface(); + HighsStatus computeInfeasibleRows(const bool elastic_columns = false, + HighsInt* infeasible_row = nullptr); HighsStatus extractIis(HighsInt& num_iis_col, HighsInt& num_iis_row, HighsInt* iis_col_index, HighsInt* iis_row_index, HighsInt* iis_col_bound, HighsInt* iis_row_bound); diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index df68c14cdc..7ad4b4d12e 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -1595,6 +1595,252 @@ HighsStatus Highs::computeIisInterface() { return this->iis_.getData(lp, options_, basis_, dual_ray_value.data()); } +HighsStatus Highs::computeInfeasibleRows(const bool elastic_columns, + HighsInt* infeasible_row) { + // Elasticity filter + // + // Construct the e-LP: + // + // Constraints L <= Ax <= U; l <= x <= u + // + // Transformed to + // + // L <= Ax + e_L - e_U <= U, + // + // l <= x + e_l - e_u <= u, + // + // where the elastic variables are not used if the corresponding + // bound is infinite, and the elastic variables e_l and e_u are not + // used if elastic_columns is false + // + // x is free, and the objective is the sum of the elastic variables. + // + // Determine the number of lower and upper elastic variables for + // columns and rows + // + // col_of_ecol lists the column indices corresponding to the entries in + // bound_of_col_of_ecol so that the results can be interpreted + // + // row_of_ecol lists the row indices corresponding to the entries in + // bound_of_row_of_ecol so that the results can be interpreted + std::vector col_of_ecol; + std::vector row_of_ecol; + std::vector bound_of_row_of_ecol; + std::vector bound_of_col_of_ecol; + std::vector erow_lower; + std::vector erow_upper; + std::vector erow_start; + std::vector erow_index; + std::vector erow_value; + // Accumulate names for ecols and erows, re-using ecol_name for the + // names of row ecols after defining the names of col ecols + std::vector ecol_name; + std::vector erow_name; + std::vector ecol_cost; + std::vector ecol_lower; + std::vector ecol_upper; + const HighsLp lp = this->model_.lp_; + HighsInt evar_ix = lp.num_col_; + HighsStatus run_status; + const bool write_model = true; + HighsInt col_ecol_offset; + if (elastic_columns) { + // When defining names, need to know the column number + HighsInt previous_num_col = lp.num_col_; + HighsInt previous_num_row = lp.num_row_; + erow_start.push_back(0); + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + const double lower = lp.col_lower_[iCol]; + const double upper = lp.col_upper_[iCol]; + // Free columns have no erow + if (lower <= -kHighsInf && upper >= kHighsInf) continue; + erow_lower.push_back(lower); + erow_upper.push_back(upper); + erow_name.push_back("row_"+std::to_string(iCol)+"_"+lp.col_names_[iCol]+"_erow"); + // Define the entry for x[iCol] + erow_index.push_back(iCol); + erow_value.push_back(1); + if (lower > -kHighsInf) { + // New e_l variable + col_of_ecol.push_back(iCol); + ecol_name.push_back("col_"+std::to_string(iCol)+"_"+lp.col_names_[iCol]+"_lower"); + bound_of_col_of_ecol.push_back(lower); + erow_index.push_back(evar_ix); + erow_value.push_back(1); + evar_ix++; + } + if (upper < kHighsInf) { + // New e_u variable + col_of_ecol.push_back(iCol); + ecol_name.push_back("col_"+std::to_string(iCol)+"_"+lp.col_names_[iCol]+"_upper"); + bound_of_col_of_ecol.push_back(upper); + erow_index.push_back(evar_ix); + erow_value.push_back(-1); + evar_ix++; + } + erow_start.push_back(erow_index.size()); + HighsInt row_nz = erow_start[erow_start.size()-1] - erow_start[erow_start.size()-2]; + printf("eRow for column %d has %d nonzeros\n", int(iCol), int(row_nz)); + assert(row_nz == 2 || row_nz == 3); + } + HighsInt num_new_col = col_of_ecol.size(); + HighsInt num_new_row = erow_start.size()-1; + HighsInt num_new_nz = erow_start[num_new_row]; + printf("Elasticity filter: For columns there are %d variables and %d constraints\n", int(num_new_col), int(num_new_row)); + const bool write_model = true; + // Free the original columns + std::vector col_lower; + std::vector col_upper; + col_lower.assign(lp.num_col_, -kHighsInf); + col_upper.assign(lp.num_col_, kHighsInf); + run_status = this->changeColsBounds(0, lp.num_col_-1, col_lower.data(), col_upper.data()); + assert(run_status == HighsStatus::kOk); + // Add the new columns + ecol_cost.assign(num_new_col, 1); + ecol_lower.assign(num_new_col, 0); + ecol_upper.assign(num_new_col, kHighsInf); + run_status = this->addCols(num_new_col, ecol_cost.data(), ecol_lower.data(), ecol_upper.data(), + 0, nullptr, nullptr, nullptr); + assert(run_status == HighsStatus::kOk); + // Add the new rows + run_status = this->addRows(num_new_row, erow_lower.data(), erow_upper.data(), + num_new_nz, erow_start.data(), erow_index.data(), erow_value.data()); + assert(run_status == HighsStatus::kOk); + for (HighsInt iCol = 0; iCol < num_new_col; iCol++) { + this->passColName(previous_num_col+iCol, ecol_name[iCol]); + } + for (HighsInt iRow = 0; iRow < num_new_row; iRow++) { + this->passRowName(previous_num_row+iRow, erow_name[iRow]); + } + if (write_model) { + printf("\nAfter adding e-rows\n=============\n"); + bool output_flag; + run_status = this->getOptionValue("output_flag", output_flag); + this->setOptionValue("output_flag", true); + this->writeModel(""); + this->setOptionValue("output_flag", output_flag); + } + } + // Add the columns corresponding to the e_L and e_U variables for + // the constraints + ecol_name.clear(); + std::vector ecol_start; + std::vector ecol_index; + std::vector ecol_value; + ecol_start.push_back(0); + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { + const double lower = lp.row_lower_[iRow]; + const double upper = lp.row_upper_[iRow]; + if (lower > -kHighsInf) { + // Create an e-var for the row lower bound + row_of_ecol.push_back(iRow); + ecol_name.push_back("row_"+std::to_string(iRow)+"_"+lp.row_names_[iRow]+"_lower"); + bound_of_row_of_ecol.push_back(lower); + // Define the sub-matrix column + ecol_index.push_back(iRow); + ecol_value.push_back(1); + ecol_start.push_back(ecol_index.size()); + evar_ix++; + } + if (upper < kHighsInf) { + // Create an e-var for the row upper bound + row_of_ecol.push_back(iRow); + ecol_name.push_back("row_"+std::to_string(iRow)+"_"+lp.row_names_[iRow]+"_upper"); + bound_of_row_of_ecol.push_back(upper); + // Define the sub-matrix column + ecol_index.push_back(iRow); + ecol_value.push_back(-1); + ecol_start.push_back(ecol_index.size()); + evar_ix++; + } + } + HighsInt num_new_col = ecol_start.size()-1; + HighsInt num_new_nz = ecol_start[num_new_col]; + ecol_cost.assign(num_new_col, 1); + ecol_lower.assign(num_new_col, 0); + ecol_upper.assign(num_new_col, kHighsInf); + HighsInt previous_num_col = lp.num_col_; + HighsInt row_ecol_offset = previous_num_col; + run_status = this->addCols(num_new_col, ecol_cost.data(), ecol_lower.data(), ecol_upper.data(), + num_new_nz, ecol_start.data(), ecol_index.data(), ecol_value.data()); + assert(run_status == HighsStatus::kOk); + for (HighsInt iCol = 0; iCol < num_new_col; iCol++) { + this->passColName(previous_num_col+iCol, ecol_name[iCol]); + } + + if (write_model) { + bool output_flag; + printf("\nAfter adding e-cols\n=============\n"); + run_status = this->getOptionValue("output_flag", output_flag); + this->setOptionValue("output_flag", true); + this->writeModel(""); + this->setOptionValue("output_flag", output_flag); + } + run_status = this->run(); + assert(run_status == HighsStatus::kOk); + this->writeSolution("", kSolutionStylePretty); + HighsModelStatus model_status = this->getModelStatus(); + assert(model_status == HighsModelStatus::kOptimal); + + const HighsSolution& solution = this->getSolution(); + // Now fix e-variables that are positive and re-solve until e-LP is infeasible + HighsInt loop_k = 0; + for (;;) { + printf("\nElasticity filter pass %d\n==============\n", int(loop_k)); + HighsInt num_fixed = 0; + if (elastic_columns) { + for (HighsInt eCol = 0; eCol < col_of_ecol.size(); eCol++) { + HighsInt iCol = col_of_ecol[eCol]; + if (solution.col_value[col_ecol_offset+eCol] > this->options_.primal_feasibility_tolerance) { + printf("E-col %2d (column %2d) corresponds to column %2d with bound %g and has solution value %g\n", + int(eCol), int(col_ecol_offset+eCol), int(iCol), bound_of_col_of_ecol[eCol], solution.col_value[col_ecol_offset+eCol]); + this->changeColBounds(col_ecol_offset+eCol, 0, 0); + num_fixed++; + } + } + } + for (HighsInt eCol = 0; eCol < row_of_ecol.size(); eCol++) { + HighsInt iRow = row_of_ecol[eCol]; + if (solution.col_value[row_ecol_offset+eCol] > this->options_.primal_feasibility_tolerance) { + printf("E-row %2d (column %2d) corresponds to row %2d with bound %g and has solution value %g\n", + int(eCol), int(row_ecol_offset+eCol), int(iRow), bound_of_row_of_ecol[eCol], solution.col_value[row_ecol_offset+eCol]); + this->changeColBounds(row_ecol_offset+eCol, 0, 0); + num_fixed++; + } + } + assert(num_fixed>0); + run_status = this->run(); + assert(run_status == HighsStatus::kOk); + this->writeSolution("", kSolutionStylePretty); + HighsModelStatus model_status = this->getModelStatus(); + if (model_status == HighsModelStatus::kInfeasible) break; + loop_k++; + if (loop_k>10) assert(1666==1999); + } + + HighsInt num_enforced_col_ecol = 0; + HighsInt num_enforced_row_ecol = 0; + for (HighsInt eCol = 0; eCol < col_of_ecol.size(); eCol++) { + HighsInt iCol = col_of_ecol[eCol]; + if (lp.col_upper_[col_ecol_offset+eCol] == 0) { + num_enforced_col_ecol++; + printf("Col e-col %2d (column %2d) corresponds to column %2d with bound %g and is enforced\n", + int(eCol), int(col_ecol_offset+eCol), int(iCol), bound_of_col_of_ecol[eCol]); + } + } + for (HighsInt eCol = 0; eCol < row_of_ecol.size(); eCol++) { + HighsInt iRow = row_of_ecol[eCol]; + if (lp.col_upper_[row_ecol_offset+eCol] == 0) { + num_enforced_row_ecol++; + printf("Row e-col %2d (column %2d) corresponds to row %2d with bound %g and is enforced\n", + int(eCol), int(row_ecol_offset+eCol), int(iRow), bound_of_row_of_ecol[eCol]); + } + } + printf("\nElasticity filter after %d passes enforces bounds on %d cols and %d rows\n", int(loop_k), int(num_enforced_col_ecol), int(num_enforced_row_ecol)); + assert(666==999); + return HighsStatus::kOk; +} + HighsStatus Highs::extractIis(HighsInt& num_iis_col, HighsInt& num_iis_row, HighsInt* iis_col_index, HighsInt* iis_row_index, HighsInt* iis_col_bound, From e652b985c1829c42202307912712f11d9ab88bfc Mon Sep 17 00:00:00 2001 From: jajhall Date: Mon, 10 Jun 2024 10:10:34 +0100 Subject: [PATCH 024/194] Now running elasticity filter in computeInfeasibleRows --- src/Highs.h | 4 ++-- src/lp_data/HighsIis.cpp | 13 ++++++------- src/lp_data/HighsIis.h | 3 ++- src/lp_data/HighsInterface.cpp | 12 ++++++++---- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/Highs.h b/src/Highs.h index 27c5a936b8..275fa36c3b 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -1515,8 +1515,8 @@ class Highs { HighsStatus getRangingInterface(); HighsStatus computeIisInterface(); - HighsStatus computeInfeasibleRows(const bool elastic_columns = false, - HighsInt* infeasible_row = nullptr); + HighsStatus computeInfeasibleRows(const bool elastic_columns, + std::vector& infeasible_row); HighsStatus extractIis(HighsInt& num_iis_col, HighsInt& num_iis_row, HighsInt* iis_col_index, HighsInt* iis_row_index, HighsInt* iis_col_bound, HighsInt* iis_row_bound); diff --git a/src/lp_data/HighsIis.cpp b/src/lp_data/HighsIis.cpp index 3a428ed97d..efa989430a 100644 --- a/src/lp_data/HighsIis.cpp +++ b/src/lp_data/HighsIis.cpp @@ -150,23 +150,22 @@ bool HighsIis::trivial(const HighsLp& lp, const HighsOptions& options) { HighsStatus HighsIis::getData(const HighsLp& lp, const HighsOptions& options, const HighsBasis& basis, - const double* dual_ray_value) { + const std::vector& infeasible_row) { // Check for trivial IIS should have been done earlier assert(!this->trivial(lp, options)); if (options.iis_strategy == kIisStrategyFromRayRowPriority || options.iis_strategy == kIisStrategyFromRayColPriority) { // Identify the LP corresponding to the ray - std::vector from_row; + std::vector from_row = infeasible_row; std::vector from_col; std::vector to_row; to_row.assign(lp.num_row_, -1); assert(lp.a_matrix_.isColwise()); - for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { - if (dual_ray_value[iRow]) { - to_row[iRow] = from_row.size(); - from_row.push_back(iRow); - } + for (HighsInt iX = 0; iX < HighsInt(infeasible_row.size()); iX++) { + HighsInt iRow = infeasible_row[iX]; + to_row[iRow] = iX; + from_row.push_back(iRow); } for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { bool use_col = false; diff --git a/src/lp_data/HighsIis.h b/src/lp_data/HighsIis.h index f9f418c131..72754c8f0f 100644 --- a/src/lp_data/HighsIis.h +++ b/src/lp_data/HighsIis.h @@ -37,7 +37,8 @@ class HighsIis { void removeCol(const HighsInt col); void removeRow(const HighsInt row); HighsStatus getData(const HighsLp& lp, const HighsOptions& options, - const HighsBasis& basis, const double* dual_ray_value); + const HighsBasis& basis, + const std::vector& infeasible_row); HighsStatus compute(const HighsLp& lp, const HighsOptions& options, const HighsBasis* basis = nullptr); diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 7ad4b4d12e..d4f6461581 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -1573,7 +1573,7 @@ HighsStatus Highs::computeIisInterface() { } assert(ekk_instance_.status_.has_invert); assert(!lp.is_moved_); - std::vector dual_ray_value; + std::vector infeasible_row; if (options_.iis_strategy == kIisStrategyFromRayRowPriority || options_.iis_strategy == kIisStrategyFromRayColPriority) { const bool has_dual_ray = ekk_instance_.status_.has_dual_ray; @@ -1582,21 +1582,25 @@ HighsStatus Highs::computeIisInterface() { HighsInt iRow = ekk_instance_.info_.dual_ray_row_; rhs.assign(num_row, 0); rhs[iRow] = ekk_instance_.info_.dual_ray_sign_; - dual_ray_value.resize(num_row); + std::vector dual_ray_value(num_row); HighsInt* dual_ray_num_nz = 0; basisSolveInterface(rhs, dual_ray_value.data(), dual_ray_num_nz, NULL, true); + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) + if (dual_ray_value[iRow]) infeasible_row.push_back(iRow); } else { highsLogUser(options_.log_options, HighsLogType::kError, "No dual ray to start IIS calculation\n"); return HighsStatus::kError; } + } else { + this->computeInfeasibleRows(false, infeasible_row); } - return this->iis_.getData(lp, options_, basis_, dual_ray_value.data()); + return this->iis_.getData(lp, options_, basis_, infeasible_row); } HighsStatus Highs::computeInfeasibleRows(const bool elastic_columns, - HighsInt* infeasible_row) { + std::vector& infeasible_row) { // Elasticity filter // // Construct the e-LP: From 053db8e8b347cc73cd2fb03e219d1afdef597f6c Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 13 Jun 2024 17:29:49 +0100 Subject: [PATCH 025/194] Much refactoring of IIS calls --- check/TestIis.cpp | 410 +++++++++++++++------------------ src/Highs.h | 15 +- src/lp_data/Highs.cpp | 36 +-- src/lp_data/HighsIis.cpp | 108 +++++---- src/lp_data/HighsInterface.cpp | 121 ++++++---- 5 files changed, 341 insertions(+), 349 deletions(-) diff --git a/check/TestIis.cpp b/check/TestIis.cpp index a490d716be..e6d842735a 100644 --- a/check/TestIis.cpp +++ b/check/TestIis.cpp @@ -9,6 +9,174 @@ const bool dev_run = true; const double inf = kHighsInf; +void testIis(const std::string& model, const HighsIis& iis); + +void testMps(std::string& model, const HighsInt iis_strategy, + const HighsModelStatus require_model_status = HighsModelStatus::kInfeasible); + +TEST_CASE("lp-incompatible-bounds", "[iis]") { + // LP has row0 and col2 with inconsistent bounds. + // + // When prioritising rows, row0 and its constituent columns (1, 2) should be + // found + // + // When prioritising columns, col2 and its constituent rows (0, 1) should be + // found + HighsLp lp; + lp.num_col_ = 3; + lp.num_row_ = 2; + lp.col_cost_ = {0, 0, 0}; + lp.col_lower_ = {0, 0, 0}; + lp.col_upper_ = {1, 1, -1}; + lp.row_lower_ = {1, 0}; + lp.row_upper_ = {0, 1}; + lp.a_matrix_.format_ = MatrixFormat::kRowwise; + lp.a_matrix_.start_ = {0, 2, 4}; + lp.a_matrix_.index_ = {1, 2, 0, 2}; + lp.a_matrix_.value_ = {1, 1, 1, 1}; + Highs highs; + highs.setOptionValue("output_flag", dev_run); + highs.passModel(lp); + REQUIRE(highs.run() == HighsStatus::kOk); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); + highs.setOptionValue("iis_strategy", kIisStrategyFromLpRowPriority); + HighsIis iis; + REQUIRE(highs.getIis(iis) == HighsStatus::kOk); + REQUIRE(iis.col_index_.size() == 0); + REQUIRE(iis.row_index_.size() == 1); + REQUIRE(iis.row_index_[0] == 0); + REQUIRE(iis.row_bound_[0] == kIisBoundStatusBoxed); + highs.setOptionValue("iis_strategy", kIisStrategyFromLpColPriority); + REQUIRE(highs.getIis(iis) == HighsStatus::kOk); + REQUIRE(iis.col_index_.size() == 1); + REQUIRE(iis.row_index_.size() == 0); + REQUIRE(iis.col_index_[0] == 2); + REQUIRE(iis.col_bound_[0] == kIisBoundStatusBoxed); +} + +TEST_CASE("lp-empty-infeasible-row", "[iis]") { + // Second row is empty, with bounds of [1, 2] + const HighsInt empty_row = 1; + HighsLp lp; + lp.num_col_ = 2; + lp.num_row_ = 3; + lp.col_cost_ = {0, 0}; + lp.col_lower_ = {0, 0}; + lp.col_upper_ = {inf, inf}; + lp.row_lower_ = {-inf, 1, -inf}; + lp.row_upper_ = {8, 2, 9}; + lp.a_matrix_.format_ = MatrixFormat::kRowwise; + lp.a_matrix_.start_ = {0, 2, 2, 4}; + lp.a_matrix_.index_ = {0, 1, 0, 1}; + lp.a_matrix_.value_ = {2, 1, 1, 3}; + Highs highs; + highs.setOptionValue("output_flag", dev_run); + highs.passModel(lp); + REQUIRE(highs.run() == HighsStatus::kOk); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); + HighsIis iis; + REQUIRE(highs.getIis(iis) == HighsStatus::kOk); + REQUIRE(iis.col_index_.size() == 0); + REQUIRE(iis.row_index_.size() == 1); + REQUIRE(iis.row_index_[0] == empty_row); + REQUIRE(iis.row_bound_[0] == kIisBoundStatusLower); + REQUIRE(highs.changeRowBounds(empty_row, -2, -1) == HighsStatus::kOk); + REQUIRE(highs.run() == HighsStatus::kOk); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); + REQUIRE(highs.getIis(iis) == HighsStatus::kOk); + REQUIRE(iis.col_index_.size() == 0); + REQUIRE(iis.row_index_.size() == 1); + REQUIRE(iis.row_index_[0] == empty_row); + REQUIRE(iis.row_bound_[0] == kIisBoundStatusUpper); +} + +TEST_CASE("lp-get-iis", "[iis]") { + HighsLp lp; + lp.num_col_ = 2; + lp.num_row_ = 3; + lp.col_cost_ = {0, 0}; + lp.col_lower_ = {0, 0}; + lp.col_upper_ = {inf, inf}; + lp.row_lower_ = {-inf, -inf, -inf}; + lp.row_upper_ = {8, 9, -2}; + lp.a_matrix_.format_ = MatrixFormat::kRowwise; + lp.a_matrix_.start_ = {0, 2, 4, 6}; + lp.a_matrix_.index_ = {0, 1, 0, 1, 0, 1}; + lp.a_matrix_.value_ = {2, 1, 1, 3, 1, 1}; + // lp.col_name_ = {"Col0", "Col1"}; + // lp.row_name_ = {"Row0", "Row1", "Row2"}; + Highs highs; + highs.setOptionValue("output_flag", dev_run); + highs.passModel(lp); + REQUIRE(highs.run() == HighsStatus::kOk); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); + highs.setOptionValue("iis_strategy", kIisStrategyFromLpRowPriority); + HighsIis iis; + REQUIRE(highs.getIis(iis) == HighsStatus::kOk); + REQUIRE(iis.col_index_.size() == 2); + REQUIRE(iis.row_index_.size() == 1); + REQUIRE(iis.col_index_[0] == 0); + REQUIRE(iis.col_index_[1] == 1); + REQUIRE(iis.row_index_[0] == 2); +} + +TEST_CASE("lp-get-iis-woodinfe", "[iis]") { + std::string model = "woodinfe"; + testMps(model, kIisStrategyFromRayRowPriority); +} + +TEST_CASE("lp-get-iis-galenet", "[iis]") { + // Dual ray corresponds to constraints + // + // r0: 0 <= c0 + c1 - c3 - c4 <=0 + // + // r1: 20 <= c2 + c3 + // + // r2: 30 <= c4 + // + // Where + // + // 0 <= c0 <= 10 + // + // 0 <= c1 <= 10 + // + // 0 <= c2 <= 2 + // + // 0 <= c3 <= 20 + // + // 0 <= c4 <= 30 + // + // This is infeasible since c4 >= 30 and c4 <= 30 fices c4 = 30, + // then c0 + c1 >= c3 + c4 >= 30 cannot be satisfied due to the + // upper bounds of 10 on these variables + // + // r1 can be removed and infeasibility is retained, but not r0 or r2 + // + // The upper bound on r0 can be removed + // + // The lower bounds on c0 and c1 can be removed, but not their upper + // bounds + // + // c2 can be removed, as it is empty once r1 is removed + // + // c3 can be removed, as the value of c4 is sufficient to make r0 + // infeasible + // + // The bounds on c4 can be removed, since it's the lower bound from + // r2 that makes r0 infeasible + // + // Hence only empty columns can be removed + std::string model = "galenet"; + testMps(model, kIisStrategyFromLpRowPriority); + testMps(model, kIisStrategyFromRayRowPriority); +} + +TEST_CASE("lp-get-iis-avgas", "[iis]") { + std::string model = "avgas"; + testMps(model, kIisStrategyFromLpRowPriority, HighsModelStatus::kOptimal); + testMps(model, kIisStrategyFromRayRowPriority, HighsModelStatus::kOptimal); +} + void testIis(const std::string& model, const HighsIis& iis) { HighsModelStatus model_status; std::string model_file = @@ -59,12 +227,9 @@ void testIis(const std::string& model, const HighsIis& iis) { lp.row_upper_[iRow] = iis_row_upper[iisRow]; } - highs.passModel(lp); - highs.run(); - model_status = highs.getModelStatus(); - printf("IIS LP yields model status %s\n", - highs.modelStatusToString(model_status).c_str()); - REQUIRE(model_status == HighsModelStatus::kInfeasible); + REQUIRE(highs.passModel(lp) == HighsStatus::kOk); + REQUIRE(highs.run() == HighsStatus::kOk); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); for (HighsInt iisCol = 0; iisCol < num_iis_col; iisCol++) { HighsInt iCol = iis.col_index_[iisCol]; @@ -133,231 +298,32 @@ void testIis(const std::string& model, const HighsIis& iis) { } } -void testMps(std::string& model, const HighsInt iis_strategy) { +void testMps(std::string& model, const HighsInt iis_strategy, + const HighsModelStatus require_model_status) { std::string model_file = std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; Highs highs; highs.setOptionValue("output_flag", dev_run); REQUIRE(highs.readModel(model_file) == HighsStatus::kOk); - highs.writeModel(""); REQUIRE(highs.run() == HighsStatus::kOk); - REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); highs.setOptionValue("iis_strategy", iis_strategy); - REQUIRE(highs.computeIis() == HighsStatus::kOk); - const HighsIis& iis = highs.getIis(); + HighsIis iis; + REQUIRE(highs.getIis(iis) == HighsStatus::kOk); HighsInt num_iis_col = iis.col_index_.size(); HighsInt num_iis_row = iis.row_index_.size(); - REQUIRE(num_iis_col > 0); - REQUIRE(num_iis_row > 0); - if (dev_run) - printf("Model %s has IIS with %d columns and %d rows\n", model.c_str(), - int(num_iis_col), int(num_iis_row)); - testIis(model, iis); -} - -TEST_CASE("lp-incompatible-bounds", "[iis]") { - // LP has row0 and col2 with inconsistent bounds. - // - // When prioritising rows, row0 and its constituent columns (1, 2) should be - // found - // - // When prioritising columns, col2 and its constituent rows (0, 1) should be - // found - HighsLp lp; - lp.num_col_ = 3; - lp.num_row_ = 2; - lp.col_cost_ = {0, 0, 0}; - lp.col_lower_ = {0, 0, 0}; - lp.col_upper_ = {1, 1, -1}; - lp.row_lower_ = {1, 0}; - lp.row_upper_ = {0, 1}; - lp.a_matrix_.format_ = MatrixFormat::kRowwise; - lp.a_matrix_.start_ = {0, 2, 4}; - lp.a_matrix_.index_ = {1, 2, 0, 2}; - lp.a_matrix_.value_ = {1, 1, 1, 1}; - Highs highs; - highs.setOptionValue("output_flag", dev_run); - highs.passModel(lp); - REQUIRE(highs.run() == HighsStatus::kOk); - REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); - HighsInt num_iis_col; - HighsInt num_iis_row; - std::vector iis_col_index; - std::vector iis_row_index; - std::vector iis_col_bound; - std::vector iis_row_bound; - highs.setOptionValue("iis_strategy", kIisStrategyFromLpRowPriority); - REQUIRE(highs.getIis(num_iis_col, num_iis_row) == HighsStatus::kOk); - REQUIRE(num_iis_col == 0); - REQUIRE(num_iis_row == 1); - iis_col_index.resize(num_iis_col); - iis_row_index.resize(num_iis_row); - iis_col_bound.resize(num_iis_col); - iis_row_bound.resize(num_iis_row); - REQUIRE(highs.getIis(num_iis_col, num_iis_row, iis_col_index.data(), - iis_row_index.data(), iis_col_bound.data(), - iis_row_bound.data()) == HighsStatus::kOk); - REQUIRE(iis_row_index[0] == 0); - REQUIRE(iis_row_bound[0] == kIisBoundStatusBoxed); - highs.setOptionValue("iis_strategy", kIisStrategyFromLpColPriority); - REQUIRE(highs.getIis(num_iis_col, num_iis_row) == HighsStatus::kOk); - REQUIRE(num_iis_col == 1); - REQUIRE(num_iis_row == 0); - iis_col_index.resize(num_iis_col); - iis_row_index.resize(num_iis_row); - iis_col_bound.resize(num_iis_col); - iis_row_bound.resize(num_iis_row); - REQUIRE(highs.getIis(num_iis_col, num_iis_row, iis_col_index.data(), - iis_row_index.data(), iis_col_bound.data(), - iis_row_bound.data()) == HighsStatus::kOk); - REQUIRE(iis_col_index[0] == 2); - REQUIRE(iis_col_bound[0] == kIisBoundStatusBoxed); -} - -TEST_CASE("lp-empty-infeasible-row", "[iis]") { - // Second row is empty, with bounds of [1, 2] - const HighsInt empty_row = 1; - HighsLp lp; - lp.num_col_ = 2; - lp.num_row_ = 3; - lp.col_cost_ = {0, 0}; - lp.col_lower_ = {0, 0}; - lp.col_upper_ = {inf, inf}; - lp.row_lower_ = {-inf, 1, -inf}; - lp.row_upper_ = {8, 2, 9}; - lp.a_matrix_.format_ = MatrixFormat::kRowwise; - lp.a_matrix_.start_ = {0, 2, 2, 4}; - lp.a_matrix_.index_ = {0, 1, 0, 1}; - lp.a_matrix_.value_ = {2, 1, 1, 3}; - Highs highs; - highs.setOptionValue("output_flag", dev_run); - highs.passModel(lp); - REQUIRE(highs.run() == HighsStatus::kOk); - REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); - HighsInt num_iis_col; - HighsInt num_iis_row; - std::vector iis_col_index; - std::vector iis_row_index; - std::vector iis_col_bound; - std::vector iis_row_bound; - REQUIRE(highs.getIis(num_iis_col, num_iis_row) == HighsStatus::kOk); - REQUIRE(num_iis_col == 0); - REQUIRE(num_iis_row == 1); - iis_col_index.resize(num_iis_col); - iis_row_index.resize(num_iis_row); - iis_col_bound.resize(num_iis_col); - iis_row_bound.resize(num_iis_row); - REQUIRE(highs.getIis(num_iis_col, num_iis_row, iis_col_index.data(), - iis_row_index.data(), iis_col_bound.data(), - iis_row_bound.data()) == HighsStatus::kOk); - REQUIRE(iis_row_index[0] == empty_row); - REQUIRE(iis_row_bound[0] == kIisBoundStatusLower); - REQUIRE(highs.changeRowBounds(empty_row, -2, -1) == HighsStatus::kOk); - REQUIRE(highs.run() == HighsStatus::kOk); - REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); - REQUIRE(highs.getIis(num_iis_col, num_iis_row) == HighsStatus::kOk); - REQUIRE(num_iis_col == 0); - REQUIRE(num_iis_row == 1); - iis_col_index.resize(num_iis_col); - iis_row_index.resize(num_iis_row); - iis_col_bound.resize(num_iis_col); - iis_row_bound.resize(num_iis_row); - REQUIRE(highs.getIis(num_iis_col, num_iis_row, iis_col_index.data(), - iis_row_index.data(), iis_col_bound.data(), - iis_row_bound.data()) == HighsStatus::kOk); - REQUIRE(iis_row_index[0] == empty_row); - REQUIRE(iis_row_bound[0] == kIisBoundStatusUpper); -} - -TEST_CASE("lp-get-iis", "[iis]") { - HighsLp lp; - lp.num_col_ = 2; - lp.num_row_ = 3; - lp.col_cost_ = {0, 0}; - lp.col_lower_ = {0, 0}; - lp.col_upper_ = {inf, inf}; - lp.row_lower_ = {-inf, -inf, -inf}; - lp.row_upper_ = {8, 9, -2}; - lp.a_matrix_.format_ = MatrixFormat::kRowwise; - lp.a_matrix_.start_ = {0, 2, 4, 6}; - lp.a_matrix_.index_ = {0, 1, 0, 1, 0, 1}; - lp.a_matrix_.value_ = {2, 1, 1, 3, 1, 1}; - Highs highs; - highs.setOptionValue("output_flag", dev_run); - highs.passModel(lp); - REQUIRE(highs.run() == HighsStatus::kOk); - REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); - HighsInt num_iis_col; - HighsInt num_iis_row; - std::vector iis_col_index; - std::vector iis_row_index; - std::vector iis_col_bound; - std::vector iis_row_bound; - highs.setOptionValue("iis_strategy", kIisStrategyFromLpRowPriority); - REQUIRE(highs.getIis(num_iis_col, num_iis_row) == HighsStatus::kOk); - REQUIRE(num_iis_col == 2); - REQUIRE(num_iis_row == 1); - iis_col_index.resize(num_iis_col); - iis_row_index.resize(num_iis_row); - iis_col_bound.resize(num_iis_col); - iis_row_bound.resize(num_iis_row); - REQUIRE(highs.getIis(num_iis_col, num_iis_row, iis_col_index.data(), - iis_row_index.data(), - // iis_col_bound.data(), iis_row_bound.data() - nullptr, nullptr) == HighsStatus::kOk); - REQUIRE(iis_col_index[0] == 0); - REQUIRE(iis_col_index[1] == 1); - REQUIRE(iis_row_index[0] == 2); -} - -TEST_CASE("lp-get-iis-woodinfe", "[iis]") { - std::string model = "woodinfe"; - testMps(model, kIisStrategyFromRayRowPriority); + HighsModelStatus model_status = highs.getModelStatus(); + REQUIRE(model_status == require_model_status); + if (model_status == HighsModelStatus::kInfeasible) { + REQUIRE(num_iis_col > 0); + REQUIRE(num_iis_row > 0); + if (dev_run) + printf("Model %s has IIS with %d columns and %d rows\n", model.c_str(), + int(num_iis_col), int(num_iis_row)); + testIis(model, iis); + } else { + REQUIRE(num_iis_col == 0); + REQUIRE(num_iis_row == 0); + } } -TEST_CASE("lp-get-iis-galenet", "[iis]") { - // Dual ray corresponds to constraints - // - // r0: 0 <= c0 + c1 - c3 - c4 <=0 - // - // r1: 20 <= c2 + c3 - // - // r2: 30 <= c4 - // - // Where - // - // 0 <= c0 <= 10 - // - // 0 <= c1 <= 10 - // - // 0 <= c2 <= 2 - // - // 0 <= c3 <= 20 - // - // 0 <= c4 <= 30 - // - // This is infeasible since c4 >= 30 and c4 <= 30 fices c4 = 30, - // then c0 + c1 >= c3 + c4 >= 30 cannot be satisfied due to the - // upper bounds of 10 on these variables - // - // r1 can be removed and infeasibility is retained, but not r0 or r2 - // - // The upper bound on r0 can be removed - // - // The lower bounds on c0 and c1 can be removed, but not their upper - // bounds - // - // c2 can be removed, as it is empty once r1 is removed - // - // c3 can be removed, as the value of c4 is sufficient to make r0 - // infeasible - // - // The bounds on c4 can be removed, since it's the lower bound from - // r2 that makes r0 infeasible - // - // Hence only empty columns can be removed - std::string model = "galenet"; - // testMps(model, kIisStrategyFromLpRowPriority); - testMps(model, kIisStrategyFromRayRowPriority); -} diff --git a/src/Highs.h b/src/Highs.h index cf4e8cbc16..7acefc61ae 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -509,13 +509,11 @@ class Highs { const HighsInt method = 0, const double ill_conditioning_bound = 1e-4); - HighsStatus getIis(HighsInt& num_iis_col, HighsInt& num_iis_row, - HighsInt* iis_col_index = nullptr, - HighsInt* iis_row_index = nullptr, - HighsInt* iis_col_bound = nullptr, - HighsInt* iis_row_bound = nullptr); - HighsStatus computeIis(); - const HighsIis& getIis() const { return iis_; } + /** + * @brief Get (any) irreducible infeasible subsystem (IIS) + * information for the incumbent model + */ + HighsStatus getIis(HighsIis& iis); /** * @brief Get the current model objective value @@ -1514,7 +1512,8 @@ class Highs { double* primal_ray_value); HighsStatus getRangingInterface(); - HighsStatus computeIisInterface(); + HighsStatus getIisInterface(); + HighsStatus computeInfeasibleRows(const bool elastic_columns, std::vector& infeasible_row); HighsStatus extractIis(HighsInt& num_iis_col, HighsInt& num_iis_row, diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index bbce9e19b0..9502523a6b 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -1679,22 +1679,28 @@ HighsStatus Highs::getIllConditioning(HighsIllConditioning& ill_conditioning, ill_conditioning_bound); } -HighsStatus Highs::getIis(HighsInt& num_iis_col, HighsInt& num_iis_row, - HighsInt* iis_col_index, HighsInt* iis_row_index, - HighsInt* iis_col_bound, HighsInt* iis_row_bound) { - HighsStatus return_status = this->computeIis(); - if (return_status != HighsStatus::kOk) return return_status; - return this->extractIis(num_iis_col, num_iis_row, iis_col_index, - iis_row_index, iis_col_bound, iis_row_bound); -} - -HighsStatus Highs::computeIis() { - if (model_status_ != HighsModelStatus::kInfeasible) { - highsLogUser(options_.log_options, HighsLogType::kError, - "getIis: model status is not infeasible\n"); - return HighsStatus::kError; +HighsStatus Highs::getIis(HighsIis& iis) { + if (this->model_status_ == HighsModelStatus::kOptimal || + this->model_status_ == HighsModelStatus::kUnbounded) { + // Strange to call getIis for a model that's known to be feasible + highsLogUser(options_.log_options, HighsLogType::kInfo, + "Calling Highs::getIis for a model that is known to be feasible\n"); + iis.invalidate(); + // No IIS exists, so validate the empty HighsIis instance + iis.valid_ = true; + return HighsStatus::kOk; } - return this->computeIisInterface(); + HighsStatus return_status = HighsStatus::kOk; + if (this->model_status_ != HighsModelStatus::kNotset && + this->model_status_ != HighsModelStatus::kInfeasible) { + return_status = HighsStatus::kWarning; + highsLogUser(options_.log_options, HighsLogType::kWarning, + "Calling Highs::getIis for a model with status %s\n", + this->modelStatusToString(this->model_status_).c_str()); + } + return_status = interpretCallStatus(options_.log_options, this->getIisInterface(), return_status, "getIisInterface"); + iis = this->iis_; + return return_status; } bool Highs::hasInvert() const { return ekk_instance_.status_.has_invert; } diff --git a/src/lp_data/HighsIis.cpp b/src/lp_data/HighsIis.cpp index 02904af899..116f84ad99 100644 --- a/src/lp_data/HighsIis.cpp +++ b/src/lp_data/HighsIis.cpp @@ -153,65 +153,61 @@ HighsStatus HighsIis::getData(const HighsLp& lp, const HighsOptions& options, const std::vector& infeasible_row) { // Check for trivial IIS should have been done earlier assert(!this->trivial(lp, options)); - - if (options.iis_strategy == kIisStrategyFromRayRowPriority || - options.iis_strategy == kIisStrategyFromRayColPriority) { - // Identify the LP corresponding to the ray - std::vector from_row = infeasible_row; - std::vector from_col; - std::vector to_row; - to_row.assign(lp.num_row_, -1); - assert(lp.a_matrix_.isColwise()); - for (HighsInt iX = 0; iX < HighsInt(infeasible_row.size()); iX++) { - HighsInt iRow = infeasible_row[iX]; - to_row[iRow] = iX; - from_row.push_back(iRow); - } - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { - bool use_col = false; - for (HighsInt iEl = lp.a_matrix_.start_[iCol]; - iEl < lp.a_matrix_.start_[iCol + 1]; iEl++) - use_col = use_col || to_row[lp.a_matrix_.index_[iEl]] >= 0; - if (use_col) from_col.push_back(iCol); - } - HighsInt to_num_col = from_col.size(); - HighsInt to_num_row = from_row.size(); - HighsLp to_lp; - to_lp.num_col_ = to_num_col; - to_lp.num_row_ = to_num_row; - for (HighsInt iCol = 0; iCol < to_num_col; iCol++) { - to_lp.col_cost_.push_back(0); - to_lp.col_lower_.push_back(lp.col_lower_[from_col[iCol]]); - to_lp.col_upper_.push_back(lp.col_upper_[from_col[iCol]]); - to_lp.col_names_.push_back(lp.col_names_[from_col[iCol]]); - for (HighsInt iEl = lp.a_matrix_.start_[from_col[iCol]]; - iEl < lp.a_matrix_.start_[from_col[iCol] + 1]; iEl++) { - HighsInt iRow = lp.a_matrix_.index_[iEl]; - if (to_row[iRow] >= 0) { - to_lp.a_matrix_.index_.push_back(to_row[iRow]); - to_lp.a_matrix_.value_.push_back(lp.a_matrix_.value_[iEl]); - } + // The number of infeasible rows must be positive + assert(infeasible_row.size() > 0); + // Identify the LP corresponding to the set of infeasible rows + std::vector from_row = infeasible_row; + std::vector from_col; + std::vector to_row; + to_row.assign(lp.num_row_, -1); + assert(lp.a_matrix_.isColwise()); + // Determine how to detect whether a row is in infeasible_row and + // (then) gather information about it + for (HighsInt iX = 0; iX < HighsInt(infeasible_row.size()); iX++) + to_row[infeasible_row[iX]] = iX; + // Identify the columns (from_col) with nonzeros in the infeasible + // rows + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + bool use_col = false; + for (HighsInt iEl = lp.a_matrix_.start_[iCol]; + iEl < lp.a_matrix_.start_[iCol + 1]; iEl++) + use_col = use_col || to_row[lp.a_matrix_.index_[iEl]] >= 0; + if (use_col) from_col.push_back(iCol); + } + HighsInt to_num_col = from_col.size(); + HighsInt to_num_row = from_row.size(); + HighsLp to_lp; + to_lp.num_col_ = to_num_col; + to_lp.num_row_ = to_num_row; + const bool has_col_names = lp.col_names_.size() > 0; + for (HighsInt iCol = 0; iCol < to_num_col; iCol++) { + to_lp.col_cost_.push_back(0); + to_lp.col_lower_.push_back(lp.col_lower_[from_col[iCol]]); + to_lp.col_upper_.push_back(lp.col_upper_[from_col[iCol]]); + if (has_col_names) to_lp.col_names_.push_back(lp.col_names_[from_col[iCol]]); + for (HighsInt iEl = lp.a_matrix_.start_[from_col[iCol]]; + iEl < lp.a_matrix_.start_[from_col[iCol] + 1]; iEl++) { + HighsInt iRow = lp.a_matrix_.index_[iEl]; + if (to_row[iRow] >= 0) { + to_lp.a_matrix_.index_.push_back(to_row[iRow]); + to_lp.a_matrix_.value_.push_back(lp.a_matrix_.value_[iEl]); } - to_lp.a_matrix_.start_.push_back(to_lp.a_matrix_.index_.size()); } - for (HighsInt iRow = 0; iRow < to_num_row; iRow++) { - to_lp.row_lower_.push_back(lp.row_lower_[from_row[iRow]]); - to_lp.row_upper_.push_back(lp.row_upper_[from_row[iRow]]); - to_lp.row_names_.push_back(lp.row_names_[from_row[iRow]]); - } - if (this->compute(to_lp, options) != HighsStatus::kOk) - return HighsStatus::kError; - // IIS col/row information is for to_lp, so indirect the values - // into the original LP - for (HighsInt iCol = 0; iCol < HighsInt(this->col_index_.size()); iCol++) - this->col_index_[iCol] = from_col[this->col_index_[iCol]]; - for (HighsInt iRow = 0; iRow < HighsInt(this->row_index_.size()); iRow++) - this->row_index_[iRow] = from_row[this->row_index_[iRow]]; - } else { - // Use the whole LP - if (this->compute(lp, options, &basis) != HighsStatus::kOk) - return HighsStatus::kError; + to_lp.a_matrix_.start_.push_back(to_lp.a_matrix_.index_.size()); + } + const bool has_row_names = lp.row_names_.size() > 0; + for (HighsInt iRow = 0; iRow < to_num_row; iRow++) { + to_lp.row_lower_.push_back(lp.row_lower_[from_row[iRow]]); + to_lp.row_upper_.push_back(lp.row_upper_[from_row[iRow]]); + if (has_row_names) to_lp.row_names_.push_back(lp.row_names_[from_row[iRow]]); } + if (this->compute(to_lp, options) != HighsStatus::kOk) + return HighsStatus::kError; + // Indirect the values into the original LP + for (HighsInt iCol = 0; iCol < HighsInt(this->col_index_.size()); iCol++) + this->col_index_[iCol] = from_col[this->col_index_[iCol]]; + for (HighsInt iRow = 0; iRow < HighsInt(this->row_index_.size()); iRow++) + this->row_index_[iRow] = from_row[this->row_index_[iRow]]; this->report("On exit", lp); return HighsStatus::kOk; } diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 166422f5fe..048a9ece0e 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -1549,62 +1549,77 @@ HighsStatus Highs::getRangingInterface() { return getRangingData(this->ranging_, solver_object); } -HighsStatus Highs::computeIisInterface() { +HighsStatus Highs::getIisInterface() { if (this->iis_.valid_) return HighsStatus::kOk; + this->iis_.invalidate(); HighsLp& lp = model_.lp_; // Check for trivial IIS: empty infeasible row or inconsistent bounds if (this->iis_.trivial(lp, options_)) return HighsStatus::kOk; HighsInt num_row = lp.num_row_; - // For an LP with no rows the dual ray is vacuous - if (num_row == 0) return HighsStatus::kOk; - assert(model_status_ == HighsModelStatus::kInfeasible); - if (!ekk_instance_.status_.has_invert) { - // No INVERT - presumably because infeasibility detected in - // presolve + if (num_row == 0) { + // For an LP with no rows, the only scope for infeasibility is + // inconsistent columns bounds - which has already been assessed, + // so validate the empty HighsIis instance + this->iis_.valid_ = true; + return HighsStatus::kOk; + } + const bool ray_option = + options_.iis_strategy == kIisStrategyFromRayRowPriority || + options_.iis_strategy == kIisStrategyFromRayColPriority; + if (this->model_status_ == HighsModelStatus::kInfeasible && ray_option && !ekk_instance_.status_.has_invert) { + // Model is known to be infeasible, and a dual ray option is + // chosen, but it has no INVERT, presumably because infeasibility + // detected in presolve, so solve without presolve std::string presolve = options_.presolve; - printf( - "Highs::getIisInterface options_.presolve = %s; kHighsOnString = " - "%s\n", - options_.presolve.c_str(), kHighsOnString.c_str()); options_.presolve = kHighsOffString; HighsStatus return_status = this->run(); options_.presolve = presolve; if (return_status != HighsStatus::kOk) return return_status; - } - assert(ekk_instance_.status_.has_invert); - assert(!lp.is_moved_); - std::vector infeasible_row; - if (options_.iis_strategy == kIisStrategyFromRayRowPriority || - options_.iis_strategy == kIisStrategyFromRayColPriority) { - const bool has_dual_ray = ekk_instance_.status_.has_dual_ray; - if (has_dual_ray) { - std::vector rhs; - HighsInt iRow = ekk_instance_.info_.dual_ray_row_; - rhs.assign(num_row, 0); - rhs[iRow] = ekk_instance_.info_.dual_ray_sign_; - std::vector dual_ray_value(num_row); - HighsInt* dual_ray_num_nz = 0; - basisSolveInterface(rhs, dual_ray_value.data(), dual_ray_num_nz, NULL, - true); - for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) - if (dual_ray_value[iRow]) infeasible_row.push_back(iRow); - } else { + // Model should remain infeasible! + if (this->model_status_ != HighsModelStatus::kInfeasible) { highsLogUser(options_.log_options, HighsLogType::kError, - "No dual ray to start IIS calculation\n"); - return HighsStatus::kError; - } + "Model status has switched from %s to %s when solving without presolve\n", + this->modelStatusToString(HighsModelStatus::kInfeasible).c_str(), + this->modelStatusToString(this->model_status_).c_str()); + return HighsStatus::kError; + } + } + const bool has_dual_ray = ekk_instance_.status_.has_dual_ray; + if (ray_option && !has_dual_ray) highsLogUser(options_.log_options, HighsLogType::kWarning, + "No known dual ray from which to compute IIS: using whole model\n"); + std::vector infeasible_row_subset; + if (ray_option && has_dual_ray) { + // Compute the dual ray to identify an infeasible subset of rows + assert(ekk_instance_.status_.has_invert); + assert(!lp.is_moved_); + std::vector rhs; + HighsInt iRow = ekk_instance_.info_.dual_ray_row_; + rhs.assign(num_row, 0); + rhs[iRow] = 1; // ekk_instance_.info_.dual_ray_sign_; + std::vector dual_ray_value(num_row); + HighsInt* dual_ray_num_nz = 0; + basisSolveInterface(rhs, dual_ray_value.data(), dual_ray_num_nz, NULL, + true); + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) + if (dual_ray_value[iRow]) infeasible_row_subset.push_back(iRow); } else { + // Full LP option chosen or no dual ray to use + // + // 1789 Remove this check! HighsLp check_lp_before = this->model_.lp_; - HighsStatus return_status = this->computeInfeasibleRows(false, infeasible_row); + // Apply the elasticity filter to the whole LP in order to + // determine an infeasible subset of rows + HighsStatus return_status = this->computeInfeasibleRows(false, infeasible_row_subset); HighsLp check_lp_after = this->model_.lp_; assert(check_lp_before == check_lp_after); if (return_status != HighsStatus::kOk) return return_status; } - return this->iis_.getData(lp, options_, basis_, infeasible_row); + HighsStatus return_status = this->iis_.getData(lp, options_, basis_, infeasible_row_subset); + return return_status; } HighsStatus Highs::computeInfeasibleRows(const bool elastic_columns, - std::vector& infeasible_row) { + std::vector& infeasible_row_subset) { // Elasticity filter // // Construct the e-LP: @@ -1662,10 +1677,12 @@ HighsStatus Highs::computeInfeasibleRows(const bool elastic_columns, run_status = this->changeColsCost(0, lp.num_col_-1, zero_costs.data()); assert(run_status == HighsStatus::kOk); + printf("Highs::computeInfeasibleRows: After entry - model status is %s\n", this->modelStatusToString(this->model_status_).c_str()); if (elastic_columns) { // When defining names, need to know the column number HighsInt previous_num_col = lp.num_col_; HighsInt previous_num_row = lp.num_row_; + const bool has_col_names = lp.col_names_.size() > 0; erow_start.push_back(0); for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { const double lower = lp.col_lower_[iCol]; @@ -1674,14 +1691,14 @@ HighsStatus Highs::computeInfeasibleRows(const bool elastic_columns, if (lower <= -kHighsInf && upper >= kHighsInf) continue; erow_lower.push_back(lower); erow_upper.push_back(upper); - erow_name.push_back("row_"+std::to_string(iCol)+"_"+lp.col_names_[iCol]+"_erow"); + if (has_col_names) erow_name.push_back("row_"+std::to_string(iCol)+"_"+lp.col_names_[iCol]+"_erow"); // Define the entry for x[iCol] erow_index.push_back(iCol); erow_value.push_back(1); if (lower > -kHighsInf) { // New e_l variable col_of_ecol.push_back(iCol); - ecol_name.push_back("col_"+std::to_string(iCol)+"_"+lp.col_names_[iCol]+"_lower"); + if (has_col_names) ecol_name.push_back("col_"+std::to_string(iCol)+"_"+lp.col_names_[iCol]+"_lower"); bound_of_col_of_ecol.push_back(lower); erow_index.push_back(evar_ix); erow_value.push_back(1); @@ -1690,7 +1707,7 @@ HighsStatus Highs::computeInfeasibleRows(const bool elastic_columns, if (upper < kHighsInf) { // New e_u variable col_of_ecol.push_back(iCol); - ecol_name.push_back("col_"+std::to_string(iCol)+"_"+lp.col_names_[iCol]+"_upper"); + if (has_col_names) ecol_name.push_back("col_"+std::to_string(iCol)+"_"+lp.col_names_[iCol]+"_upper"); bound_of_col_of_ecol.push_back(upper); erow_index.push_back(evar_ix); erow_value.push_back(-1); @@ -1724,11 +1741,11 @@ HighsStatus Highs::computeInfeasibleRows(const bool elastic_columns, run_status = this->addRows(num_new_row, erow_lower.data(), erow_upper.data(), num_new_nz, erow_start.data(), erow_index.data(), erow_value.data()); assert(run_status == HighsStatus::kOk); - for (HighsInt iCol = 0; iCol < num_new_col; iCol++) { - this->passColName(previous_num_col+iCol, ecol_name[iCol]); - } - for (HighsInt iRow = 0; iRow < num_new_row; iRow++) { - this->passRowName(previous_num_row+iRow, erow_name[iRow]); + if (has_col_names) { + for (HighsInt iCol = 0; iCol < num_new_col; iCol++) + this->passColName(previous_num_col+iCol, ecol_name[iCol]); + for (HighsInt iRow = 0; iRow < num_new_row; iRow++) + this->passRowName(previous_num_row+iRow, erow_name[iRow]); } if (write_model) { printf("\nAfter adding e-rows\n=============\n"); @@ -1746,13 +1763,15 @@ HighsStatus Highs::computeInfeasibleRows(const bool elastic_columns, std::vector ecol_index; std::vector ecol_value; ecol_start.push_back(0); + const bool has_row_names = lp.row_names_.size() > 0; for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { const double lower = lp.row_lower_[iRow]; const double upper = lp.row_upper_[iRow]; if (lower > -kHighsInf) { // Create an e-var for the row lower bound row_of_ecol.push_back(iRow); - ecol_name.push_back("row_"+std::to_string(iRow)+"_"+lp.row_names_[iRow]+"_lower"); + if (has_row_names) + ecol_name.push_back("row_"+std::to_string(iRow)+"_"+lp.row_names_[iRow]+"_lower"); bound_of_row_of_ecol.push_back(lower); // Define the sub-matrix column ecol_index.push_back(iRow); @@ -1763,7 +1782,8 @@ HighsStatus Highs::computeInfeasibleRows(const bool elastic_columns, if (upper < kHighsInf) { // Create an e-var for the row upper bound row_of_ecol.push_back(iRow); - ecol_name.push_back("row_"+std::to_string(iRow)+"_"+lp.row_names_[iRow]+"_upper"); + if (has_row_names) + ecol_name.push_back("row_"+std::to_string(iRow)+"_"+lp.row_names_[iRow]+"_upper"); bound_of_row_of_ecol.push_back(upper); // Define the sub-matrix column ecol_index.push_back(iRow); @@ -1782,8 +1802,9 @@ HighsStatus Highs::computeInfeasibleRows(const bool elastic_columns, run_status = this->addCols(num_new_col, ecol_cost.data(), ecol_lower.data(), ecol_upper.data(), num_new_nz, ecol_start.data(), ecol_index.data(), ecol_value.data()); assert(run_status == HighsStatus::kOk); - for (HighsInt iCol = 0; iCol < num_new_col; iCol++) { - this->passColName(previous_num_col+iCol, ecol_name[iCol]); + if (has_row_names) { + for (HighsInt iCol = 0; iCol < num_new_col; iCol++) + this->passColName(previous_num_col+iCol, ecol_name[iCol]); } if (write_model) { @@ -1836,6 +1857,7 @@ HighsStatus Highs::computeInfeasibleRows(const bool elastic_columns, if (loop_k>10) assert(1666==1999); } + infeasible_row_subset.clear(); HighsInt num_enforced_col_ecol = 0; HighsInt num_enforced_row_ecol = 0; for (HighsInt eCol = 0; eCol < col_of_ecol.size(); eCol++) { @@ -1852,10 +1874,12 @@ HighsStatus Highs::computeInfeasibleRows(const bool elastic_columns, num_enforced_row_ecol++; printf("Row e-col %2d (column %2d) corresponds to row %2d with bound %g and is enforced\n", int(eCol), int(row_ecol_offset+eCol), int(iRow), bound_of_row_of_ecol[eCol]); + infeasible_row_subset.push_back(iRow); } } printf("\nElasticity filter after %d passes enforces bounds on %d cols and %d rows\n", int(loop_k), int(num_enforced_col_ecol), int(num_enforced_row_ecol)); + printf("Highs::computeInfeasibleRows: Before clearing additional rows and columns - model status is %s\n", this->modelStatusToString(this->model_status_).c_str()); // Delete any additional rows and columns, and restore the original // column costs and bounds run_status = this->deleteRows(original_num_row, lp.num_row_-1); @@ -1873,6 +1897,7 @@ HighsStatus Highs::computeInfeasibleRows(const bool elastic_columns, assert(lp.num_col_ == original_num_col); assert(lp.num_row_ == original_num_row); + printf("Highs::computeInfeasibleRows: After clearing additional rows and columns - model status is %s\n", this->modelStatusToString(this->model_status_).c_str()); return HighsStatus::kOk; } From 7a2f366a33118a8b2abe4d3e9ce00b48015fbdf1 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 13 Jun 2024 18:33:06 +0100 Subject: [PATCH 026/194] All unit tests pass --- check/TestIis.cpp | 9 +- src/Highs.h | 2 +- src/lp_data/Highs.cpp | 15 +- src/lp_data/HighsIis.cpp | 14 +- src/lp_data/HighsIis.h | 2 +- src/lp_data/HighsInterface.cpp | 244 ++++++++++++++++++++------------- src/lp_data/HighsLp.cpp | 46 +++++-- src/lp_data/HighsLp.h | 2 + 8 files changed, 209 insertions(+), 125 deletions(-) diff --git a/check/TestIis.cpp b/check/TestIis.cpp index e6d842735a..5c325b60b6 100644 --- a/check/TestIis.cpp +++ b/check/TestIis.cpp @@ -12,7 +12,8 @@ const double inf = kHighsInf; void testIis(const std::string& model, const HighsIis& iis); void testMps(std::string& model, const HighsInt iis_strategy, - const HighsModelStatus require_model_status = HighsModelStatus::kInfeasible); + const HighsModelStatus require_model_status = + HighsModelStatus::kInfeasible); TEST_CASE("lp-incompatible-bounds", "[iis]") { // LP has row0 and col2 with inconsistent bounds. @@ -122,6 +123,7 @@ TEST_CASE("lp-get-iis", "[iis]") { TEST_CASE("lp-get-iis-woodinfe", "[iis]") { std::string model = "woodinfe"; + testMps(model, kIisStrategyFromLpRowPriority); testMps(model, kIisStrategyFromRayRowPriority); } @@ -299,7 +301,7 @@ void testIis(const std::string& model, const HighsIis& iis) { } void testMps(std::string& model, const HighsInt iis_strategy, - const HighsModelStatus require_model_status) { + const HighsModelStatus require_model_status) { std::string model_file = std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; Highs highs; @@ -319,11 +321,10 @@ void testMps(std::string& model, const HighsInt iis_strategy, REQUIRE(num_iis_row > 0); if (dev_run) printf("Model %s has IIS with %d columns and %d rows\n", model.c_str(), - int(num_iis_col), int(num_iis_row)); + int(num_iis_col), int(num_iis_row)); testIis(model, iis); } else { REQUIRE(num_iis_col == 0); REQUIRE(num_iis_row == 0); } } - diff --git a/src/Highs.h b/src/Highs.h index 7acefc61ae..aac00433bf 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -1515,7 +1515,7 @@ class Highs { HighsStatus getIisInterface(); HighsStatus computeInfeasibleRows(const bool elastic_columns, - std::vector& infeasible_row); + std::vector& infeasible_row); HighsStatus extractIis(HighsInt& num_iis_col, HighsInt& num_iis_row, HighsInt* iis_col_index, HighsInt* iis_row_index, HighsInt* iis_col_bound, HighsInt* iis_row_bound); diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index 9502523a6b..719f9bdc71 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -1683,8 +1683,9 @@ HighsStatus Highs::getIis(HighsIis& iis) { if (this->model_status_ == HighsModelStatus::kOptimal || this->model_status_ == HighsModelStatus::kUnbounded) { // Strange to call getIis for a model that's known to be feasible - highsLogUser(options_.log_options, HighsLogType::kInfo, - "Calling Highs::getIis for a model that is known to be feasible\n"); + highsLogUser( + options_.log_options, HighsLogType::kInfo, + "Calling Highs::getIis for a model that is known to be feasible\n"); iis.invalidate(); // No IIS exists, so validate the empty HighsIis instance iis.valid_ = true; @@ -1696,9 +1697,11 @@ HighsStatus Highs::getIis(HighsIis& iis) { return_status = HighsStatus::kWarning; highsLogUser(options_.log_options, HighsLogType::kWarning, "Calling Highs::getIis for a model with status %s\n", - this->modelStatusToString(this->model_status_).c_str()); + this->modelStatusToString(this->model_status_).c_str()); } - return_status = interpretCallStatus(options_.log_options, this->getIisInterface(), return_status, "getIisInterface"); + return_status = + interpretCallStatus(options_.log_options, this->getIisInterface(), + return_status, "getIisInterface"); iis = this->iis_; return return_status; } @@ -2349,7 +2352,7 @@ static HighsStatus analyseSetCreateError(HighsLogOptions log_options, const HighsInt create_error, const bool ordered, const HighsInt num_set_entries, - const HighsInt* set, + const HighsInt* set, const HighsInt dimension) { if (create_error == kIndexCollectionCreateIllegalSetSize) { highsLogUser(log_options, HighsLogType::kError, @@ -4247,7 +4250,7 @@ HighsStatus Highs::returnFromHighs(HighsStatus highs_return_status) { lpDimensionsOk("returnFromHighs", model_.lp_, options_.log_options); if (!dimensions_ok) { highsLogDev(options_.log_options, HighsLogType::kError, - "LP Dimension error in returnFromHighs()\n"); + "LP Dimension error in returnFromHighs()\n"); return_status = HighsStatus::kError; } assert(dimensions_ok); diff --git a/src/lp_data/HighsIis.cpp b/src/lp_data/HighsIis.cpp index 116f84ad99..f36ea06ac8 100644 --- a/src/lp_data/HighsIis.cpp +++ b/src/lp_data/HighsIis.cpp @@ -170,7 +170,7 @@ HighsStatus HighsIis::getData(const HighsLp& lp, const HighsOptions& options, for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { bool use_col = false; for (HighsInt iEl = lp.a_matrix_.start_[iCol]; - iEl < lp.a_matrix_.start_[iCol + 1]; iEl++) + iEl < lp.a_matrix_.start_[iCol + 1]; iEl++) use_col = use_col || to_row[lp.a_matrix_.index_[iEl]] >= 0; if (use_col) from_col.push_back(iCol); } @@ -184,13 +184,14 @@ HighsStatus HighsIis::getData(const HighsLp& lp, const HighsOptions& options, to_lp.col_cost_.push_back(0); to_lp.col_lower_.push_back(lp.col_lower_[from_col[iCol]]); to_lp.col_upper_.push_back(lp.col_upper_[from_col[iCol]]); - if (has_col_names) to_lp.col_names_.push_back(lp.col_names_[from_col[iCol]]); + if (has_col_names) + to_lp.col_names_.push_back(lp.col_names_[from_col[iCol]]); for (HighsInt iEl = lp.a_matrix_.start_[from_col[iCol]]; - iEl < lp.a_matrix_.start_[from_col[iCol] + 1]; iEl++) { + iEl < lp.a_matrix_.start_[from_col[iCol] + 1]; iEl++) { HighsInt iRow = lp.a_matrix_.index_[iEl]; if (to_row[iRow] >= 0) { - to_lp.a_matrix_.index_.push_back(to_row[iRow]); - to_lp.a_matrix_.value_.push_back(lp.a_matrix_.value_[iEl]); + to_lp.a_matrix_.index_.push_back(to_row[iRow]); + to_lp.a_matrix_.value_.push_back(lp.a_matrix_.value_[iEl]); } } to_lp.a_matrix_.start_.push_back(to_lp.a_matrix_.index_.size()); @@ -199,7 +200,8 @@ HighsStatus HighsIis::getData(const HighsLp& lp, const HighsOptions& options, for (HighsInt iRow = 0; iRow < to_num_row; iRow++) { to_lp.row_lower_.push_back(lp.row_lower_[from_row[iRow]]); to_lp.row_upper_.push_back(lp.row_upper_[from_row[iRow]]); - if (has_row_names) to_lp.row_names_.push_back(lp.row_names_[from_row[iRow]]); + if (has_row_names) + to_lp.row_names_.push_back(lp.row_names_[from_row[iRow]]); } if (this->compute(to_lp, options) != HighsStatus::kOk) return HighsStatus::kError; diff --git a/src/lp_data/HighsIis.h b/src/lp_data/HighsIis.h index 72754c8f0f..4034cfecea 100644 --- a/src/lp_data/HighsIis.h +++ b/src/lp_data/HighsIis.h @@ -38,7 +38,7 @@ class HighsIis { void removeRow(const HighsInt row); HighsStatus getData(const HighsLp& lp, const HighsOptions& options, const HighsBasis& basis, - const std::vector& infeasible_row); + const std::vector& infeasible_row); HighsStatus compute(const HighsLp& lp, const HighsOptions& options, const HighsBasis* basis = nullptr); diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 048a9ece0e..51cbf1f230 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -1560,13 +1560,14 @@ HighsStatus Highs::getIisInterface() { // For an LP with no rows, the only scope for infeasibility is // inconsistent columns bounds - which has already been assessed, // so validate the empty HighsIis instance - this->iis_.valid_ = true; + this->iis_.valid_ = true; return HighsStatus::kOk; } const bool ray_option = - options_.iis_strategy == kIisStrategyFromRayRowPriority || - options_.iis_strategy == kIisStrategyFromRayColPriority; - if (this->model_status_ == HighsModelStatus::kInfeasible && ray_option && !ekk_instance_.status_.has_invert) { + options_.iis_strategy == kIisStrategyFromRayRowPriority || + options_.iis_strategy == kIisStrategyFromRayColPriority; + if (this->model_status_ == HighsModelStatus::kInfeasible && ray_option && + !ekk_instance_.status_.has_invert) { // Model is known to be infeasible, and a dual ray option is // chosen, but it has no INVERT, presumably because infeasibility // detected in presolve, so solve without presolve @@ -1577,16 +1578,20 @@ HighsStatus Highs::getIisInterface() { if (return_status != HighsStatus::kOk) return return_status; // Model should remain infeasible! if (this->model_status_ != HighsModelStatus::kInfeasible) { - highsLogUser(options_.log_options, HighsLogType::kError, - "Model status has switched from %s to %s when solving without presolve\n", - this->modelStatusToString(HighsModelStatus::kInfeasible).c_str(), - this->modelStatusToString(this->model_status_).c_str()); - return HighsStatus::kError; + highsLogUser( + options_.log_options, HighsLogType::kError, + "Model status has switched from %s to %s when solving without " + "presolve\n", + this->modelStatusToString(HighsModelStatus::kInfeasible).c_str(), + this->modelStatusToString(this->model_status_).c_str()); + return HighsStatus::kError; } } const bool has_dual_ray = ekk_instance_.status_.has_dual_ray; - if (ray_option && !has_dual_ray) highsLogUser(options_.log_options, HighsLogType::kWarning, - "No known dual ray from which to compute IIS: using whole model\n"); + if (ray_option && !has_dual_ray) + highsLogUser( + options_.log_options, HighsLogType::kWarning, + "No known dual ray from which to compute IIS: using whole model\n"); std::vector infeasible_row_subset; if (ray_option && has_dual_ray) { // Compute the dual ray to identify an infeasible subset of rows @@ -1595,31 +1600,40 @@ HighsStatus Highs::getIisInterface() { std::vector rhs; HighsInt iRow = ekk_instance_.info_.dual_ray_row_; rhs.assign(num_row, 0); - rhs[iRow] = 1; // ekk_instance_.info_.dual_ray_sign_; + rhs[iRow] = 1; // ekk_instance_.info_.dual_ray_sign_; std::vector dual_ray_value(num_row); HighsInt* dual_ray_num_nz = 0; basisSolveInterface(rhs, dual_ray_value.data(), dual_ray_num_nz, NULL, - true); - for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) + true); + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) if (dual_ray_value[iRow]) infeasible_row_subset.push_back(iRow); } else { // Full LP option chosen or no dual ray to use // + // Working on the whole model so clear all solver data + this->invalidateUserSolverData(); // 1789 Remove this check! HighsLp check_lp_before = this->model_.lp_; - // Apply the elasticity filter to the whole LP in order to + // Apply the elasticity filter to the whole model in order to // determine an infeasible subset of rows - HighsStatus return_status = this->computeInfeasibleRows(false, infeasible_row_subset); + HighsStatus return_status = + this->computeInfeasibleRows(false, infeasible_row_subset); HighsLp check_lp_after = this->model_.lp_; - assert(check_lp_before == check_lp_after); + assert(check_lp_before.equalButForScalingAndNames(check_lp_after)); if (return_status != HighsStatus::kOk) return return_status; } - HighsStatus return_status = this->iis_.getData(lp, options_, basis_, infeasible_row_subset); + HighsStatus return_status = + this->iis_.getData(lp, options_, basis_, infeasible_row_subset); + if (return_status == HighsStatus::kOk) { + // Existence of non-empty IIS => infeasibility + if (this->iis_.col_index_.size() > 0 || this->iis_.row_index_.size() > 0) + this->model_status_ = HighsModelStatus::kInfeasible; + } return return_status; } -HighsStatus Highs::computeInfeasibleRows(const bool elastic_columns, - std::vector& infeasible_row_subset) { +HighsStatus Highs::computeInfeasibleRows( + const bool elastic_columns, std::vector& infeasible_row_subset) { // Elasticity filter // // Construct the e-LP: @@ -1674,12 +1688,13 @@ HighsStatus Highs::computeInfeasibleRows(const bool elastic_columns, const std::vector original_col_upper = lp.col_upper_; std::vector zero_costs; zero_costs.assign(original_num_col, 0); - run_status = this->changeColsCost(0, lp.num_col_-1, zero_costs.data()); + run_status = this->changeColsCost(0, lp.num_col_ - 1, zero_costs.data()); assert(run_status == HighsStatus::kOk); - - printf("Highs::computeInfeasibleRows: After entry - model status is %s\n", this->modelStatusToString(this->model_status_).c_str()); + + printf("Highs::computeInfeasibleRows: After entry - model status is %s\n", + this->modelStatusToString(this->model_status_).c_str()); if (elastic_columns) { - // When defining names, need to know the column number + // When defining names, need to know the column number HighsInt previous_num_col = lp.num_col_; HighsInt previous_num_row = lp.num_row_; const bool has_col_names = lp.col_names_.size() > 0; @@ -1691,69 +1706,81 @@ HighsStatus Highs::computeInfeasibleRows(const bool elastic_columns, if (lower <= -kHighsInf && upper >= kHighsInf) continue; erow_lower.push_back(lower); erow_upper.push_back(upper); - if (has_col_names) erow_name.push_back("row_"+std::to_string(iCol)+"_"+lp.col_names_[iCol]+"_erow"); + if (has_col_names) + erow_name.push_back("row_" + std::to_string(iCol) + "_" + + lp.col_names_[iCol] + "_erow"); // Define the entry for x[iCol] erow_index.push_back(iCol); erow_value.push_back(1); if (lower > -kHighsInf) { - // New e_l variable - col_of_ecol.push_back(iCol); - if (has_col_names) ecol_name.push_back("col_"+std::to_string(iCol)+"_"+lp.col_names_[iCol]+"_lower"); - bound_of_col_of_ecol.push_back(lower); - erow_index.push_back(evar_ix); - erow_value.push_back(1); - evar_ix++; + // New e_l variable + col_of_ecol.push_back(iCol); + if (has_col_names) + ecol_name.push_back("col_" + std::to_string(iCol) + "_" + + lp.col_names_[iCol] + "_lower"); + bound_of_col_of_ecol.push_back(lower); + erow_index.push_back(evar_ix); + erow_value.push_back(1); + evar_ix++; } if (upper < kHighsInf) { - // New e_u variable - col_of_ecol.push_back(iCol); - if (has_col_names) ecol_name.push_back("col_"+std::to_string(iCol)+"_"+lp.col_names_[iCol]+"_upper"); - bound_of_col_of_ecol.push_back(upper); - erow_index.push_back(evar_ix); - erow_value.push_back(-1); - evar_ix++; + // New e_u variable + col_of_ecol.push_back(iCol); + if (has_col_names) + ecol_name.push_back("col_" + std::to_string(iCol) + "_" + + lp.col_names_[iCol] + "_upper"); + bound_of_col_of_ecol.push_back(upper); + erow_index.push_back(evar_ix); + erow_value.push_back(-1); + evar_ix++; } erow_start.push_back(erow_index.size()); - HighsInt row_nz = erow_start[erow_start.size()-1] - erow_start[erow_start.size()-2]; + HighsInt row_nz = + erow_start[erow_start.size() - 1] - erow_start[erow_start.size() - 2]; printf("eRow for column %d has %d nonzeros\n", int(iCol), int(row_nz)); assert(row_nz == 2 || row_nz == 3); } HighsInt num_new_col = col_of_ecol.size(); - HighsInt num_new_row = erow_start.size()-1; + HighsInt num_new_row = erow_start.size() - 1; HighsInt num_new_nz = erow_start[num_new_row]; - printf("Elasticity filter: For columns there are %d variables and %d constraints\n", int(num_new_col), int(num_new_row)); + printf( + "Elasticity filter: For columns there are %d variables and %d " + "constraints\n", + int(num_new_col), int(num_new_row)); const bool write_model = true; // Free the original columns std::vector col_lower; std::vector col_upper; col_lower.assign(lp.num_col_, -kHighsInf); col_upper.assign(lp.num_col_, kHighsInf); - run_status = this->changeColsBounds(0, lp.num_col_-1, col_lower.data(), col_upper.data()); + run_status = this->changeColsBounds(0, lp.num_col_ - 1, col_lower.data(), + col_upper.data()); assert(run_status == HighsStatus::kOk); // Add the new columns ecol_cost.assign(num_new_col, 1); ecol_lower.assign(num_new_col, 0); ecol_upper.assign(num_new_col, kHighsInf); - run_status = this->addCols(num_new_col, ecol_cost.data(), ecol_lower.data(), ecol_upper.data(), - 0, nullptr, nullptr, nullptr); + run_status = this->addCols(num_new_col, ecol_cost.data(), ecol_lower.data(), + ecol_upper.data(), 0, nullptr, nullptr, nullptr); assert(run_status == HighsStatus::kOk); // Add the new rows - run_status = this->addRows(num_new_row, erow_lower.data(), erow_upper.data(), - num_new_nz, erow_start.data(), erow_index.data(), erow_value.data()); + run_status = this->addRows(num_new_row, erow_lower.data(), + erow_upper.data(), num_new_nz, erow_start.data(), + erow_index.data(), erow_value.data()); assert(run_status == HighsStatus::kOk); if (has_col_names) { - for (HighsInt iCol = 0; iCol < num_new_col; iCol++) - this->passColName(previous_num_col+iCol, ecol_name[iCol]); - for (HighsInt iRow = 0; iRow < num_new_row; iRow++) - this->passRowName(previous_num_row+iRow, erow_name[iRow]); + for (HighsInt iCol = 0; iCol < num_new_col; iCol++) + this->passColName(previous_num_col + iCol, ecol_name[iCol]); + for (HighsInt iRow = 0; iRow < num_new_row; iRow++) + this->passRowName(previous_num_row + iRow, erow_name[iRow]); } if (write_model) { printf("\nAfter adding e-rows\n=============\n"); bool output_flag; - run_status = this->getOptionValue("output_flag", output_flag); - this->setOptionValue("output_flag", true); - this->writeModel(""); - this->setOptionValue("output_flag", output_flag); + run_status = this->getOptionValue("output_flag", output_flag); + this->setOptionValue("output_flag", true); + this->writeModel(""); + this->setOptionValue("output_flag", output_flag); } } // Add the columns corresponding to the e_L and e_U variables for @@ -1770,8 +1797,9 @@ HighsStatus Highs::computeInfeasibleRows(const bool elastic_columns, if (lower > -kHighsInf) { // Create an e-var for the row lower bound row_of_ecol.push_back(iRow); - if (has_row_names) - ecol_name.push_back("row_"+std::to_string(iRow)+"_"+lp.row_names_[iRow]+"_lower"); + if (has_row_names) + ecol_name.push_back("row_" + std::to_string(iRow) + "_" + + lp.row_names_[iRow] + "_lower"); bound_of_row_of_ecol.push_back(lower); // Define the sub-matrix column ecol_index.push_back(iRow); @@ -1782,8 +1810,9 @@ HighsStatus Highs::computeInfeasibleRows(const bool elastic_columns, if (upper < kHighsInf) { // Create an e-var for the row upper bound row_of_ecol.push_back(iRow); - if (has_row_names) - ecol_name.push_back("row_"+std::to_string(iRow)+"_"+lp.row_names_[iRow]+"_upper"); + if (has_row_names) + ecol_name.push_back("row_" + std::to_string(iRow) + "_" + + lp.row_names_[iRow] + "_upper"); bound_of_row_of_ecol.push_back(upper); // Define the sub-matrix column ecol_index.push_back(iRow); @@ -1792,19 +1821,20 @@ HighsStatus Highs::computeInfeasibleRows(const bool elastic_columns, evar_ix++; } } - HighsInt num_new_col = ecol_start.size()-1; + HighsInt num_new_col = ecol_start.size() - 1; HighsInt num_new_nz = ecol_start[num_new_col]; ecol_cost.assign(num_new_col, 1); ecol_lower.assign(num_new_col, 0); ecol_upper.assign(num_new_col, kHighsInf); HighsInt previous_num_col = lp.num_col_; HighsInt row_ecol_offset = previous_num_col; - run_status = this->addCols(num_new_col, ecol_cost.data(), ecol_lower.data(), ecol_upper.data(), - num_new_nz, ecol_start.data(), ecol_index.data(), ecol_value.data()); + run_status = this->addCols(num_new_col, ecol_cost.data(), ecol_lower.data(), + ecol_upper.data(), num_new_nz, ecol_start.data(), + ecol_index.data(), ecol_value.data()); assert(run_status == HighsStatus::kOk); if (has_row_names) { - for (HighsInt iCol = 0; iCol < num_new_col; iCol++) - this->passColName(previous_num_col+iCol, ecol_name[iCol]); + for (HighsInt iCol = 0; iCol < num_new_col; iCol++) + this->passColName(previous_num_col + iCol, ecol_name[iCol]); } if (write_model) { @@ -1829,32 +1859,42 @@ HighsStatus Highs::computeInfeasibleRows(const bool elastic_columns, HighsInt num_fixed = 0; if (elastic_columns) { for (HighsInt eCol = 0; eCol < col_of_ecol.size(); eCol++) { - HighsInt iCol = col_of_ecol[eCol]; - if (solution.col_value[col_ecol_offset+eCol] > this->options_.primal_feasibility_tolerance) { - printf("E-col %2d (column %2d) corresponds to column %2d with bound %g and has solution value %g\n", - int(eCol), int(col_ecol_offset+eCol), int(iCol), bound_of_col_of_ecol[eCol], solution.col_value[col_ecol_offset+eCol]); - this->changeColBounds(col_ecol_offset+eCol, 0, 0); - num_fixed++; - } + HighsInt iCol = col_of_ecol[eCol]; + if (solution.col_value[col_ecol_offset + eCol] > + this->options_.primal_feasibility_tolerance) { + printf( + "E-col %2d (column %2d) corresponds to column %2d with bound %g " + "and has solution value %g\n", + int(eCol), int(col_ecol_offset + eCol), int(iCol), + bound_of_col_of_ecol[eCol], + solution.col_value[col_ecol_offset + eCol]); + this->changeColBounds(col_ecol_offset + eCol, 0, 0); + num_fixed++; + } } } for (HighsInt eCol = 0; eCol < row_of_ecol.size(); eCol++) { HighsInt iRow = row_of_ecol[eCol]; - if (solution.col_value[row_ecol_offset+eCol] > this->options_.primal_feasibility_tolerance) { - printf("E-row %2d (column %2d) corresponds to row %2d with bound %g and has solution value %g\n", - int(eCol), int(row_ecol_offset+eCol), int(iRow), bound_of_row_of_ecol[eCol], solution.col_value[row_ecol_offset+eCol]); - this->changeColBounds(row_ecol_offset+eCol, 0, 0); - num_fixed++; + if (solution.col_value[row_ecol_offset + eCol] > + this->options_.primal_feasibility_tolerance) { + printf( + "E-row %2d (column %2d) corresponds to row %2d with bound %g " + "and has solution value %g\n", + int(eCol), int(row_ecol_offset + eCol), int(iRow), + bound_of_row_of_ecol[eCol], + solution.col_value[row_ecol_offset + eCol]); + this->changeColBounds(row_ecol_offset + eCol, 0, 0); + num_fixed++; } } - assert(num_fixed>0); + assert(num_fixed > 0); run_status = this->run(); assert(run_status == HighsStatus::kOk); this->writeSolution("", kSolutionStylePretty); HighsModelStatus model_status = this->getModelStatus(); if (model_status == HighsModelStatus::kInfeasible) break; loop_k++; - if (loop_k>10) assert(1666==1999); + if (loop_k > 10) assert(1666 == 1999); } infeasible_row_subset.clear(); @@ -1862,42 +1902,60 @@ HighsStatus Highs::computeInfeasibleRows(const bool elastic_columns, HighsInt num_enforced_row_ecol = 0; for (HighsInt eCol = 0; eCol < col_of_ecol.size(); eCol++) { HighsInt iCol = col_of_ecol[eCol]; - if (lp.col_upper_[col_ecol_offset+eCol] == 0) { + if (lp.col_upper_[col_ecol_offset + eCol] == 0) { num_enforced_col_ecol++; - printf("Col e-col %2d (column %2d) corresponds to column %2d with bound %g and is enforced\n", - int(eCol), int(col_ecol_offset+eCol), int(iCol), bound_of_col_of_ecol[eCol]); + printf( + "Col e-col %2d (column %2d) corresponds to column %2d with bound %g " + "and is enforced\n", + int(eCol), int(col_ecol_offset + eCol), int(iCol), + bound_of_col_of_ecol[eCol]); } } for (HighsInt eCol = 0; eCol < row_of_ecol.size(); eCol++) { HighsInt iRow = row_of_ecol[eCol]; - if (lp.col_upper_[row_ecol_offset+eCol] == 0) { + if (lp.col_upper_[row_ecol_offset + eCol] == 0) { num_enforced_row_ecol++; - printf("Row e-col %2d (column %2d) corresponds to row %2d with bound %g and is enforced\n", - int(eCol), int(row_ecol_offset+eCol), int(iRow), bound_of_row_of_ecol[eCol]); + printf( + "Row e-col %2d (column %2d) corresponds to row %2d with bound %g " + "and is enforced\n", + int(eCol), int(row_ecol_offset + eCol), int(iRow), + bound_of_row_of_ecol[eCol]); infeasible_row_subset.push_back(iRow); } } - printf("\nElasticity filter after %d passes enforces bounds on %d cols and %d rows\n", int(loop_k), int(num_enforced_col_ecol), int(num_enforced_row_ecol)); + printf( + "\nElasticity filter after %d passes enforces bounds on %d cols and %d " + "rows\n", + int(loop_k), int(num_enforced_col_ecol), int(num_enforced_row_ecol)); - printf("Highs::computeInfeasibleRows: Before clearing additional rows and columns - model status is %s\n", this->modelStatusToString(this->model_status_).c_str()); + printf( + "Highs::computeInfeasibleRows: Before clearing additional rows and " + "columns - model status is %s\n", + this->modelStatusToString(this->model_status_).c_str()); // Delete any additional rows and columns, and restore the original // column costs and bounds - run_status = this->deleteRows(original_num_row, lp.num_row_-1); + run_status = this->deleteRows(original_num_row, lp.num_row_ - 1); assert(run_status == HighsStatus::kOk); - - run_status = this->deleteCols(original_num_col, lp.num_col_-1); + + run_status = this->deleteCols(original_num_col, lp.num_col_ - 1); assert(run_status == HighsStatus::kOk); - - run_status = this->changeColsCost(0, original_num_col-1, original_col_cost.data()); + + run_status = + this->changeColsCost(0, original_num_col - 1, original_col_cost.data()); assert(run_status == HighsStatus::kOk); - run_status = this->changeColsBounds(0, original_num_col-1, original_col_lower.data(), original_col_upper.data()); + run_status = + this->changeColsBounds(0, original_num_col - 1, original_col_lower.data(), + original_col_upper.data()); assert(run_status == HighsStatus::kOk); assert(lp.num_col_ == original_num_col); assert(lp.num_row_ == original_num_row); - printf("Highs::computeInfeasibleRows: After clearing additional rows and columns - model status is %s\n", this->modelStatusToString(this->model_status_).c_str()); + printf( + "Highs::computeInfeasibleRows: After clearing additional rows and " + "columns - model status is %s\n", + this->modelStatusToString(this->model_status_).c_str()); return HighsStatus::kOk; } diff --git a/src/lp_data/HighsLp.cpp b/src/lp_data/HighsLp.cpp index 23546b5660..f7e63312f4 100644 --- a/src/lp_data/HighsLp.cpp +++ b/src/lp_data/HighsLp.cpp @@ -68,6 +68,34 @@ bool HighsLp::operator==(const HighsLp& lp) const { return equal; } +bool HighsLp::equalButForNames(const HighsLp& lp) const { + bool equal = equalButForScalingAndNames(lp); + equal = equalScaling(lp) && equal; + return equal; +} + +bool HighsLp::equalButForScalingAndNames(const HighsLp& lp) const { + bool equal_vectors = true; + equal_vectors = this->num_col_ == lp.num_col_ && equal_vectors; + equal_vectors = this->num_row_ == lp.num_row_ && equal_vectors; + equal_vectors = this->sense_ == lp.sense_ && equal_vectors; + equal_vectors = this->offset_ == lp.offset_ && equal_vectors; + equal_vectors = this->model_name_ == lp.model_name_ && equal_vectors; + equal_vectors = this->col_cost_ == lp.col_cost_ && equal_vectors; + equal_vectors = this->col_upper_ == lp.col_upper_ && equal_vectors; + equal_vectors = this->col_lower_ == lp.col_lower_ && equal_vectors; + equal_vectors = this->row_upper_ == lp.row_upper_ && equal_vectors; + equal_vectors = this->row_lower_ == lp.row_lower_ && equal_vectors; +#ifndef NDEBUG + if (!equal_vectors) printf("HighsLp::equalButForNames: Unequal vectors\n"); +#endif + const bool equal_matrix = this->a_matrix_ == lp.a_matrix_; +#ifndef NDEBUG + if (!equal_matrix) printf("HighsLp::equalButForNames: Unequal matrix\n"); +#endif + return equal_vectors && equal_matrix; +} + bool HighsLp::equalNames(const HighsLp& lp) const { bool equal = true; equal = this->objective_name_ == lp.objective_name_ && equal; @@ -76,21 +104,8 @@ bool HighsLp::equalNames(const HighsLp& lp) const { return equal; } -bool HighsLp::equalButForNames(const HighsLp& lp) const { +bool HighsLp::equalScaling(const HighsLp& lp) const { bool equal = true; - equal = this->num_col_ == lp.num_col_ && equal; - equal = this->num_row_ == lp.num_row_ && equal; - equal = this->sense_ == lp.sense_ && equal; - equal = this->offset_ == lp.offset_ && equal; - equal = this->model_name_ == lp.model_name_ && equal; - equal = this->col_cost_ == lp.col_cost_ && equal; - equal = this->col_upper_ == lp.col_upper_ && equal; - equal = this->col_lower_ == lp.col_lower_ && equal; - equal = this->row_upper_ == lp.row_upper_ && equal; - equal = this->row_lower_ == lp.row_lower_ && equal; - - equal = this->a_matrix_ == lp.a_matrix_; - equal = this->scale_.strategy == lp.scale_.strategy && equal; equal = this->scale_.has_scaling == lp.scale_.has_scaling && equal; equal = this->scale_.num_col == lp.scale_.num_col && equal; @@ -98,6 +113,9 @@ bool HighsLp::equalButForNames(const HighsLp& lp) const { equal = this->scale_.cost == lp.scale_.cost && equal; equal = this->scale_.col == lp.scale_.col && equal; equal = this->scale_.row == lp.scale_.row && equal; +#ifndef NDEBUG + if (!equal) printf("HighsLp::equalScaling: Unequal scaling\n"); +#endif return equal; } diff --git a/src/lp_data/HighsLp.h b/src/lp_data/HighsLp.h index 1e8b766759..fc337211c7 100644 --- a/src/lp_data/HighsLp.h +++ b/src/lp_data/HighsLp.h @@ -61,7 +61,9 @@ class HighsLp { bool operator==(const HighsLp& lp) const; bool equalButForNames(const HighsLp& lp) const; + bool equalButForScalingAndNames(const HighsLp& lp) const; bool equalNames(const HighsLp& lp) const; + bool equalScaling(const HighsLp& lp) const; bool isMip() const; bool hasSemiVariables() const; bool hasInfiniteCost(const double infinite_cost) const; From d68df222c7969064118229a293381bee201f3444 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 14 Jun 2024 11:57:51 +0100 Subject: [PATCH 027/194] printf and other logging conditionally silenced by kIisDevReport --- check/TestIis.cpp | 7 +-- src/lp_data/HighsIis.cpp | 13 +++-- src/lp_data/HighsIis.h | 2 + src/lp_data/HighsInterface.cpp | 88 ++++++++++++++++++---------------- 4 files changed, 61 insertions(+), 49 deletions(-) diff --git a/check/TestIis.cpp b/check/TestIis.cpp index 5c325b60b6..6d718d01a7 100644 --- a/check/TestIis.cpp +++ b/check/TestIis.cpp @@ -6,7 +6,7 @@ #include "catch.hpp" //#include "io/FilereaderLp.h" -const bool dev_run = true; +const bool dev_run = false; const double inf = kHighsInf; void testIis(const std::string& model, const HighsIis& iis); @@ -248,8 +248,9 @@ void testIis(const std::string& model, const HighsIis& iis) { } else if (iis_bound == kIisBoundStatusUpper) { to_upper = inf; } else if (iis_bound == kIisBoundStatusFree) { - printf("IIS Col %2d (LP col %6d) status %s\n", int(iisCol), int(iCol), - iis.iisBoundStatusToString(iis_bound).c_str()); + if (dev_run) + printf("IIS Col %2d (LP col %6d) status %s\n", int(iisCol), int(iCol), + iis.iisBoundStatusToString(iis_bound).c_str()); continue; } REQUIRE(highs.changeColBounds(iCol, to_lower, to_upper) == diff --git a/src/lp_data/HighsIis.cpp b/src/lp_data/HighsIis.cpp index f36ea06ac8..7609874a8a 100644 --- a/src/lp_data/HighsIis.cpp +++ b/src/lp_data/HighsIis.cpp @@ -210,7 +210,7 @@ HighsStatus HighsIis::getData(const HighsLp& lp, const HighsOptions& options, this->col_index_[iCol] = from_col[this->col_index_[iCol]]; for (HighsInt iRow = 0; iRow < HighsInt(this->row_index_.size()); iRow++) this->row_index_[iRow] = from_row[this->row_index_[iRow]]; - this->report("On exit", lp); + if (kIisDevReport) this->report("On exit", lp); return HighsStatus::kOk; } @@ -225,7 +225,7 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) this->addCol(iCol); for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) this->addRow(iRow); Highs highs; - // highs.setOptionValue("output_flag", false); + highs.setOptionValue("output_flag", kIisDevReport); highs.setOptionValue("presolve", kHighsOffString); const HighsLp& incumbent_lp = highs.getLp(); HighsStatus run_status = highs.passModel(lp); @@ -246,12 +246,15 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, const bool use_sensitivity_filter = false; if (use_sensitivity_filter) { + bool output_flag; + highs.getOptionValue("output_flag", output_flag); highs.setOptionValue("simplex_strategy", kSimplexStrategyPrimal); // highs.setOptionValue("output_flag", true); // Solve the LP run_status = highs.run(); highs.writeSolution("", kSolutionStylePretty); + highs.setOptionValue("output_flag", output_flag); } // Pass twice: rows before columns, or columns before rows, according to @@ -384,7 +387,7 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, } if (k == 1) continue; // End of first pass: look to simplify second pass - this->report("End of deletion", incumbent_lp); + if (kIisDevReport) this->report("End of deletion", incumbent_lp); if (row_deletion) { // Mark empty columns as dropped for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { @@ -406,9 +409,9 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, } } } - this->report("End of pass 1", incumbent_lp); + if (kIisDevReport) this->report("End of pass 1", incumbent_lp); } - this->report("End of pass 2", incumbent_lp); + if (kIisDevReport) this->report("End of pass 2", incumbent_lp); HighsInt iss_num_col = 0; for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { if (this->col_bound_[iCol] != kIisBoundStatusDropped) { diff --git a/src/lp_data/HighsIis.h b/src/lp_data/HighsIis.h index 4034cfecea..d060c85a55 100644 --- a/src/lp_data/HighsIis.h +++ b/src/lp_data/HighsIis.h @@ -16,6 +16,8 @@ #include "lp_data/HighsLp.h" +const bool kIisDevReport = false; + enum IisBoundStatus { kIisBoundStatusDropped = -1, kIisBoundStatusNull, // 0 diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 51cbf1f230..8d0020bc71 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -1679,7 +1679,7 @@ HighsStatus Highs::computeInfeasibleRows( const HighsLp& lp = this->model_.lp_; HighsInt evar_ix = lp.num_col_; HighsStatus run_status; - const bool write_model = true; + const bool write_model = false; HighsInt col_ecol_offset; const HighsInt original_num_col = lp.num_col_; const HighsInt original_num_row = lp.num_row_; @@ -1691,8 +1691,6 @@ HighsStatus Highs::computeInfeasibleRows( run_status = this->changeColsCost(0, lp.num_col_ - 1, zero_costs.data()); assert(run_status == HighsStatus::kOk); - printf("Highs::computeInfeasibleRows: After entry - model status is %s\n", - this->modelStatusToString(this->model_status_).c_str()); if (elastic_columns) { // When defining names, need to know the column number HighsInt previous_num_col = lp.num_col_; @@ -1743,11 +1741,11 @@ HighsStatus Highs::computeInfeasibleRows( HighsInt num_new_col = col_of_ecol.size(); HighsInt num_new_row = erow_start.size() - 1; HighsInt num_new_nz = erow_start[num_new_row]; - printf( - "Elasticity filter: For columns there are %d variables and %d " - "constraints\n", - int(num_new_col), int(num_new_row)); - const bool write_model = true; + if (kIisDevReport) + printf( + "Elasticity filter: For columns there are %d variables and %d" + "constraints\n", + int(num_new_col), int(num_new_row)); // Free the original columns std::vector col_lower; std::vector col_upper; @@ -1847,7 +1845,7 @@ HighsStatus Highs::computeInfeasibleRows( } run_status = this->run(); assert(run_status == HighsStatus::kOk); - this->writeSolution("", kSolutionStylePretty); + if (kIisDevReport) this->writeSolution("", kSolutionStylePretty); HighsModelStatus model_status = this->getModelStatus(); assert(model_status == HighsModelStatus::kOptimal); @@ -1855,19 +1853,22 @@ HighsStatus Highs::computeInfeasibleRows( // Now fix e-variables that are positive and re-solve until e-LP is infeasible HighsInt loop_k = 0; for (;;) { - printf("\nElasticity filter pass %d\n==============\n", int(loop_k)); + if (kIisDevReport) + printf("\nElasticity filter pass %d\n==============\n", int(loop_k)); HighsInt num_fixed = 0; if (elastic_columns) { for (HighsInt eCol = 0; eCol < col_of_ecol.size(); eCol++) { HighsInt iCol = col_of_ecol[eCol]; if (solution.col_value[col_ecol_offset + eCol] > this->options_.primal_feasibility_tolerance) { - printf( - "E-col %2d (column %2d) corresponds to column %2d with bound %g " - "and has solution value %g\n", - int(eCol), int(col_ecol_offset + eCol), int(iCol), - bound_of_col_of_ecol[eCol], - solution.col_value[col_ecol_offset + eCol]); + if (kIisDevReport) + printf( + "E-col %2d (column %2d) corresponds to column %2d with bound " + "%g " + "and has solution value %g\n", + int(eCol), int(col_ecol_offset + eCol), int(iCol), + bound_of_col_of_ecol[eCol], + solution.col_value[col_ecol_offset + eCol]); this->changeColBounds(col_ecol_offset + eCol, 0, 0); num_fixed++; } @@ -1877,12 +1878,13 @@ HighsStatus Highs::computeInfeasibleRows( HighsInt iRow = row_of_ecol[eCol]; if (solution.col_value[row_ecol_offset + eCol] > this->options_.primal_feasibility_tolerance) { - printf( - "E-row %2d (column %2d) corresponds to row %2d with bound %g " - "and has solution value %g\n", - int(eCol), int(row_ecol_offset + eCol), int(iRow), - bound_of_row_of_ecol[eCol], - solution.col_value[row_ecol_offset + eCol]); + if (kIisDevReport) + printf( + "E-row %2d (column %2d) corresponds to row %2d with bound %g " + "and has solution value %g\n", + int(eCol), int(row_ecol_offset + eCol), int(iRow), + bound_of_row_of_ecol[eCol], + solution.col_value[row_ecol_offset + eCol]); this->changeColBounds(row_ecol_offset + eCol, 0, 0); num_fixed++; } @@ -1890,7 +1892,7 @@ HighsStatus Highs::computeInfeasibleRows( assert(num_fixed > 0); run_status = this->run(); assert(run_status == HighsStatus::kOk); - this->writeSolution("", kSolutionStylePretty); + if (kIisDevReport) this->writeSolution("", kSolutionStylePretty); HighsModelStatus model_status = this->getModelStatus(); if (model_status == HighsModelStatus::kInfeasible) break; loop_k++; @@ -1915,23 +1917,26 @@ HighsStatus Highs::computeInfeasibleRows( HighsInt iRow = row_of_ecol[eCol]; if (lp.col_upper_[row_ecol_offset + eCol] == 0) { num_enforced_row_ecol++; - printf( - "Row e-col %2d (column %2d) corresponds to row %2d with bound %g " - "and is enforced\n", - int(eCol), int(row_ecol_offset + eCol), int(iRow), - bound_of_row_of_ecol[eCol]); + if (kIisDevReport) + printf( + "Row e-col %2d (column %2d) corresponds to row %2d with bound " + "%g " + "and is enforced\n", + int(eCol), int(row_ecol_offset + eCol), int(iRow), + bound_of_row_of_ecol[eCol]); infeasible_row_subset.push_back(iRow); } } - printf( - "\nElasticity filter after %d passes enforces bounds on %d cols and %d " - "rows\n", - int(loop_k), int(num_enforced_col_ecol), int(num_enforced_row_ecol)); - - printf( - "Highs::computeInfeasibleRows: Before clearing additional rows and " - "columns - model status is %s\n", - this->modelStatusToString(this->model_status_).c_str()); + if (kIisDevReport) { + printf( + "\nElasticity filter after %d passes enforces bounds on %d cols and %d " + "rows\n", + int(loop_k), int(num_enforced_col_ecol), int(num_enforced_row_ecol)); + printf( + "Highs::computeInfeasibleRows: Before clearing additional rows and " + "columns - model status is %s\n", + this->modelStatusToString(this->model_status_).c_str()); + } // Delete any additional rows and columns, and restore the original // column costs and bounds run_status = this->deleteRows(original_num_row, lp.num_row_ - 1); @@ -1952,10 +1957,11 @@ HighsStatus Highs::computeInfeasibleRows( assert(lp.num_col_ == original_num_col); assert(lp.num_row_ == original_num_row); - printf( - "Highs::computeInfeasibleRows: After clearing additional rows and " - "columns - model status is %s\n", - this->modelStatusToString(this->model_status_).c_str()); + if (kIisDevReport) + printf( + "Highs::computeInfeasibleRows: After clearing additional rows and " + "columns - model status is %s\n", + this->modelStatusToString(this->model_status_).c_str()); return HighsStatus::kOk; } From 392bc070acdfec0328bd73106520f49e8a84187a Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 14 Jun 2024 18:05:11 +0100 Subject: [PATCH 028/194] Now using reciprocal filter --- check/TestIis.cpp | 14 ++-- src/lp_data/HighsIis.cpp | 52 +++++++++----- src/lp_data/HighsIis.h | 6 ++ src/lp_data/HighsInterface.cpp | 125 +++++++++++++++++++++++++-------- 4 files changed, 144 insertions(+), 53 deletions(-) diff --git a/check/TestIis.cpp b/check/TestIis.cpp index 6d718d01a7..b3442bf2f3 100644 --- a/check/TestIis.cpp +++ b/check/TestIis.cpp @@ -175,7 +175,11 @@ TEST_CASE("lp-get-iis-galenet", "[iis]") { TEST_CASE("lp-get-iis-avgas", "[iis]") { std::string model = "avgas"; - testMps(model, kIisStrategyFromLpRowPriority, HighsModelStatus::kOptimal); + // For the whole LP calculation the elasticity filter only + // identified feasibility, so the model status is not set + testMps(model, kIisStrategyFromLpRowPriority, HighsModelStatus::kNotset); + // For the ray calculation the model is solved, so its status is + // known testMps(model, kIisStrategyFromRayRowPriority, HighsModelStatus::kOptimal); } @@ -263,8 +267,6 @@ void testIis(const std::string& model, const HighsIis& iis) { "%s\n", int(iisCol), int(iCol), iis.iisBoundStatusToString(iis_bound).c_str(), highs.modelStatusToString(model_status).c_str()); - // if (model_status != HighsModelStatus::kOptimal) - // highs.writeSolution("", kSolutionStylePretty); REQUIRE(model_status == HighsModelStatus::kOptimal); REQUIRE(highs.changeColBounds(iCol, lower, upper) == HighsStatus::kOk); } @@ -309,7 +311,11 @@ void testMps(std::string& model, const HighsInt iis_strategy, highs.setOptionValue("output_flag", dev_run); REQUIRE(highs.readModel(model_file) == HighsStatus::kOk); - REQUIRE(highs.run() == HighsStatus::kOk); + if (iis_strategy == kIisStrategyFromRayRowPriority || + iis_strategy == kIisStrategyFromRayColPriority) { + // For a ray strategy, solve the LP first + REQUIRE(highs.run() == HighsStatus::kOk); + } highs.setOptionValue("iis_strategy", iis_strategy); HighsIis iis; REQUIRE(highs.getIis(iis) == HighsStatus::kOk); diff --git a/src/lp_data/HighsIis.cpp b/src/lp_data/HighsIis.cpp index 7609874a8a..3ea491c57d 100644 --- a/src/lp_data/HighsIis.cpp +++ b/src/lp_data/HighsIis.cpp @@ -12,8 +12,6 @@ * @brief Class-independent utilities for HiGHS */ -#include "lp_data/HighsIis.h" - #include "Highs.h" void HighsIis::invalidate() { @@ -23,6 +21,7 @@ void HighsIis::invalidate() { this->row_index_.clear(); this->col_bound_.clear(); this->row_bound_.clear(); + this->info_.clear(); } std::string HighsIis::iisBoundStatusToString(HighsInt bound_status) const { @@ -216,7 +215,6 @@ HighsStatus HighsIis::getData(const HighsLp& lp, const HighsOptions& options, HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, const HighsBasis* basis) { - this->invalidate(); const HighsLogOptions& log_options = options.log_options; const bool row_priority = options.iis_strategy == kIisStrategyFromRayRowPriority || @@ -225,6 +223,7 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) this->addCol(iCol); for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) this->addRow(iRow); Highs highs; + const HighsInfo& info = highs.getInfo(); highs.setOptionValue("output_flag", kIisDevReport); highs.setOptionValue("presolve", kHighsOffString); const HighsLp& incumbent_lp = highs.getLp(); @@ -239,9 +238,24 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, assert(run_status == HighsStatus::kOk); // Solve the LP if (basis) highs.setBasis(*basis); - run_status = highs.run(); - assert(run_status == HighsStatus::kOk); + + // Lambda for gathering data when solving an LP + auto solveLp = [&]() -> HighsStatus { + HighsIisInfo iis_info; + iis_info.simplex_time = -highs.getRunTime(); + iis_info.simplex_iterations = -info.simplex_iteration_count; + run_status = highs.run(); + assert(run_status == HighsStatus::kOk); + if (run_status != HighsStatus::kOk) return run_status; + iis_info.simplex_time += highs.getRunTime(); + iis_info.simplex_iterations += info.simplex_iteration_count; + this->info_.push_back(iis_info); + return run_status; + }; + + run_status = solveLp(); if (run_status != HighsStatus::kOk) return run_status; + assert(highs.getModelStatus() == HighsModelStatus::kInfeasible); const bool use_sensitivity_filter = false; @@ -252,7 +266,8 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, // highs.setOptionValue("output_flag", true); // Solve the LP - run_status = highs.run(); + run_status = solveLp(); + if (run_status != HighsStatus::kOk) return run_status; highs.writeSolution("", kSolutionStylePretty); highs.setOptionValue("output_flag", output_flag); } @@ -275,15 +290,15 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, double upper = row_deletion ? lp.row_upper_[iX] : lp.col_upper_[iX]; // Record whether the upper bound has been dropped due to the lower bound // being kept - bool drop_upper = false; if (lower > -kHighsInf) { // Drop the lower bound temporarily run_status = row_deletion ? highs.changeRowBounds(iX, -kHighsInf, upper) : highs.changeColBounds(iX, -kHighsInf, upper); assert(run_status == HighsStatus::kOk); - run_status = highs.run(); - assert(run_status == HighsStatus::kOk); + // Solve the LP + run_status = solveLp(); + if (run_status != HighsStatus::kOk) return run_status; HighsModelStatus model_status = highs.getModelStatus(); if (model_status == HighsModelStatus::kOptimal) { // Now feasible, so restore the lower bound @@ -292,18 +307,21 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, assert(run_status == HighsStatus::kOk); // If the lower bound must be kept, then any finite upper bound // must be dropped - const bool apply_reciprocal_rule = false; + const bool apply_reciprocal_rule = true; if (apply_reciprocal_rule) { if (upper < kHighsInf) { // Drop the upper bound permanently upper = kHighsInf; run_status = row_deletion - ? highs.changeRowBounds(iX, lower, kHighsInf) + ? highs.changeRowBounds(iX, lower, upper) : highs.changeColBounds(iX, lower, upper); assert(run_status == HighsStatus::kOk); - drop_upper = true; } - // continue; + assert(upper >= kHighsInf); + // Since upper = kHighsInf, allow the loop to run so that + // bound status is set as if upper were set to kHighsInf + // by relaxing it and finding that the LP was still + // infeasible } } else { // Bound can be dropped permanently @@ -316,12 +334,10 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, run_status = row_deletion ? highs.changeRowBounds(iX, lower, kHighsInf) : highs.changeColBounds(iX, lower, kHighsInf); assert(run_status == HighsStatus::kOk); - run_status = highs.run(); - assert(run_status == HighsStatus::kOk); + // Solve the LP + run_status = solveLp(); + if (run_status != HighsStatus::kOk) return run_status; HighsModelStatus model_status = highs.getModelStatus(); - // If the upper bound has been dropped due to the reciprical - // rule, the LP must be infeasible - if (drop_upper) assert(model_status == HighsModelStatus::kInfeasible); if (model_status == HighsModelStatus::kOptimal) { // Now feasible, so restore the upper bound run_status = row_deletion ? highs.changeRowBounds(iX, lower, upper) diff --git a/src/lp_data/HighsIis.h b/src/lp_data/HighsIis.h index d060c85a55..cb01ea3500 100644 --- a/src/lp_data/HighsIis.h +++ b/src/lp_data/HighsIis.h @@ -27,6 +27,11 @@ enum IisBoundStatus { kIisBoundStatusBoxed // 4 }; +struct HighsIisInfo { + double simplex_time = 0; + HighsInt simplex_iterations = 0; +}; + class HighsIis { public: HighsIis() {} @@ -54,6 +59,7 @@ class HighsIis { std::vector row_index_; std::vector col_bound_; std::vector row_bound_; + std::vector info_; }; #endif // LP_DATA_HIGHSIIS_H_ diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 8d0020bc71..27f46585bd 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -1573,9 +1573,17 @@ HighsStatus Highs::getIisInterface() { // detected in presolve, so solve without presolve std::string presolve = options_.presolve; options_.presolve = kHighsOffString; - HighsStatus return_status = this->run(); + + HighsIisInfo iis_info; + iis_info.simplex_time = -this->getRunTime(); + iis_info.simplex_iterations = -info_.simplex_iteration_count; + HighsStatus run_status = this->run(); options_.presolve = presolve; - if (return_status != HighsStatus::kOk) return return_status; + if (run_status != HighsStatus::kOk) return run_status; + iis_info.simplex_time += this->getRunTime(); + iis_info.simplex_iterations += -info_.simplex_iteration_count; + this->iis_.info_.push_back(iis_info); + // Model should remain infeasible! if (this->model_status_ != HighsModelStatus::kInfeasible) { highsLogUser( @@ -1600,7 +1608,7 @@ HighsStatus Highs::getIisInterface() { std::vector rhs; HighsInt iRow = ekk_instance_.info_.dual_ray_row_; rhs.assign(num_row, 0); - rhs[iRow] = 1; // ekk_instance_.info_.dual_ray_sign_; + rhs[iRow] = 1; std::vector dual_ray_value(num_row); HighsInt* dual_ray_num_nz = 0; basisSolveInterface(rhs, dual_ray_value.data(), dual_ray_num_nz, NULL, @@ -1622,12 +1630,45 @@ HighsStatus Highs::getIisInterface() { assert(check_lp_before.equalButForScalingAndNames(check_lp_after)); if (return_status != HighsStatus::kOk) return return_status; } - HighsStatus return_status = - this->iis_.getData(lp, options_, basis_, infeasible_row_subset); - if (return_status == HighsStatus::kOk) { - // Existence of non-empty IIS => infeasibility - if (this->iis_.col_index_.size() > 0 || this->iis_.row_index_.size() > 0) - this->model_status_ = HighsModelStatus::kInfeasible; + HighsStatus return_status = HighsStatus::kOk; + if (infeasible_row_subset.size() == 0) { + // No subset of infeasible rows, so model is feasible + this->iis_.valid_ = true; + } else { + return_status = + this->iis_.getData(lp, options_, basis_, infeasible_row_subset); + if (return_status == HighsStatus::kOk) { + // Existence of non-empty IIS => infeasibility + if (this->iis_.col_index_.size() > 0 || this->iis_.row_index_.size() > 0) + this->model_status_ = HighsModelStatus::kInfeasible; + } + // Analyse the LP solution data + const HighsInt num_lp_solved = this->iis_.info_.size(); + double min_time = kHighsInf; + double sum_time = 0; + double max_time = 0; + HighsInt min_iterations = kHighsIInf; + HighsInt sum_iterations = 0; + HighsInt max_iterations = 0; + for (HighsInt iX = 0; iX < num_lp_solved; iX++) { + double time = this->iis_.info_[iX].simplex_time; + HighsInt iterations = this->iis_.info_[iX].simplex_iterations; + min_time = std::min(time, min_time); + sum_time += time; + max_time = std::max(time, max_time); + min_iterations = std::min(iterations, min_iterations); + sum_iterations += iterations; + max_iterations = std::max(iterations, max_iterations); + } + highsLogUser(options_.log_options, HighsLogType::kInfo, + " (min / average / max) iteration count (%6d / %6.2g / % 6d)" + " and time (%6.2f / %6.2f / % 6.2f) \n", + int(this->iis_.col_index_.size()), + int(this->iis_.row_index_.size()), int(num_lp_solved), + int(min_iterations), + num_lp_solved > 0 ? (1.0 * sum_iterations) / num_lp_solved : 0, + int(max_iterations), min_time, + num_lp_solved > 0 ? sum_time / num_lp_solved : 0, max_time); } return return_status; } @@ -1843,15 +1884,31 @@ HighsStatus Highs::computeInfeasibleRows( this->writeModel(""); this->setOptionValue("output_flag", output_flag); } - run_status = this->run(); - assert(run_status == HighsStatus::kOk); + // Lambda for gathering data when solving an LP + auto solveLp = [&]() -> HighsStatus { + HighsIisInfo iis_info; + iis_info.simplex_time = -this->getRunTime(); + iis_info.simplex_iterations = -info_.simplex_iteration_count; + run_status = this->run(); + assert(run_status == HighsStatus::kOk); + if (run_status != HighsStatus::kOk) return run_status; + iis_info.simplex_time += this->getRunTime(); + iis_info.simplex_iterations += info_.simplex_iteration_count; + this->iis_.info_.push_back(iis_info); + return run_status; + }; + + run_status = solveLp(); + if (run_status != HighsStatus::kOk) return run_status; if (kIisDevReport) this->writeSolution("", kSolutionStylePretty); - HighsModelStatus model_status = this->getModelStatus(); - assert(model_status == HighsModelStatus::kOptimal); + // Model status should be optimal, unless model is unbounded + assert(this->model_status_ == HighsModelStatus::kOptimal || + this->model_status_ == HighsModelStatus::kUnbounded); const HighsSolution& solution = this->getSolution(); // Now fix e-variables that are positive and re-solve until e-LP is infeasible HighsInt loop_k = 0; + bool feasible_model = false; for (;;) { if (kIisDevReport) printf("\nElasticity filter pass %d\n==============\n", int(loop_k)); @@ -1889,14 +1946,17 @@ HighsStatus Highs::computeInfeasibleRows( num_fixed++; } } - assert(num_fixed > 0); - run_status = this->run(); - assert(run_status == HighsStatus::kOk); + if (num_fixed == 0) { + // No elastic variables were positive, so problem is feasible + feasible_model = true; + break; + } + HighsStatus run_status = solveLp(); + if (run_status != HighsStatus::kOk) return run_status; if (kIisDevReport) this->writeSolution("", kSolutionStylePretty); HighsModelStatus model_status = this->getModelStatus(); if (model_status == HighsModelStatus::kInfeasible) break; loop_k++; - if (loop_k > 10) assert(1666 == 1999); } infeasible_row_subset.clear(); @@ -1917,26 +1977,31 @@ HighsStatus Highs::computeInfeasibleRows( HighsInt iRow = row_of_ecol[eCol]; if (lp.col_upper_[row_ecol_offset + eCol] == 0) { num_enforced_row_ecol++; + infeasible_row_subset.push_back(iRow); if (kIisDevReport) printf( "Row e-col %2d (column %2d) corresponds to row %2d with bound " - "%g " - "and is enforced\n", + "%g and is enforced\n", int(eCol), int(row_ecol_offset + eCol), int(iRow), bound_of_row_of_ecol[eCol]); - infeasible_row_subset.push_back(iRow); } } - if (kIisDevReport) { + + if (feasible_model) + assert(num_enforced_col_ecol == 0 && num_enforced_row_ecol == 0); + + highsLogUser( + options_.log_options, HighsLogType::kInfo, + "Elasticity filter after %d passes enforces bounds on %d cols and %d " + "rows\n", + int(loop_k), int(num_enforced_col_ecol), int(num_enforced_row_ecol)); + + if (kIisDevReport) printf( "\nElasticity filter after %d passes enforces bounds on %d cols and %d " "rows\n", int(loop_k), int(num_enforced_col_ecol), int(num_enforced_row_ecol)); - printf( - "Highs::computeInfeasibleRows: Before clearing additional rows and " - "columns - model status is %s\n", - this->modelStatusToString(this->model_status_).c_str()); - } + // Delete any additional rows and columns, and restore the original // column costs and bounds run_status = this->deleteRows(original_num_row, lp.num_row_ - 1); @@ -1957,11 +2022,9 @@ HighsStatus Highs::computeInfeasibleRows( assert(lp.num_col_ == original_num_col); assert(lp.num_row_ == original_num_row); - if (kIisDevReport) - printf( - "Highs::computeInfeasibleRows: After clearing additional rows and " - "columns - model status is %s\n", - this->modelStatusToString(this->model_status_).c_str()); + // If the model is feasible, then the status of model is not known + if (feasible_model) this->model_status_ = HighsModelStatus::kNotset; + return HighsStatus::kOk; } From a463332e75e58029ccf0a329a904809b52920aaf Mon Sep 17 00:00:00 2001 From: jajhall Date: Tue, 18 Jun 2024 15:45:02 +0100 Subject: [PATCH 029/194] Now extracting primal phase 1 duals for sensitivity test --- src/Highs.h | 8 ++++ src/lp_data/HighsIis.cpp | 85 ++++++++++++++++++++++++++++++-------- src/simplex/HEkk.cpp | 2 + src/simplex/HEkk.h | 1 + src/simplex/HEkkPrimal.cpp | 5 +++ 5 files changed, 84 insertions(+), 17 deletions(-) diff --git a/src/Highs.h b/src/Highs.h index 6f7152d77a..11053f2282 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -1217,6 +1217,14 @@ class Highs { HighsStatus getBasisInverseRowSparse(const HighsInt row, HVector& row_ep_buffer); + /** + * @Brief Get the primal simplex phase 1 dual values. Advanced + * method: for HiGHS IIS calculation + */ + const std::vector& getPrimalPhase1Dual() const { + return ekk_instance_.primal_phase1_dual_; + } + // Start of deprecated methods std::string compilationDate() const { return "deprecated"; } diff --git a/src/lp_data/HighsIis.cpp b/src/lp_data/HighsIis.cpp index 3ea491c57d..ec641467d6 100644 --- a/src/lp_data/HighsIis.cpp +++ b/src/lp_data/HighsIis.cpp @@ -227,6 +227,8 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, highs.setOptionValue("output_flag", kIisDevReport); highs.setOptionValue("presolve", kHighsOffString); const HighsLp& incumbent_lp = highs.getLp(); + const HighsBasis& incumbent_basis = highs.getBasis(); + const HighsSolution& solution = highs.getSolution(); HighsStatus run_status = highs.passModel(lp); assert(run_status == HighsStatus::kOk); if (basis) highs.setBasis(*basis); @@ -238,6 +240,11 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, assert(run_status == HighsStatus::kOk); // Solve the LP if (basis) highs.setBasis(*basis); + const bool use_sensitivity_filter = false; + std::vector primal_phase1_dual; + bool row_deletion = false; + HighsInt iX = -1; + bool drop_lower = false; // Lambda for gathering data when solving an LP auto solveLp = [&]() -> HighsStatus { @@ -247,6 +254,64 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, run_status = highs.run(); assert(run_status == HighsStatus::kOk); if (run_status != HighsStatus::kOk) return run_status; + HighsModelStatus model_status = highs.getModelStatus(); + if (use_sensitivity_filter && + model_status == HighsModelStatus::kInfeasible) { + printf("\nHighsIis::compute %s deletion for %d and %s bound\n", + row_deletion ? "Row" : "Col", int(iX), + drop_lower ? "Lower" : "Upper"); + bool output_flag; + highs.getOptionValue("output_flag", output_flag); + highs.setOptionValue("output_flag", true); + HighsInt simplex_strategy; + highs.getOptionValue("simplex_strategy", simplex_strategy); + highs.setOptionValue("simplex_strategy", kSimplexStrategyPrimal); + // Solve the LP + run_status = highs.run(); + if (run_status != HighsStatus::kOk) return run_status; + highs.writeSolution("", kSolutionStylePretty); + primal_phase1_dual = highs.getPrimalPhase1Dual(); + HighsInt num_zero_dual = 0; + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + const HighsBasisStatus status = incumbent_basis.col_status[iCol]; + const double dual = primal_phase1_dual[iCol]; + const double lower = lp.col_lower_[iCol]; + const double upper = lp.col_upper_[iCol]; + const double value = solution.col_value[iCol]; + if (status != HighsBasisStatus::kBasic && + std::fabs(dual) < options.dual_feasibility_tolerance) { + num_zero_dual++; + // Small dual for nonbasic variable + printf( + "HighsIis::compute Column %d [%g, %g, %g] with status %s has " + "dual %g\n", + int(iCol), lower, value, upper, + highs.basisStatusToString(status).c_str(), dual); + // assert(123 == 456); + } + } + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { + const HighsBasisStatus status = incumbent_basis.row_status[iRow]; + const double dual = primal_phase1_dual[lp.num_col_ + iRow]; + const double lower = lp.row_lower_[iRow]; + const double upper = lp.row_upper_[iRow]; + const double value = solution.row_value[iRow]; + if (status != HighsBasisStatus::kBasic && + std::fabs(dual) < options.dual_feasibility_tolerance) { + num_zero_dual++; + // Small dual for nonbasic variable + printf( + "HighsIis::compute Row %d [%g, %g, %g] with status %s has " + "dual %g\n", + int(iRow), lower, value, upper, + highs.basisStatusToString(status).c_str(), dual); + // assert(123 == 456); + } + } + highs.setOptionValue("output_flag", output_flag); + highs.setOptionValue("simplex_strategy", simplex_strategy); + assert(!num_zero_dual); + } iis_info.simplex_time += highs.getRunTime(); iis_info.simplex_iterations += info.simplex_iteration_count; this->info_.push_back(iis_info); @@ -258,29 +323,14 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, assert(highs.getModelStatus() == HighsModelStatus::kInfeasible); - const bool use_sensitivity_filter = false; - if (use_sensitivity_filter) { - bool output_flag; - highs.getOptionValue("output_flag", output_flag); - highs.setOptionValue("simplex_strategy", kSimplexStrategyPrimal); - // - highs.setOptionValue("output_flag", true); - // Solve the LP - run_status = solveLp(); - if (run_status != HighsStatus::kOk) return run_status; - highs.writeSolution("", kSolutionStylePretty); - highs.setOptionValue("output_flag", output_flag); - } - // Pass twice: rows before columns, or columns before rows, according to // row_priority for (HighsInt k = 0; k < 2; k++) { - const bool row_deletion = - (row_priority && k == 0) || (!row_priority && k == 1); + row_deletion = (row_priority && k == 0) || (!row_priority && k == 1); std::string type = row_deletion ? "Row" : "Col"; // Perform deletion pass HighsInt num_index = row_deletion ? lp.num_row_ : lp.num_col_; - for (HighsInt iX = 0; iX < num_index; iX++) { + for (iX = 0; iX < num_index; iX++) { const HighsInt ix_status = row_deletion ? this->row_bound_[iX] : this->col_bound_[iX]; if (ix_status == kIisBoundStatusDropped || @@ -292,6 +342,7 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, // being kept if (lower > -kHighsInf) { // Drop the lower bound temporarily + bool drop_lower = true; run_status = row_deletion ? highs.changeRowBounds(iX, -kHighsInf, upper) : highs.changeColBounds(iX, -kHighsInf, upper); diff --git a/src/simplex/HEkk.cpp b/src/simplex/HEkk.cpp index b94d0959f6..459bdd7a52 100644 --- a/src/simplex/HEkk.cpp +++ b/src/simplex/HEkk.cpp @@ -161,6 +161,8 @@ void HEkk::clearEkkData() { this->debug_max_relative_dual_steepest_edge_weight_error = 0; clearBadBasisChange(); + + this->primal_phase1_dual_.clear(); } void HEkk::clearEkkDataInfo() { diff --git a/src/simplex/HEkk.h b/src/simplex/HEkk.h index c91aba199a..27e927a67d 100644 --- a/src/simplex/HEkk.h +++ b/src/simplex/HEkk.h @@ -210,6 +210,7 @@ class HEkk { double debug_max_relative_dual_steepest_edge_weight_error = 0; std::vector bad_basis_change_; + std::vector primal_phase1_dual_; private: bool isUnconstrainedLp(); diff --git a/src/simplex/HEkkPrimal.cpp b/src/simplex/HEkkPrimal.cpp index 8c9787cee4..1bf52aa439 100644 --- a/src/simplex/HEkkPrimal.cpp +++ b/src/simplex/HEkkPrimal.cpp @@ -253,6 +253,11 @@ HighsStatus HEkkPrimal::solve(const bool pass_force_phase2) { // LP identified as not having an optimal solution assert(ekk_instance_.model_status_ == HighsModelStatus::kInfeasible || ekk_instance_.model_status_ == HighsModelStatus::kUnbounded); + // If infeasible, save the primal phase 1 dual values before + // they are overwritten with the duals for the original + // objective + if (ekk_instance_.model_status_ == HighsModelStatus::kInfeasible) + ekk_instance_.primal_phase1_dual_ = ekk_instance_.info_.workDual_; break; } if (solve_phase == kSolvePhaseOptimalCleanup) { From 73c487eb5da43eb50dd3671882cc6f389fc31215 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 19 Jun 2024 18:14:37 +0100 Subject: [PATCH 030/194] First steps to compute primal phase 1 dual for sensitivity filter --- src/lp_data/HighsIis.cpp | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/lp_data/HighsIis.cpp b/src/lp_data/HighsIis.cpp index ec641467d6..6046d44ff0 100644 --- a/src/lp_data/HighsIis.cpp +++ b/src/lp_data/HighsIis.cpp @@ -178,6 +178,8 @@ HighsStatus HighsIis::getData(const HighsLp& lp, const HighsOptions& options, HighsLp to_lp; to_lp.num_col_ = to_num_col; to_lp.num_row_ = to_num_row; + to_lp.a_matrix_.num_col_ = to_lp.num_col_; + to_lp.a_matrix_.num_row_ = to_lp.num_row_; const bool has_col_names = lp.col_names_.size() > 0; for (HighsInt iCol = 0; iCol < to_num_col; iCol++) { to_lp.col_cost_.push_back(0); @@ -240,7 +242,7 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, assert(run_status == HighsStatus::kOk); // Solve the LP if (basis) highs.setBasis(*basis); - const bool use_sensitivity_filter = false; + const bool use_sensitivity_filter = true; std::vector primal_phase1_dual; bool row_deletion = false; HighsInt iX = -1; @@ -270,6 +272,32 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, run_status = highs.run(); if (run_status != HighsStatus::kOk) return run_status; highs.writeSolution("", kSolutionStylePretty); + const HighsInt* basic_index = highs.getBasicVariablesArray(); + std::vector rhs; + rhs.assign(lp.num_row_, 0); + // Get duals for nonbasic rows, and initialise duals so that basic duals are zero + assert(101==202); + + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { + HighsInt iVar = basic_index[iRow]; + const double lower = iVar < lp.num_col_ ? lp.col_lower_[iVar] : lp.row_lower_[iVar-lp.num_col_]; + const double upper = iVar < lp.num_col_ ? lp.col_upper_[iVar] : lp.row_upper_[iVar-lp.num_col_]; + const double value = iVar < lp.num_col_ ? solution.col_value[iVar] : solution.row_value[iVar-lp.num_col_]; + if (value < lower - options.primal_feasibility_tolerance) { + rhs[iRow] = -1; + } else if (value > upper + options.primal_feasibility_tolerance) { + rhs[iRow] = 1; + } + } + HVector pi; + pi.setup(lp.num_row_); + highs.getBasisTransposeSolve(rhs.data(), &pi.array[0], NULL, NULL); + pi.count = lp.num_row_; + std::vector reduced_costs_value; + std::vector reduced_costs_index; + lp.a_matrix_.productTransposeQuad(reduced_costs_value, reduced_costs_index, pi); + + primal_phase1_dual = highs.getPrimalPhase1Dual(); HighsInt num_zero_dual = 0; for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { From 4a09183b851f68f4dcad5106dde8f8a7b8700c5c Mon Sep 17 00:00:00 2001 From: fwesselm Date: Sun, 30 Jun 2024 21:27:47 +0200 Subject: [PATCH 031/194] Compute bound differences using HighsCDouble --- src/mip/HighsDomain.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/mip/HighsDomain.cpp b/src/mip/HighsDomain.cpp index a00e8784c0..eb38717812 100644 --- a/src/mip/HighsDomain.cpp +++ b/src/mip/HighsDomain.cpp @@ -46,18 +46,18 @@ static double activityContributionMax(double coef, const double& lb, } } -static double computeDelta(double val, double oldbound, double newbound, - double inf, HighsInt& numinfs) { - // if bounds are huge, HighsCDouble should be used when computing bound - // differences. todo: qualify usage of HighsCDouble in this function. +static HighsCDouble computeDelta(double val, double oldbound, double newbound, + double inf, HighsInt& numinfs) { if (oldbound == inf) { --numinfs; - return newbound * val; + return static_cast(newbound) * val; } else if (newbound == inf) { ++numinfs; - return -oldbound * val; + return static_cast(-oldbound) * val; } else { - return (newbound - oldbound) * val; + return (static_cast(newbound) - + static_cast(oldbound)) * + val; } } @@ -481,8 +481,8 @@ void HighsDomain::CutpoolPropagation::updateActivityLbChange(HighsInt col, cutpool->getMatrix().forEachPositiveColumnEntry( col, [&](HighsInt row, double val) { assert(val > 0); - double deltamin = computeDelta(val, oldbound, newbound, -kHighsInf, - activitycutsinf_[row]); + HighsCDouble deltamin = computeDelta(val, oldbound, newbound, + -kHighsInf, activitycutsinf_[row]); activitycuts_[row] += deltamin; if (deltamin <= 0) { @@ -542,8 +542,8 @@ void HighsDomain::CutpoolPropagation::updateActivityUbChange(HighsInt col, cutpool->getMatrix().forEachNegativeColumnEntry( col, [&](HighsInt row, double val) { assert(val < 0); - double deltamin = computeDelta(val, oldbound, newbound, kHighsInf, - activitycutsinf_[row]); + HighsCDouble deltamin = computeDelta(val, oldbound, newbound, kHighsInf, + activitycutsinf_[row]); activitycuts_[row] += deltamin; if (deltamin <= 0) { @@ -1528,7 +1528,7 @@ void HighsDomain::updateActivityLbChange(HighsInt col, double oldbound, for (HighsInt i = start; i != end; ++i) { if (mip->a_matrix_.value_[i] > 0) { - double deltamin = + HighsCDouble deltamin = computeDelta(mip->a_matrix_.value_[i], oldbound, newbound, -kHighsInf, activitymininf_[mip->a_matrix_.index_[i]]); activitymin_[mip->a_matrix_.index_[i]] += deltamin; @@ -1572,7 +1572,7 @@ void HighsDomain::updateActivityLbChange(HighsInt col, double oldbound, mip->row_upper_[mip->a_matrix_.index_[i]] != kHighsInf) markPropagate(mip->a_matrix_.index_[i]); } else { - double deltamax = + HighsCDouble deltamax = computeDelta(mip->a_matrix_.value_[i], oldbound, newbound, -kHighsInf, activitymaxinf_[mip->a_matrix_.index_[i]]); activitymax_[mip->a_matrix_.index_[i]] += deltamax; @@ -1667,7 +1667,7 @@ void HighsDomain::updateActivityUbChange(HighsInt col, double oldbound, for (HighsInt i = start; i != end; ++i) { if (mip->a_matrix_.value_[i] > 0) { - double deltamax = + HighsCDouble deltamax = computeDelta(mip->a_matrix_.value_[i], oldbound, newbound, kHighsInf, activitymaxinf_[mip->a_matrix_.index_[i]]); activitymax_[mip->a_matrix_.index_[i]] += deltamax; @@ -1714,7 +1714,7 @@ void HighsDomain::updateActivityUbChange(HighsInt col, double oldbound, // propagateinds_.push_back(mip->a_matrix_.index_[i]); } } else { - double deltamin = + HighsCDouble deltamin = computeDelta(mip->a_matrix_.value_[i], oldbound, newbound, kHighsInf, activitymininf_[mip->a_matrix_.index_[i]]); activitymin_[mip->a_matrix_.index_[i]] += deltamin; From 3681efa428da085ad251e72329d0ae1ba968dc79 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Sun, 30 Jun 2024 21:51:49 +0200 Subject: [PATCH 032/194] Use tolerances when rounding in postsolve --- src/presolve/HighsPostsolveStack.cpp | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/presolve/HighsPostsolveStack.cpp b/src/presolve/HighsPostsolveStack.cpp index f490a3689a..6172394186 100644 --- a/src/presolve/HighsPostsolveStack.cpp +++ b/src/presolve/HighsPostsolveStack.cpp @@ -905,10 +905,12 @@ bool HighsPostsolveStack::DuplicateColumn::okMerge( const double scale = colScale; const bool x_int = colIntegral; const bool y_int = duplicateColIntegral; - const double x_lo = x_int ? std::ceil(colLower) : colLower; - const double x_up = x_int ? std::floor(colUpper) : colUpper; - const double y_lo = y_int ? std::ceil(duplicateColLower) : duplicateColLower; - const double y_up = y_int ? std::floor(duplicateColUpper) : duplicateColUpper; + const double x_lo = x_int ? std::ceil(colLower - tolerance) : colLower; + const double x_up = x_int ? std::floor(colUpper + tolerance) : colUpper; + const double y_lo = + y_int ? std::ceil(duplicateColLower - tolerance) : duplicateColLower; + const double y_up = + y_int ? std::floor(duplicateColUpper + tolerance) : duplicateColUpper; const double x_len = x_up - x_lo; const double y_len = y_up - y_lo; std::string newline = "\n"; @@ -1027,10 +1029,16 @@ void HighsPostsolveStack::DuplicateColumn::undoFix( const bool y_int = duplicateColIntegral; const int x_ix = col; const int y_ix = duplicateCol; - const double x_lo = x_int ? std::ceil(colLower) : colLower; - const double x_up = x_int ? std::floor(colUpper) : colUpper; - const double y_lo = y_int ? std::ceil(duplicateColLower) : duplicateColLower; - const double y_up = y_int ? std::floor(duplicateColUpper) : duplicateColUpper; + const double x_lo = + x_int ? std::ceil(colLower - mip_feasibility_tolerance) : colLower; + const double x_up = + x_int ? std::floor(colUpper + mip_feasibility_tolerance) : colUpper; + const double y_lo = + y_int ? std::ceil(duplicateColLower - mip_feasibility_tolerance) + : duplicateColLower; + const double y_up = + y_int ? std::floor(duplicateColUpper + mip_feasibility_tolerance) + : duplicateColUpper; if (kAllowDeveloperAssert) assert(scale); double x_v = merge_value; double y_v; From 9a98a6fc09a624814b146bac9968e893db4c58db Mon Sep 17 00:00:00 2001 From: fwesselm Date: Mon, 1 Jul 2024 11:10:06 +0200 Subject: [PATCH 033/194] Minor change --- src/presolve/HighsPostsolveStack.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/presolve/HighsPostsolveStack.cpp b/src/presolve/HighsPostsolveStack.cpp index 6172394186..bd73f128e4 100644 --- a/src/presolve/HighsPostsolveStack.cpp +++ b/src/presolve/HighsPostsolveStack.cpp @@ -1017,9 +1017,8 @@ void HighsPostsolveStack::DuplicateColumn::undoFix( }; auto isFeasible = [&](const double l, const double v, const double u) { - if (v < l - primal_feasibility_tolerance) return false; - if (v > u + primal_feasibility_tolerance) return false; - return true; + return v >= l - primal_feasibility_tolerance && + v <= u + primal_feasibility_tolerance; }; const double merge_value = col_value[col]; const double value_max = 1000; From 600b069569b30070bc390e6188d92bf0db3649d9 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Tue, 9 Jul 2024 15:40:53 +0100 Subject: [PATCH 034/194] Added int64_t mip_total_lp_iterations to HighsCallbackDataOut and modified accessor function --- check/TestCallbacks.cpp | 9 +++++---- src/interfaces/highs_c_api.cpp | 3 +++ src/interfaces/highs_c_api.h | 2 ++ src/lp_data/HighsCallbackStruct.h | 1 + src/mip/HighsMipSolverData.cpp | 2 ++ 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/check/TestCallbacks.cpp b/check/TestCallbacks.cpp index 171bf52721..0c221288e8 100644 --- a/check/TestCallbacks.cpp +++ b/check/TestCallbacks.cpp @@ -190,12 +190,13 @@ std::functionmip_node_count, data_out->running_time, - data_out->mip_dual_bound, data_out->mip_primal_bound, - data_out->mip_gap, data_out->objective_function_value, - message.c_str()); + data_out->mip_node_count, data_out->mip_total_lp_iterations, + data_out->running_time, data_out->mip_dual_bound, + data_out->mip_primal_bound, data_out->mip_gap, + data_out->objective_function_value, message.c_str()); }; TEST_CASE("my-callback-logging", "[highs-callback]") { diff --git a/src/interfaces/highs_c_api.cpp b/src/interfaces/highs_c_api.cpp index 07e8a0054e..0642b73283 100644 --- a/src/interfaces/highs_c_api.cpp +++ b/src/interfaces/highs_c_api.cpp @@ -1364,6 +1364,9 @@ const void* Highs_getCallbackDataOutItem(const HighsCallbackDataOut* data_out, return (void*)(&data_out->objective_function_value); } else if (!strcmp(item_name, kHighsCallbackDataOutMipNodeCountName)) { return (void*)(&data_out->mip_node_count); + } else if (!strcmp(item_name, + kHighsCallbackDataOutMipTotalLpIterationsName)) { + return (void*)(&data_out->mip_total_lp_iterations); } else if (!strcmp(item_name, kHighsCallbackDataOutMipPrimalBoundName)) { return (void*)(&data_out->mip_primal_bound); } else if (!strcmp(item_name, kHighsCallbackDataOutMipDualBoundName)) { diff --git a/src/interfaces/highs_c_api.h b/src/interfaces/highs_c_api.h index c9c5ad1cd1..1a43dd8e81 100644 --- a/src/interfaces/highs_c_api.h +++ b/src/interfaces/highs_c_api.h @@ -120,6 +120,8 @@ const char* const kHighsCallbackDataOutPdlpIterationCountName = const char* const kHighsCallbackDataOutObjectiveFunctionValueName = "objective_function_value"; const char* const kHighsCallbackDataOutMipNodeCountName = "mip_node_count"; +const char* const kHighsCallbackDataOutMipTotalLpIterationsName = + "mip_total_lp_iterations"; const char* const kHighsCallbackDataOutMipPrimalBoundName = "mip_primal_bound"; const char* const kHighsCallbackDataOutMipDualBoundName = "mip_dual_bound"; const char* const kHighsCallbackDataOutMipGapName = "mip_gap"; diff --git a/src/lp_data/HighsCallbackStruct.h b/src/lp_data/HighsCallbackStruct.h index b76716d3eb..a5f7140834 100644 --- a/src/lp_data/HighsCallbackStruct.h +++ b/src/lp_data/HighsCallbackStruct.h @@ -32,6 +32,7 @@ typedef struct { HighsInt pdlp_iteration_count; double objective_function_value; int64_t mip_node_count; + int64_t mip_total_lp_iterations; double mip_primal_bound; double mip_dual_bound; double mip_gap; diff --git a/src/mip/HighsMipSolverData.cpp b/src/mip/HighsMipSolverData.cpp index 1d926134ab..1b0610a340 100644 --- a/src/mip/HighsMipSolverData.cpp +++ b/src/mip/HighsMipSolverData.cpp @@ -1999,6 +1999,8 @@ bool HighsMipSolverData::interruptFromCallbackWithData( mipsolver.callback_->data_out.objective_function_value = mipsolver_objective_value; mipsolver.callback_->data_out.mip_node_count = mipsolver.mipdata_->num_nodes; + mipsolver.callback_->data_out.mip_total_lp_iterations = + mipsolver.mipdata_->total_lp_iterations; mipsolver.callback_->data_out.mip_primal_bound = primal_bound; mipsolver.callback_->data_out.mip_dual_bound = dual_bound; // Option mip_rel_gap, and mip_gap in HighsInfo, are both fractions, From 66849cac15451c76bafd5d40b3130e8f26b794b4 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Tue, 9 Jul 2024 15:43:38 +0100 Subject: [PATCH 035/194] Reset FEATURES.md and added comment on fix-1814 --- FEATURES.md | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/FEATURES.md b/FEATURES.md index c587caf6ee..9d7043302e 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -1,22 +1,6 @@ ## Build changes -The python wrapper highspy is now available for aarch64 on manylinux -This allows highs to be run through Python on AWS arm64 - -Bug fix for fortran on macOS - ## Code changes -The accessor function Highs_getCallbackDataOutItem in the C API means -that `pdlp_iteration_count` can be moved back to where it was inserted -into the `HighsCallbackDataOut` struct in v1.7.0, which broke the C -API. This fixes #1812 - -Some duplicate code has been eliminated from the MIP solver, and -modifications made to eliminate compiler warnings - -Declaration of the (deprecated) method `char* highsCompilationDate()` -has been corrected - -Fixed bug when describing integrality status during the human-readable solution write +Added `int64_t mip_total_lp_iterations` to `HighsCallbackDataOut` and modified accessor function From 2d078b0c5e3810d9a5ef97bfd000213d3d422288 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Tue, 9 Jul 2024 22:49:12 +0100 Subject: [PATCH 036/194] Introduced highsFprintfString to replace fprintf(file so that solution etc to console can be handled through callbacks --- check/TestCallbacks.cpp | 22 +++- src/io/HighsIO.cpp | 10 ++ src/io/HighsIO.h | 7 ++ src/lp_data/HighsModelUtils.cpp | 188 +++++++++++++++++++------------- src/lp_data/HighsModelUtils.h | 21 ++-- src/mip/HighsMipSolverData.cpp | 9 +- 6 files changed, 166 insertions(+), 91 deletions(-) diff --git a/check/TestCallbacks.cpp b/check/TestCallbacks.cpp index 0c221288e8..06d9cc7e94 100644 --- a/check/TestCallbacks.cpp +++ b/check/TestCallbacks.cpp @@ -6,7 +6,7 @@ #include "catch.hpp" #include "lp_data/HighsCallback.h" -const bool dev_run = false; +const bool dev_run = true; const double egout_optimal_objective = 568.1007; const double egout_objective_target = 610; @@ -122,8 +122,9 @@ HighsCallbackFunctionType userInterruptCallback = } if (callback_type == kCallbackLogging) { if (dev_run) - printf("userInterruptCallback(type %2d; data %2d): %s", - callback_type, local_callback_data, message.c_str()); + printf("Callback: %s", message.c_str()); +// printf("userInterruptCallback(type %2d; data %2d): %s", +// callback_type, local_callback_data, message.c_str()); } else if (callback_type == kCallbackSimplexInterrupt) { if (dev_run) printf( @@ -265,6 +266,21 @@ TEST_CASE("highs-callback-logging", "[highs-callback]") { highs.run(); } +TEST_CASE("highs-callback-solution-basis-logging", "[highs-callback]") { + std::string filename = std::string(HIGHS_DIR) + "/check/instances/avgas.mps"; + int user_callback_data = kUserCallbackData; + void* p_user_callback_data = + reinterpret_cast(static_cast(user_callback_data)); + Highs highs; + highs.setOptionValue("output_flag", dev_run); + highs.readModel(filename); + highs.run(); + highs.setCallback(userInterruptCallback, p_user_callback_data); + highs.startCallback(kCallbackLogging); + highs.writeSolution("", kSolutionStylePretty); + highs.writeBasis(""); +} + TEST_CASE("highs-callback-simplex-interrupt", "[highs-callback]") { std::string filename = std::string(HIGHS_DIR) + "/check/instances/adlittle.mps"; diff --git a/src/io/HighsIO.cpp b/src/io/HighsIO.cpp index 38fbd6b405..fc622a5c20 100644 --- a/src/io/HighsIO.cpp +++ b/src/io/HighsIO.cpp @@ -219,6 +219,16 @@ void highsLogDev(const HighsLogOptions& log_options_, const HighsLogType type, va_end(argptr); } +void highsFprintfString(FILE* file, const HighsLogOptions& log_options_, + const std::string& s) { + if (file == nullptr) return; + if (file == stdout) { + highsLogUser(log_options_, HighsLogType::kInfo, "%s", s.c_str()); + } else { + fprintf(file, "%s", s.c_str()); + } +} + void highsReportDevInfo(const HighsLogOptions* log_options, const std::string line) { if (log_options) { diff --git a/src/io/HighsIO.h b/src/io/HighsIO.h index adead90c24..6da477a5d5 100644 --- a/src/io/HighsIO.h +++ b/src/io/HighsIO.h @@ -88,6 +88,13 @@ void highsLogUser(const HighsLogOptions& log_options_, const HighsLogType type, void highsLogDev(const HighsLogOptions& log_options_, const HighsLogType type, const char* format, ...); +/** + * @brief Replaces fprintf(file,... so that when file=stdout highsLogUser is used + */ +// Printing format: must contain exactly one "\n" at end of format +void highsFprintfString(FILE* file, const HighsLogOptions& log_options_, + const std::string& s); + /** * @brief For development logging when true log_options may not be available - * indicated by null pointer diff --git a/src/lp_data/HighsModelUtils.cpp b/src/lp_data/HighsModelUtils.cpp index 6a60010282..201c090581 100644 --- a/src/lp_data/HighsModelUtils.cpp +++ b/src/lp_data/HighsModelUtils.cpp @@ -133,7 +133,8 @@ std::string typeToString(const HighsVarType type) { } void writeModelBoundSolution( - FILE* file, const bool columns, const HighsInt dim, + FILE* file, const HighsLogOptions& log_options, + const bool columns, const HighsInt dim, const std::vector& lower, const std::vector& upper, const std::vector& names, const bool have_primal, const std::vector& primal, const bool have_dual, @@ -146,73 +147,73 @@ void writeModelBoundSolution( if (have_dual) assert((int)dual.size() >= dim); if (have_basis) assert((int)status.size() >= dim); const bool have_integrality = integrality != NULL; - std::string var_status_string; - if (columns) { - fprintf(file, "Columns\n"); - } else { - fprintf(file, "Rows\n"); - } - fprintf( - file, - " Index Status Lower Upper Primal Dual"); - if (have_integrality) fprintf(file, " Type "); + std::stringstream ss; + std::string s = columns ? "Columns\n" : "Rows\n"; + highsFprintfString(file, log_options, s); + ss.str(std::string()); + ss << " Index Status Lower Upper Primal Dual"; + if (have_integrality) ss << " Type "; if (have_names) { - fprintf(file, " Name\n"); + ss << " Name\n"; } else { - fprintf(file, "\n"); + ss << "\n"; } + highsFprintfString(file, log_options, ss.str()); for (HighsInt ix = 0; ix < dim; ix++) { - if (have_basis) { - var_status_string = statusToString(status[ix], lower[ix], upper[ix]); - } else { - var_status_string = ""; - } - fprintf(file, "%9" HIGHSINT_FORMAT " %4s %12g %12g", ix, - var_status_string.c_str(), lower[ix], upper[ix]); + ss.str(std::string()); + std::string var_status_string = have_basis ? + statusToString(status[ix], lower[ix], upper[ix]) : ""; + ss << highsFormatToString("%9" HIGHSINT_FORMAT " %4s %12g %12g", ix, + var_status_string.c_str(), lower[ix], upper[ix]); if (have_primal) { - fprintf(file, " %12g", primal[ix]); + ss << highsFormatToString(" %12g", primal[ix]); } else { - fprintf(file, " "); + ss << " "; } if (have_dual) { - fprintf(file, " %12g", dual[ix]); + ss << highsFormatToString(" %12g", dual[ix]); } else { - fprintf(file, " "); + ss << " "; } if (have_integrality) - fprintf(file, " %s", typeToString(integrality[ix]).c_str()); + ss << highsFormatToString(" %s", typeToString(integrality[ix]).c_str()); if (have_names) { - fprintf(file, " %-s\n", names[ix].c_str()); + ss << highsFormatToString(" %-s\n", names[ix].c_str()); } else { - fprintf(file, "\n"); + ss << "\n"; } + highsFprintfString(file, log_options, ss.str()); } } -void writeModelObjective(FILE* file, const HighsModel& model, +void writeModelObjective(FILE* file, const HighsLogOptions& log_options, + const HighsModel& model, const std::vector& primal_solution) { HighsCDouble objective_value = model.lp_.objectiveCDoubleValue(primal_solution); objective_value += model.hessian_.objectiveCDoubleValue(primal_solution); - writeObjectiveValue(file, (double)objective_value); + writeObjectiveValue(file, log_options, (double)objective_value); } -void writeLpObjective(FILE* file, const HighsLp& lp, +void writeLpObjective(FILE* file, const HighsLogOptions& log_options, + const HighsLp& lp, const std::vector& primal_solution) { HighsCDouble objective_value = lp.objectiveCDoubleValue(primal_solution); - writeObjectiveValue(file, (double)objective_value); + writeObjectiveValue(file, log_options, (double)objective_value); } -void writeObjectiveValue(FILE* file, const double objective_value) { +void writeObjectiveValue(FILE* file, const HighsLogOptions& log_options, + const double objective_value) { std::array objStr = highsDoubleToString( objective_value, kHighsSolutionValueToStringTolerance); - fprintf(file, "Objective %s\n", objStr.data()); + std::string s = highsFormatToString("Objective %s\n", objStr.data()); + highsFprintfString(file, log_options, s); } -void writePrimalSolution(FILE* file, const HighsLp& lp, +void writePrimalSolution(FILE* file, const HighsLogOptions& log_options, + const HighsLp& lp, const std::vector& primal_solution, const bool sparse) { - std::stringstream ss; HighsInt num_nonzero_primal_value = 0; const bool have_col_names = lp.col_names_.size() > 0; if (sparse) { @@ -223,8 +224,12 @@ void writePrimalSolution(FILE* file, const HighsLp& lp, // Indicate the number of column values to be written out, depending // on whether format is sparse: either lp.num_col_ if not sparse, or // the negation of the number of nonzero values, if sparse - fprintf(file, "# Columns %" HIGHSINT_FORMAT "\n", + + std::stringstream ss; + ss.str(std::string()); + ss << highsFormatToString("# Columns %" HIGHSINT_FORMAT "\n", sparse ? -num_nonzero_primal_value : lp.num_col_); + highsFprintfString(file, log_options, ss.str()); for (HighsInt ix = 0; ix < lp.num_col_; ix++) { if (sparse && !primal_solution[ix]) continue; std::array valStr = highsDoubleToString( @@ -233,12 +238,16 @@ void writePrimalSolution(FILE* file, const HighsLp& lp, ss.str(std::string()); ss << "C" << ix; const std::string name = have_col_names ? lp.col_names_[ix] : ss.str(); - fprintf(file, "%-s %s", name.c_str(), valStr.data()); - if (sparse) fprintf(file, " %d", int(ix)); - fprintf(file, "\n"); + ss.str(std::string()); + ss << highsFormatToString("%-s %s", name.c_str(), valStr.data()); + if (sparse) ss << highsFormatToString(" %d", int(ix)); + ss << "\n"; + highsFprintfString(file, log_options, ss.str()); } } -void writeModelSolution(FILE* file, const HighsModel& model, + +void writeModelSolution(FILE* file, const HighsLogOptions& log_options, + const HighsModel& model, const HighsSolution& solution, const HighsInfo& info, const bool sparse) { const HighsLp& lp = model.lp_; @@ -246,7 +255,6 @@ void writeModelSolution(FILE* file, const HighsModel& model, const bool have_row_names = lp.row_names_.size() > 0; const bool have_primal = solution.value_valid; const bool have_dual = solution.dual_valid; - std::stringstream ss; if (have_col_names) assert((int)lp.col_names_.size() >= lp.num_col_); if (have_row_names) assert((int)lp.row_names_.size() >= lp.num_row_); if (have_primal) { @@ -259,20 +267,23 @@ void writeModelSolution(FILE* file, const HighsModel& model, assert((int)solution.row_dual.size() >= lp.num_row_); assert(info.dual_solution_status != kSolutionStatusNone); } - fprintf(file, "\n# Primal solution values\n"); + std::stringstream ss; + highsFprintfString(file, log_options, "\n# Primal solution values\n"); if (!have_primal || info.primal_solution_status == kSolutionStatusNone) { - fprintf(file, "None\n"); + highsFprintfString(file, log_options, "None\n"); } else { if (info.primal_solution_status == kSolutionStatusFeasible) { - fprintf(file, "Feasible\n"); + highsFprintfString(file, log_options, "Feasible\n"); } else { assert(info.primal_solution_status == kSolutionStatusInfeasible); - fprintf(file, "Infeasible\n"); + highsFprintfString(file, log_options, "Infeasible\n"); } - writeModelObjective(file, model, solution.col_value); - writePrimalSolution(file, model.lp_, solution.col_value, sparse); + writeModelObjective(file, log_options, model, solution.col_value); + writePrimalSolution(file, log_options, model.lp_, solution.col_value, sparse); if (sparse) return; - fprintf(file, "# Rows %" HIGHSINT_FORMAT "\n", lp.num_row_); + ss.str(std::string()); + ss << highsFormatToString("# Rows %" HIGHSINT_FORMAT "\n", lp.num_row_); + highsFprintfString(file, log_options, ss.str()); for (HighsInt ix = 0; ix < lp.num_row_; ix++) { std::array valStr = highsDoubleToString( solution.row_value[ix], kHighsSolutionValueToStringTolerance); @@ -280,36 +291,46 @@ void writeModelSolution(FILE* file, const HighsModel& model, ss.str(std::string()); ss << "R" << ix; const std::string name = have_row_names ? lp.row_names_[ix] : ss.str(); - fprintf(file, "%-s %s\n", name.c_str(), valStr.data()); + ss.str(std::string()); + ss << highsFormatToString("%-s %s\n", name.c_str(), valStr.data()); + highsFprintfString(file, log_options, ss.str()); } } - fprintf(file, "\n# Dual solution values\n"); + highsFprintfString(file, log_options, "\n# Dual solution values\n"); if (!have_dual || info.dual_solution_status == kSolutionStatusNone) { - fprintf(file, "None\n"); + highsFprintfString(file, log_options, "None\n"); } else { if (info.dual_solution_status == kSolutionStatusFeasible) { - fprintf(file, "Feasible\n"); + highsFprintfString(file, log_options, "Feasible\n"); } else { assert(info.dual_solution_status == kSolutionStatusInfeasible); - fprintf(file, "Infeasible\n"); + highsFprintfString(file, log_options, "Infeasible\n"); } - fprintf(file, "# Columns %" HIGHSINT_FORMAT "\n", lp.num_col_); + ss.str(std::string()); + ss << highsFormatToString("# Columns %" HIGHSINT_FORMAT "\n", lp.num_col_); + highsFprintfString(file, log_options, ss.str()); for (HighsInt ix = 0; ix < lp.num_col_; ix++) { std::array valStr = highsDoubleToString( solution.col_dual[ix], kHighsSolutionValueToStringTolerance); ss.str(std::string()); ss << "C" << ix; const std::string name = have_col_names ? lp.col_names_[ix] : ss.str(); - fprintf(file, "%-s %s\n", name.c_str(), valStr.data()); + ss.str(std::string()); + ss << highsFormatToString("%-s %s\n", name.c_str(), valStr.data()); + highsFprintfString(file, log_options, ss.str()); } - fprintf(file, "# Rows %" HIGHSINT_FORMAT "\n", lp.num_row_); + ss.str(std::string()); + ss << highsFormatToString("# Rows %" HIGHSINT_FORMAT "\n", lp.num_row_); + highsFprintfString(file, log_options, ss.str()); for (HighsInt ix = 0; ix < lp.num_row_; ix++) { std::array valStr = highsDoubleToString( solution.row_dual[ix], kHighsSolutionValueToStringTolerance); ss.str(std::string()); ss << "R" << ix; const std::string name = have_row_names ? lp.row_names_[ix] : ss.str(); - fprintf(file, "%-s %s\n", name.c_str(), valStr.data()); + ss.str(std::string()); + ss << highsFormatToString("%-s %s\n", name.c_str(), valStr.data()); + highsFprintfString(file, log_options, ss.str()); } } } @@ -395,25 +416,33 @@ void writeSolutionFile(FILE* file, const HighsOptions& options, const bool have_dual = solution.dual_valid; const bool have_basis = basis.valid; const HighsLp& lp = model.lp_; + const HighsLogOptions& log_options = options.log_options; if (style == kSolutionStyleOldRaw) { - writeOldRawSolution(file, lp, basis, solution); + writeOldRawSolution(file, log_options, lp, basis, solution); } else if (style == kSolutionStylePretty) { const HighsVarType* integrality = lp.integrality_.size() > 0 ? lp.integrality_.data() : nullptr; - writeModelBoundSolution(file, true, lp.num_col_, lp.col_lower_, + writeModelBoundSolution(file, log_options, true, lp.num_col_, lp.col_lower_, lp.col_upper_, lp.col_names_, have_primal, solution.col_value, have_dual, solution.col_dual, have_basis, basis.col_status, integrality); - writeModelBoundSolution(file, false, lp.num_row_, lp.row_lower_, + writeModelBoundSolution(file, log_options, false, lp.num_row_, lp.row_lower_, lp.row_upper_, lp.row_names_, have_primal, solution.row_value, have_dual, solution.row_dual, have_basis, basis.row_status); - fprintf(file, "\nModel status: %s\n", + highsFprintfString(file, log_options, "\n"); + std::stringstream ss; + ss.str(std::string()); + ss << highsFormatToString("Model status: %s\n", utilModelStatusToString(model_status).c_str()); + highsFprintfString(file, log_options, ss.str()); std::array objStr = highsDoubleToString((double)info.objective_function_value, kHighsSolutionValueToStringTolerance); - fprintf(file, "\nObjective value: %s\n", objStr.data()); + highsFprintfString(file, log_options, "\n"); + ss.str(std::string()); + ss << highsFormatToString("Objective value: %s\n", objStr.data()); + highsFprintfString(file, log_options, ss.str()); } else if (style == kSolutionStyleGlpsolRaw || style == kSolutionStyleGlpsolPretty) { const bool raw = style == kSolutionStyleGlpsolRaw; @@ -423,9 +452,12 @@ void writeSolutionFile(FILE* file, const HighsOptions& options, // Standard raw solution file, possibly sparse => only nonzero primal values const bool sparse = style == kSolutionStyleSparse; assert(style == kSolutionStyleRaw || sparse); - fprintf(file, "Model status\n"); - fprintf(file, "%s\n", utilModelStatusToString(model_status).c_str()); - writeModelSolution(file, model, solution, info, sparse); + highsFprintfString(file, log_options, "Model status\n"); + std::stringstream ss; + ss.str(std::string()); + ss << highsFormatToString("%s\n", utilModelStatusToString(model_status).c_str()); + highsFprintfString(file, log_options, ss.str()); + writeModelSolution(file, log_options, model, solution, info, sparse); } } @@ -951,14 +983,15 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, double absolute_error_value; HighsInt relative_error_index; double relative_error_value; + const HighsLogOptions& log_options = options.log_options; getKktFailures(options, model, solution, basis, local_info, errors, true); - fprintf(file, "\n"); + highsFprintfString(file, log_options, "\n"); if (is_mip) { - fprintf(file, "Integer feasibility conditions:\n"); + highsFprintfString(file, log_options, "Integer feasibility conditions:\n"); } else { - fprintf(file, "Karush-Kuhn-Tucker optimality conditions:\n"); + highsFprintfString(file, log_options, "Karush-Kuhn-Tucker optimality conditions:\n"); } - fprintf(file, "\n"); + highsFprintfString(file, log_options, "\n"); // Primal residual absolute_error_value = errors.max_primal_residual.absolute_value; absolute_error_index = errors.max_primal_residual.absolute_index + 1; @@ -976,7 +1009,7 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, : relative_error_value <= kGlpsolLowQuality ? "Low quality" : "PRIMAL SOLUTION IS WRONG"); - fprintf(file, "\n"); + highsFprintfString(file, log_options, "\n"); // Primal infeasibility absolute_error_value = errors.max_primal_infeasibility.absolute_value; @@ -1003,7 +1036,7 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, : relative_error_value <= kGlpsolLowQuality ? "Low quality" : "PRIMAL SOLUTION IS INFEASIBLE"); - fprintf(file, "\n"); + highsFprintfString(file, log_options, "\n"); if (have_dual) { // Dual residual @@ -1023,7 +1056,7 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, : relative_error_value <= kGlpsolLowQuality ? "Low quality" : "DUAL SOLUTION IS WRONG"); - fprintf(file, "\n"); + highsFprintfString(file, log_options, "\n"); // Dual infeasibility absolute_error_value = errors.max_dual_infeasibility.absolute_value; @@ -1051,12 +1084,13 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, : relative_error_value <= kGlpsolLowQuality ? "Low quality" : "DUAL SOLUTION IS INFEASIBLE"); - fprintf(file, "\n"); + highsFprintfString(file, log_options, "\n"); } - fprintf(file, "End of output\n"); + highsFprintfString(file, log_options, "End of output\n"); } -void writeOldRawSolution(FILE* file, const HighsLp& lp, const HighsBasis& basis, +void writeOldRawSolution(FILE* file, const HighsLogOptions& log_options, + const HighsLp& lp, const HighsBasis& basis, const HighsSolution& solution) { const bool have_value = solution.value_valid; const bool have_dual = solution.dual_valid; @@ -1103,7 +1137,7 @@ void writeOldRawSolution(FILE* file, const HighsLp& lp, const HighsBasis& basis, fprintf(file, "F"); } fprintf(file, " Basis\n"); - fprintf(file, "Columns\n"); + highsFprintfString(file, log_options, "Columns\n"); for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { if (have_value) fprintf(file, "%.15g ", use_col_value[iCol]); if (have_dual) fprintf(file, "%.15g ", use_col_dual[iCol]); @@ -1111,7 +1145,7 @@ void writeOldRawSolution(FILE* file, const HighsLp& lp, const HighsBasis& basis, fprintf(file, "%" HIGHSINT_FORMAT "", (HighsInt)use_col_status[iCol]); fprintf(file, "\n"); } - fprintf(file, "Rows\n"); + highsFprintfString(file, log_options, "Rows\n"); for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { if (have_value) fprintf(file, "%.15g ", use_row_value[iRow]); if (have_dual) fprintf(file, "%.15g ", use_row_dual[iRow]); diff --git a/src/lp_data/HighsModelUtils.h b/src/lp_data/HighsModelUtils.h index bd8d5e4b75..ac369ab5cf 100644 --- a/src/lp_data/HighsModelUtils.h +++ b/src/lp_data/HighsModelUtils.h @@ -31,7 +31,8 @@ bool hasNamesWithSpaces(const HighsLogOptions& log_options, const HighsInt num_name, const std::vector& names); void writeModelBoundSolution( - FILE* file, const bool columns, const HighsInt dim, + FILE* file, const HighsLogOptions& log_options, + const bool columns, const HighsInt dim, const std::vector& lower, const std::vector& upper, const std::vector& names, const bool have_primal, const std::vector& primal, const bool have_dual, @@ -39,19 +40,24 @@ void writeModelBoundSolution( const std::vector& status, const HighsVarType* integrality = NULL); -void writeModelObjective(FILE* file, const HighsModel& model, +void writeModelObjective(FILE* file, const HighsLogOptions& log_options, + const HighsModel& model, const std::vector& primal_solution); -void writeLpObjective(FILE* file, const HighsLp& lp, +void writeLpObjective(FILE* file, const HighsLogOptions& log_options, + const HighsLp& lp, const std::vector& primal_solution); -void writeObjectiveValue(FILE* file, const double objective_value); +void writeObjectiveValue(FILE* file, const HighsLogOptions& log_options, + const double objective_value); -void writePrimalSolution(FILE* file, const HighsLp& lp, +void writePrimalSolution(FILE* file, const HighsLogOptions& log_options, + const HighsLp& lp, const std::vector& primal_solution, const bool sparse = false); -void writeModelSolution(FILE* file, const HighsModel& model, +void writeModelSolution(FILE* file, const HighsLogOptions& log_options, + const HighsModel& model, const HighsSolution& solution, const HighsInfo& info, const bool sparse = false); @@ -78,7 +84,8 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, const HighsModelStatus model_status, const HighsInfo& info, const bool raw); -void writeOldRawSolution(FILE* file, const HighsLp& lp, const HighsBasis& basis, +void writeOldRawSolution(FILE* file, const HighsLogOptions& log_options, + const HighsLp& lp, const HighsBasis& basis, const HighsSolution& solution); HighsBasisStatus checkedVarHighsNonbasicStatus( diff --git a/src/mip/HighsMipSolverData.cpp b/src/mip/HighsMipSolverData.cpp index 1b0610a340..eafb952cf7 100644 --- a/src/mip/HighsMipSolverData.cpp +++ b/src/mip/HighsMipSolverData.cpp @@ -1941,10 +1941,11 @@ void HighsMipSolverData::saveReportMipSolution(const double new_upper_limit) { } FILE* file = mipsolver.improving_solution_file_; if (file) { - writeLpObjective(file, *(mipsolver.orig_model_), mipsolver.solution_); - writePrimalSolution( - file, *(mipsolver.orig_model_), mipsolver.solution_, - mipsolver.options_mip_->mip_improving_solution_report_sparse); + writeLpObjective(file, mipsolver.options_mip_->log_options, + *(mipsolver.orig_model_), mipsolver.solution_); + writePrimalSolution(file, mipsolver.options_mip_->log_options, + *(mipsolver.orig_model_), mipsolver.solution_, + mipsolver.options_mip_->mip_improving_solution_report_sparse); } } From 0c6b2539336ef353717099bfc4359fa716728016 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 10 Jul 2024 14:02:20 +0100 Subject: [PATCH 037/194] Replaced fprintf(file in HighsModelUtils.cpp by stringstreams and calls to highsFprintfString(file, log_options --- check/TestCallbacks.cpp | 10 +- src/io/HighsIO.cpp | 6 +- src/io/HighsIO.h | 7 +- src/lp_data/HighsModelUtils.cpp | 500 +++++++++++++++++++------------- src/lp_data/HighsModelUtils.h | 31 +- src/mip/HighsMipSolverData.cpp | 9 +- 6 files changed, 323 insertions(+), 240 deletions(-) diff --git a/check/TestCallbacks.cpp b/check/TestCallbacks.cpp index 06d9cc7e94..62e9dc105f 100644 --- a/check/TestCallbacks.cpp +++ b/check/TestCallbacks.cpp @@ -121,10 +121,10 @@ HighsCallbackFunctionType userInterruptCallback = REQUIRE(local_callback_data == kUserCallbackNoData); } if (callback_type == kCallbackLogging) { - if (dev_run) - printf("Callback: %s", message.c_str()); -// printf("userInterruptCallback(type %2d; data %2d): %s", -// callback_type, local_callback_data, message.c_str()); + if (dev_run) printf("Callback: %s", message.c_str()); + // printf("userInterruptCallback(type %2d; data %2d): %s", + // callback_type, local_callback_data, + // message.c_str()); } else if (callback_type == kCallbackSimplexInterrupt) { if (dev_run) printf( @@ -267,7 +267,7 @@ TEST_CASE("highs-callback-logging", "[highs-callback]") { } TEST_CASE("highs-callback-solution-basis-logging", "[highs-callback]") { - std::string filename = std::string(HIGHS_DIR) + "/check/instances/avgas.mps"; + std::string filename = std::string(HIGHS_DIR) + "/check/instances/avgas.mps"; int user_callback_data = kUserCallbackData; void* p_user_callback_data = reinterpret_cast(static_cast(user_callback_data)); diff --git a/src/io/HighsIO.cpp b/src/io/HighsIO.cpp index fc622a5c20..8d7911e02f 100644 --- a/src/io/HighsIO.cpp +++ b/src/io/HighsIO.cpp @@ -219,14 +219,14 @@ void highsLogDev(const HighsLogOptions& log_options_, const HighsLogType type, va_end(argptr); } -void highsFprintfString(FILE* file, const HighsLogOptions& log_options_, - const std::string& s) { +void highsFprintfString(FILE* file, const HighsLogOptions& log_options_, + const std::string& s) { if (file == nullptr) return; if (file == stdout) { highsLogUser(log_options_, HighsLogType::kInfo, "%s", s.c_str()); } else { fprintf(file, "%s", s.c_str()); - } + } } void highsReportDevInfo(const HighsLogOptions* log_options, diff --git a/src/io/HighsIO.h b/src/io/HighsIO.h index 6da477a5d5..b63d47141f 100644 --- a/src/io/HighsIO.h +++ b/src/io/HighsIO.h @@ -89,11 +89,12 @@ void highsLogDev(const HighsLogOptions& log_options_, const HighsLogType type, const char* format, ...); /** - * @brief Replaces fprintf(file,... so that when file=stdout highsLogUser is used + * @brief Replaces fprintf(file,... so that when file=stdout highsLogUser is + * used */ // Printing format: must contain exactly one "\n" at end of format -void highsFprintfString(FILE* file, const HighsLogOptions& log_options_, - const std::string& s); +void highsFprintfString(FILE* file, const HighsLogOptions& log_options_, + const std::string& s); /** * @brief For development logging when true log_options may not be available - diff --git a/src/lp_data/HighsModelUtils.cpp b/src/lp_data/HighsModelUtils.cpp index 201c090581..0e4db30085 100644 --- a/src/lp_data/HighsModelUtils.cpp +++ b/src/lp_data/HighsModelUtils.cpp @@ -133,13 +133,12 @@ std::string typeToString(const HighsVarType type) { } void writeModelBoundSolution( - FILE* file, const HighsLogOptions& log_options, - const bool columns, const HighsInt dim, - const std::vector& lower, const std::vector& upper, - const std::vector& names, const bool have_primal, - const std::vector& primal, const bool have_dual, - const std::vector& dual, const bool have_basis, - const std::vector& status, + FILE* file, const HighsLogOptions& log_options, const bool columns, + const HighsInt dim, const std::vector& lower, + const std::vector& upper, const std::vector& names, + const bool have_primal, const std::vector& primal, + const bool have_dual, const std::vector& dual, + const bool have_basis, const std::vector& status, const HighsVarType* integrality) { const bool have_names = names.size() > 0; if (have_names) assert((int)names.size() >= dim); @@ -161,10 +160,10 @@ void writeModelBoundSolution( highsFprintfString(file, log_options, ss.str()); for (HighsInt ix = 0; ix < dim; ix++) { ss.str(std::string()); - std::string var_status_string = have_basis ? - statusToString(status[ix], lower[ix], upper[ix]) : ""; + std::string var_status_string = + have_basis ? statusToString(status[ix], lower[ix], upper[ix]) : ""; ss << highsFormatToString("%9" HIGHSINT_FORMAT " %4s %12g %12g", ix, - var_status_string.c_str(), lower[ix], upper[ix]); + var_status_string.c_str(), lower[ix], upper[ix]); if (have_primal) { ss << highsFormatToString(" %12g", primal[ix]); } else { @@ -187,7 +186,7 @@ void writeModelBoundSolution( } void writeModelObjective(FILE* file, const HighsLogOptions& log_options, - const HighsModel& model, + const HighsModel& model, const std::vector& primal_solution) { HighsCDouble objective_value = model.lp_.objectiveCDoubleValue(primal_solution); @@ -196,14 +195,14 @@ void writeModelObjective(FILE* file, const HighsLogOptions& log_options, } void writeLpObjective(FILE* file, const HighsLogOptions& log_options, - const HighsLp& lp, + const HighsLp& lp, const std::vector& primal_solution) { HighsCDouble objective_value = lp.objectiveCDoubleValue(primal_solution); writeObjectiveValue(file, log_options, (double)objective_value); } void writeObjectiveValue(FILE* file, const HighsLogOptions& log_options, - const double objective_value) { + const double objective_value) { std::array objStr = highsDoubleToString( objective_value, kHighsSolutionValueToStringTolerance); std::string s = highsFormatToString("Objective %s\n", objStr.data()); @@ -211,7 +210,7 @@ void writeObjectiveValue(FILE* file, const HighsLogOptions& log_options, } void writePrimalSolution(FILE* file, const HighsLogOptions& log_options, - const HighsLp& lp, + const HighsLp& lp, const std::vector& primal_solution, const bool sparse) { HighsInt num_nonzero_primal_value = 0; @@ -224,11 +223,11 @@ void writePrimalSolution(FILE* file, const HighsLogOptions& log_options, // Indicate the number of column values to be written out, depending // on whether format is sparse: either lp.num_col_ if not sparse, or // the negation of the number of nonzero values, if sparse - + std::stringstream ss; ss.str(std::string()); ss << highsFormatToString("# Columns %" HIGHSINT_FORMAT "\n", - sparse ? -num_nonzero_primal_value : lp.num_col_); + sparse ? -num_nonzero_primal_value : lp.num_col_); highsFprintfString(file, log_options, ss.str()); for (HighsInt ix = 0; ix < lp.num_col_; ix++) { if (sparse && !primal_solution[ix]) continue; @@ -247,9 +246,8 @@ void writePrimalSolution(FILE* file, const HighsLogOptions& log_options, } void writeModelSolution(FILE* file, const HighsLogOptions& log_options, - const HighsModel& model, - const HighsSolution& solution, const HighsInfo& info, - const bool sparse) { + const HighsModel& model, const HighsSolution& solution, + const HighsInfo& info, const bool sparse) { const HighsLp& lp = model.lp_; const bool have_col_names = lp.col_names_.size() > 0; const bool have_row_names = lp.row_names_.size() > 0; @@ -279,10 +277,11 @@ void writeModelSolution(FILE* file, const HighsLogOptions& log_options, highsFprintfString(file, log_options, "Infeasible\n"); } writeModelObjective(file, log_options, model, solution.col_value); - writePrimalSolution(file, log_options, model.lp_, solution.col_value, sparse); + writePrimalSolution(file, log_options, model.lp_, solution.col_value, + sparse); if (sparse) return; ss.str(std::string()); - ss << highsFormatToString("# Rows %" HIGHSINT_FORMAT "\n", lp.num_row_); + ss << highsFormatToString("# Rows %" HIGHSINT_FORMAT "\n", lp.num_row_); highsFprintfString(file, log_options, ss.str()); for (HighsInt ix = 0; ix < lp.num_row_; ix++) { std::array valStr = highsDoubleToString( @@ -307,7 +306,7 @@ void writeModelSolution(FILE* file, const HighsLogOptions& log_options, highsFprintfString(file, log_options, "Infeasible\n"); } ss.str(std::string()); - ss << highsFormatToString("# Columns %" HIGHSINT_FORMAT "\n", lp.num_col_); + ss << highsFormatToString("# Columns %" HIGHSINT_FORMAT "\n", lp.num_col_); highsFprintfString(file, log_options, ss.str()); for (HighsInt ix = 0; ix < lp.num_col_; ix++) { std::array valStr = highsDoubleToString( @@ -320,7 +319,7 @@ void writeModelSolution(FILE* file, const HighsLogOptions& log_options, highsFprintfString(file, log_options, ss.str()); } ss.str(std::string()); - ss << highsFormatToString("# Rows %" HIGHSINT_FORMAT "\n", lp.num_row_); + ss << highsFormatToString("# Rows %" HIGHSINT_FORMAT "\n", lp.num_row_); highsFprintfString(file, log_options, ss.str()); for (HighsInt ix = 0; ix < lp.num_row_; ix++) { std::array valStr = highsDoubleToString( @@ -426,22 +425,22 @@ void writeSolutionFile(FILE* file, const HighsOptions& options, lp.col_upper_, lp.col_names_, have_primal, solution.col_value, have_dual, solution.col_dual, have_basis, basis.col_status, integrality); - writeModelBoundSolution(file, log_options, false, lp.num_row_, lp.row_lower_, - lp.row_upper_, lp.row_names_, have_primal, - solution.row_value, have_dual, solution.row_dual, - have_basis, basis.row_status); + writeModelBoundSolution(file, log_options, false, lp.num_row_, + lp.row_lower_, lp.row_upper_, lp.row_names_, + have_primal, solution.row_value, have_dual, + solution.row_dual, have_basis, basis.row_status); highsFprintfString(file, log_options, "\n"); std::stringstream ss; ss.str(std::string()); - ss << highsFormatToString("Model status: %s\n", - utilModelStatusToString(model_status).c_str()); + ss << highsFormatToString("Model status: %s\n", + utilModelStatusToString(model_status).c_str()); highsFprintfString(file, log_options, ss.str()); std::array objStr = highsDoubleToString((double)info.objective_function_value, kHighsSolutionValueToStringTolerance); highsFprintfString(file, log_options, "\n"); ss.str(std::string()); - ss << highsFormatToString("Objective value: %s\n", objStr.data()); + ss << highsFormatToString("Objective value: %s\n", objStr.data()); highsFprintfString(file, log_options, ss.str()); } else if (style == kSolutionStyleGlpsolRaw || style == kSolutionStyleGlpsolPretty) { @@ -455,36 +454,42 @@ void writeSolutionFile(FILE* file, const HighsOptions& options, highsFprintfString(file, log_options, "Model status\n"); std::stringstream ss; ss.str(std::string()); - ss << highsFormatToString("%s\n", utilModelStatusToString(model_status).c_str()); - highsFprintfString(file, log_options, ss.str()); + ss << highsFormatToString("%s\n", + utilModelStatusToString(model_status).c_str()); + highsFprintfString(file, log_options, ss.str()); writeModelSolution(file, log_options, model, solution, info, sparse); } } -void writeGlpsolCostRow(FILE* file, const bool raw, const bool is_mip, +void writeGlpsolCostRow(FILE* file, const HighsLogOptions& log_options, + const bool raw, const bool is_mip, const HighsInt row_id, const std::string objective_name, const double objective_function_value) { + std::stringstream ss; + ss.str(std::string()); if (raw) { double double_value = objective_function_value; std::array double_string = highsDoubleToString( double_value, kGlpsolSolutionValueToStringTolerance); // Last term of 0 for dual should (also) be blank when not MIP - fprintf(file, "i %d %s%s%s\n", (int)row_id, is_mip ? "" : "b ", - double_string.data(), is_mip ? "" : " 0"); + ss << highsFormatToString("i %d %s%s%s\n", (int)row_id, is_mip ? "" : "b ", + double_string.data(), is_mip ? "" : " 0"); } else { - fprintf(file, "%6d ", (int)row_id); + ss << highsFormatToString("%6d ", (int)row_id); if (objective_name.length() <= 12) { - fprintf(file, "%-12s ", objective_name.c_str()); + ss << highsFormatToString("%-12s ", objective_name.c_str()); } else { - fprintf(file, "%s\n%20s", objective_name.c_str(), ""); + ss << highsFormatToString("%s\n%20s", objective_name.c_str(), ""); } if (is_mip) { - fprintf(file, " "); + ss << highsFormatToString(" "); } else { - fprintf(file, "B "); + ss << highsFormatToString("B "); } - fprintf(file, "%13.6g %13s %13s \n", objective_function_value, "", ""); + ss << highsFormatToString("%13.6g %13s %13s \n", objective_function_value, + "", ""); } + highsFprintfString(file, log_options, ss.str()); } void writeGlpsolSolution(FILE* file, const HighsOptions& options, @@ -500,6 +505,7 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, const double kGlpsolLowQuality = 1e-3; const double kGlpsolPrintAsZero = 1e-9; const HighsLp& lp = model.lp_; + const HighsLogOptions& log_options = options.log_options; const bool have_col_names = (lp.col_names_.size() != 0); const bool have_row_names = (lp.row_names_.size() != 0); // Determine number of nonzeros including the objective function @@ -626,16 +632,24 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, // prefix to raw lines std::string line_prefix = ""; if (raw) line_prefix = "c "; - fprintf(file, "%s%-12s%s\n", line_prefix.c_str(), - "Problem:", lp.model_name_.c_str()); - fprintf(file, "%s%-12s%d\n", line_prefix.c_str(), - "Rows:", (int)glpsol_num_row); - fprintf(file, "%s%-12s%d", line_prefix.c_str(), "Columns:", (int)num_col); + highsFprintfString(file, log_options, + highsFormatToString("%s%-12s%s\n", line_prefix.c_str(), + "Problem:", lp.model_name_.c_str())); + highsFprintfString(file, log_options, + highsFormatToString("%s%-12s%d\n", line_prefix.c_str(), + "Rows:", (int)glpsol_num_row)); + std::stringstream ss; + ss.str(std::string()); + ss << highsFormatToString("%s%-12s%d", line_prefix.c_str(), + "Columns:", (int)num_col); if (!raw && is_mip) - fprintf(file, " (%d integer, %d binary)", (int)num_integer, - (int)num_binary); - fprintf(file, "\n"); - fprintf(file, "%s%-12s%d\n", line_prefix.c_str(), "Non-zeros:", (int)num_nz); + ss << highsFormatToString(" (%d integer, %d binary)", (int)num_integer, + (int)num_binary); + ss << highsFormatToString("\n"); + highsFprintfString(file, log_options, ss.str()); + highsFprintfString(file, log_options, + highsFormatToString("%s%-12s%d\n", line_prefix.c_str(), + "Non-zeros:", (int)num_nz)); // Use model_status to define the GLPK model_status_text and // solution_status_char, where the former is used to specify the // model status. GLPK uses a single character to specify the @@ -682,8 +696,9 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, } assert(model_status_text != "???"); if (is_mip) assert(solution_status_char != "?"); - fprintf(file, "%s%-12s%s\n", line_prefix.c_str(), - "Status:", model_status_text.c_str()); + highsFprintfString(file, log_options, + highsFormatToString("%s%-12s%s\n", line_prefix.c_str(), + "Status:", model_status_text.c_str())); // If info is not valid, then cannot write more if (!info.valid) return; // Now write out the numerical information @@ -697,92 +712,106 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, // non-trivial objective name if (have_row_names) assert(lp.objective_name_ != ""); const bool has_objective_name = lp.objective_name_ != ""; - fprintf(file, "%s%-12s%s%.10g (%s)\n", line_prefix.c_str(), "Objective:", + highsFprintfString( + file, log_options, + highsFormatToString( + "%s%-12s%s%.10g (%s)\n", line_prefix.c_str(), "Objective:", !(has_objective && has_objective_name) ? "" : (objective_name + " = ").c_str(), has_objective ? info.objective_function_value : 0, - lp.sense_ == ObjSense::kMinimize ? "MINimum" : "MAXimum"); + lp.sense_ == ObjSense::kMinimize ? "MINimum" : "MAXimum")); // No space after "c" on blank line! if (raw) line_prefix = "c"; - fprintf(file, "%s\n", line_prefix.c_str()); + highsFprintfString(file, log_options, + highsFormatToString("%s\n", line_prefix.c_str())); // Detailed lines are rather different if (raw) { - fprintf(file, "s %s %d %d ", is_mip ? "mip" : "bas", (int)glpsol_num_row, - (int)num_col); + ss.str(std::string()); + ss << highsFormatToString("s %s %d %d ", is_mip ? "mip" : "bas", + (int)glpsol_num_row, (int)num_col); if (is_mip) { - fprintf(file, "%s", solution_status_char.c_str()); + ss << highsFormatToString("%s", solution_status_char.c_str()); } else { if (info.primal_solution_status == kSolutionStatusNone) { - fprintf(file, "u"); + ss << highsFormatToString("u"); } else if (info.primal_solution_status == kSolutionStatusInfeasible) { - fprintf(file, "i"); + ss << highsFormatToString("i"); } else if (info.primal_solution_status == kSolutionStatusFeasible) { - fprintf(file, "f"); + ss << highsFormatToString("f"); } else { - fprintf(file, "?"); + ss << highsFormatToString("?"); } - fprintf(file, " "); + ss << highsFormatToString(" "); if (info.dual_solution_status == kSolutionStatusNone) { - fprintf(file, "u"); + ss << highsFormatToString("u"); } else if (info.dual_solution_status == kSolutionStatusInfeasible) { - fprintf(file, "i"); + ss << highsFormatToString("i"); } else if (info.dual_solution_status == kSolutionStatusFeasible) { - fprintf(file, "f"); + ss << highsFormatToString("f"); } else { - fprintf(file, "?"); + ss << highsFormatToString("?"); } } double double_value = has_objective ? info.objective_function_value : 0; std::array double_string = highsDoubleToString(double_value, kHighsSolutionValueToStringTolerance); - fprintf(file, " %s\n", double_string.data()); + ss << highsFormatToString(" %s\n", double_string.data()); + highsFprintfString(file, log_options, ss.str()); } // GLPK puts out i 1 b 0 0 etc if there's no primal point, but // that's meaningless at best, so HiGHS returns in that case if (!have_value) return; if (!raw) { - fprintf(file, - " No. Row name %s Activity Lower bound " - " Upper bound", - have_basis ? "St" : " "); - if (have_dual) fprintf(file, " Marginal"); - fprintf(file, "\n"); - - fprintf(file, - "------ ------------ %s ------------- ------------- " - "-------------", - have_basis ? "--" : " "); - if (have_dual) fprintf(file, " -------------"); - fprintf(file, "\n"); + ss.str(std::string()); + ss << highsFormatToString( + " No. Row name %s Activity Lower bound " + " Upper bound", + have_basis ? "St" : " "); + if (have_dual) ss << highsFormatToString(" Marginal"); + ss << highsFormatToString("\n"); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); + ss << highsFormatToString( + "------ ------------ %s ------------- ------------- " + "-------------", + have_basis ? "--" : " "); + if (have_dual) ss << highsFormatToString(" -------------"); + ss << highsFormatToString("\n"); + highsFprintfString(file, log_options, ss.str()); } HighsInt row_id = 0; for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { row_id++; if (row_id == cost_row_location) { - writeGlpsolCostRow(file, raw, is_mip, row_id, objective_name, + writeGlpsolCostRow(file, log_options, raw, is_mip, row_id, objective_name, info.objective_function_value); row_id++; } + ss.str(std::string()); if (raw) { - fprintf(file, "i %d ", (int)row_id); + ss << highsFormatToString("i %d ", (int)row_id); if (is_mip) { // Complete the line if for a MIP double double_value = have_value ? solution.row_value[iRow] : 0; std::array double_string = highsDoubleToString( double_value, kHighsSolutionValueToStringTolerance); - fprintf(file, "%s\n", double_string.data()); + ss << highsFormatToString("%s\n", double_string.data()); + highsFprintfString(file, log_options, ss.str()); continue; } } else { - fprintf(file, "%6d ", (int)row_id); + ss << highsFormatToString("%6d ", (int)row_id); std::string row_name = ""; if (have_row_names) row_name = lp.row_names_[iRow]; if (row_name.length() <= 12) { - fprintf(file, "%-12s ", row_name.c_str()); + ss << highsFormatToString("%-12s ", row_name.c_str()); } else { - fprintf(file, "%s\n%20s", row_name.c_str(), ""); + ss << highsFormatToString("%s\n", row_name.c_str()); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); + ss << " "; } } const double lower = lp.row_lower_[iRow]; @@ -816,29 +845,30 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, } } if (raw) { - fprintf(file, "%s ", status_char.c_str()); + ss << highsFormatToString("%s ", status_char.c_str()); double double_value = have_value ? solution.row_value[iRow] : 0; std::array double_string = highsDoubleToString( double_value, kHighsSolutionValueToStringTolerance); - fprintf(file, "%s ", double_string.data()); + ss << highsFormatToString("%s ", double_string.data()); } else { - fprintf(file, "%s ", status_text.c_str()); - fprintf(file, "%13.6g ", fabs(value) <= kGlpsolPrintAsZero ? 0.0 : value); + ss << highsFormatToString("%s ", status_text.c_str()); + ss << highsFormatToString( + "%13.6g ", fabs(value) <= kGlpsolPrintAsZero ? 0.0 : value); if (lower > -kHighsInf) - fprintf(file, "%13.6g ", lower); + ss << highsFormatToString("%13.6g ", lower); else - fprintf(file, "%13s ", ""); + ss << highsFormatToString("%13s ", ""); if (lower != upper && upper < kHighsInf) - fprintf(file, "%13.6g ", upper); + ss << highsFormatToString("%13.6g ", upper); else - fprintf(file, "%13s ", lower == upper ? "=" : ""); + ss << highsFormatToString("%13s ", lower == upper ? "=" : ""); } if (have_dual) { if (raw) { double double_value = solution.row_dual[iRow]; std::array double_string = highsDoubleToString( double_value, kHighsSolutionValueToStringTolerance); - fprintf(file, "%s", double_string.data()); + ss << highsFormatToString("%s", double_string.data()); } else { // If the row is known to be basic, don't print the dual // value. If there's no basis, row cannot be known to be basic @@ -847,56 +877,67 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, not_basic = basis.row_status[iRow] != HighsBasisStatus::kBasic; if (not_basic) { if (fabs(dual) <= kGlpsolPrintAsZero) - fprintf(file, "%13s", "< eps"); + ss << highsFormatToString("%13s", "< eps"); else - fprintf(file, "%13.6g ", dual); + ss << highsFormatToString("%13.6g ", dual); } } } - fprintf(file, "\n"); + ss << highsFormatToString("\n"); + highsFprintfString(file, log_options, ss.str()); } if (cost_row_location == lp.num_row_ + 1) { row_id++; - writeGlpsolCostRow(file, raw, is_mip, row_id, objective_name, + writeGlpsolCostRow(file, log_options, raw, is_mip, row_id, objective_name, info.objective_function_value); } - if (!raw) fprintf(file, "\n"); + if (!raw) highsFprintfString(file, log_options, "\n"); if (!raw) { - fprintf(file, - " No. Column name %s Activity Lower bound " - " Upper bound", - have_basis ? "St" : " "); - if (have_dual) fprintf(file, " Marginal"); - fprintf(file, "\n"); - fprintf(file, - "------ ------------ %s ------------- ------------- " - "-------------", - have_basis ? "--" : " "); - if (have_dual) fprintf(file, " -------------"); - fprintf(file, "\n"); + ss.str(std::string()); + ss << highsFormatToString( + " No. Column name %s Activity Lower bound " + " Upper bound", + have_basis ? "St" : " "); + if (have_dual) ss << highsFormatToString(" Marginal"); + ss << highsFormatToString("\n"); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); + ss << highsFormatToString( + "------ ------------ %s ------------- ------------- " + "-------------", + have_basis ? "--" : " "); + if (have_dual) ss << highsFormatToString(" -------------"); + ss << highsFormatToString("\n"); + highsFprintfString(file, log_options, ss.str()); } if (raw) line_prefix = "j "; for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + ss.str(std::string()); if (raw) { - fprintf(file, "%s%d ", line_prefix.c_str(), (int)(iCol + 1)); + ss << highsFormatToString("%s%d ", line_prefix.c_str(), (int)(iCol + 1)); if (is_mip) { double double_value = have_value ? solution.col_value[iCol] : 0; std::array double_string = highsDoubleToString( double_value, kHighsSolutionValueToStringTolerance); - fprintf(file, "%s\n", double_string.data()); + ss << highsFormatToString("%s\n", double_string.data()); + highsFprintfString(file, log_options, ss.str()); continue; } } else { - fprintf(file, "%6d ", (int)(iCol + 1)); + ss << highsFormatToString("%6d ", (int)(iCol + 1)); std::string col_name = ""; if (have_col_names) col_name = lp.col_names_[iCol]; if (!have_col_names || col_name.length() <= 12) { - fprintf(file, "%-12s ", !have_col_names ? "" : col_name.c_str()); + ss << highsFormatToString("%-12s ", + !have_col_names ? "" : col_name.c_str()); } else { - fprintf(file, "%s\n%20s", col_name.c_str(), ""); + ss << highsFormatToString("%s\n", col_name.c_str()); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); + ss << " "; } } const double lower = lp.col_lower_[iCol]; @@ -933,29 +974,30 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, status_text = "* "; } if (raw) { - fprintf(file, "%s ", status_char.c_str()); + ss << highsFormatToString("%s ", status_char.c_str()); double double_value = have_value ? solution.col_value[iCol] : 0; std::array double_string = highsDoubleToString( double_value, kHighsSolutionValueToStringTolerance); - fprintf(file, "%s ", double_string.data()); + ss << highsFormatToString("%s ", double_string.data()); } else { - fprintf(file, "%s ", status_text.c_str()); - fprintf(file, "%13.6g ", fabs(value) <= kGlpsolPrintAsZero ? 0.0 : value); + ss << highsFormatToString("%s ", status_text.c_str()); + ss << highsFormatToString( + "%13.6g ", fabs(value) <= kGlpsolPrintAsZero ? 0.0 : value); if (lower > -kHighsInf) - fprintf(file, "%13.6g ", lower); + ss << highsFormatToString("%13.6g ", lower); else - fprintf(file, "%13s ", ""); + ss << highsFormatToString("%13s ", ""); if (lower != upper && upper < kHighsInf) - fprintf(file, "%13.6g ", upper); + ss << highsFormatToString("%13.6g ", upper); else - fprintf(file, "%13s ", lower == upper ? "=" : ""); + ss << highsFormatToString("%13s ", lower == upper ? "=" : ""); } if (have_dual) { if (raw) { double double_value = solution.col_dual[iCol]; std::array double_string = highsDoubleToString( double_value, kHighsSolutionValueToStringTolerance); - fprintf(file, "%s", double_string.data()); + ss << highsFormatToString("%s", double_string.data()); } else { // If the column is known to be basic, don't print the dual // value. If there's no basis, column cannot be known to be @@ -965,16 +1007,17 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, not_basic = basis.col_status[iCol] != HighsBasisStatus::kBasic; if (not_basic) { if (fabs(dual) <= kGlpsolPrintAsZero) - fprintf(file, "%13s", "< eps"); + ss << highsFormatToString("%13s", "< eps"); else - fprintf(file, "%13.6g ", dual); + ss << highsFormatToString("%13.6g ", dual); } } } - fprintf(file, "\n"); + ss << highsFormatToString("\n"); + highsFprintfString(file, log_options, ss.str()); } if (raw) { - fprintf(file, "e o f\n"); + highsFprintfString(file, log_options, "e o f\n"); return; } HighsPrimalDualErrors errors; @@ -983,13 +1026,13 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, double absolute_error_value; HighsInt relative_error_index; double relative_error_value; - const HighsLogOptions& log_options = options.log_options; getKktFailures(options, model, solution, basis, local_info, errors, true); highsFprintfString(file, log_options, "\n"); if (is_mip) { highsFprintfString(file, log_options, "Integer feasibility conditions:\n"); } else { - highsFprintfString(file, log_options, "Karush-Kuhn-Tucker optimality conditions:\n"); + highsFprintfString(file, log_options, + "Karush-Kuhn-Tucker optimality conditions:\n"); } highsFprintfString(file, log_options, "\n"); // Primal residual @@ -999,17 +1042,25 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, relative_error_index = errors.max_primal_residual.relative_index + 1; if (!absolute_error_value) absolute_error_index = 0; if (!relative_error_value) relative_error_index = 0; - fprintf(file, "KKT.PE: max.abs.err = %.2e on row %d\n", absolute_error_value, - absolute_error_index == 0 ? 0 : (int)absolute_error_index); - fprintf(file, " max.rel.err = %.2e on row %d\n", relative_error_value, - absolute_error_index == 0 ? 0 : (int)relative_error_index); - fprintf(file, "%8s%s\n", "", - relative_error_value <= kGlpsolHighQuality ? "High quality" - : relative_error_value <= kGlpsolMediumQuality ? "Medium quality" - : relative_error_value <= kGlpsolLowQuality - ? "Low quality" - : "PRIMAL SOLUTION IS WRONG"); - highsFprintfString(file, log_options, "\n"); + ss.str(std::string()); + ss << highsFormatToString( + "KKT.PE: max.abs.err = %.2e on row %d\n", absolute_error_value, + absolute_error_index == 0 ? 0 : (int)absolute_error_index); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); + ss << highsFormatToString( + " max.rel.err = %.2e on row %d\n", relative_error_value, + absolute_error_index == 0 ? 0 : (int)relative_error_index); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); + ss << highsFormatToString( + "%8s%s\n", "", + relative_error_value <= kGlpsolHighQuality ? "High quality" + : relative_error_value <= kGlpsolMediumQuality ? "Medium quality" + : relative_error_value <= kGlpsolLowQuality ? "Low quality" + : "PRIMAL SOLUTION IS WRONG"); + ss << "\n"; + highsFprintfString(file, log_options, ss.str()); // Primal infeasibility absolute_error_value = errors.max_primal_infeasibility.absolute_value; @@ -1019,24 +1070,31 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, if (!absolute_error_value) absolute_error_index = 0; if (!relative_error_value) relative_error_index = 0; bool on_col = absolute_error_index > 0 && absolute_error_index <= lp.num_col_; - fprintf(file, "KKT.PB: max.abs.err = %.2e on %s %d\n", absolute_error_value, - on_col ? "column" : "row", - absolute_error_index <= lp.num_col_ - ? (int)absolute_error_index - : (int)(absolute_error_index - lp.num_col_)); + ss.str(std::string()); + ss << highsFormatToString("KKT.PB: max.abs.err = %.2e on %s %d\n", + absolute_error_value, on_col ? "column" : "row", + absolute_error_index <= lp.num_col_ + ? (int)absolute_error_index + : (int)(absolute_error_index - lp.num_col_)); + highsFprintfString(file, log_options, ss.str()); on_col = relative_error_index > 0 && relative_error_index <= lp.num_col_; - fprintf(file, " max.rel.err = %.2e on %s %d\n", relative_error_value, - on_col ? "column" : "row", - relative_error_index <= lp.num_col_ - ? (int)relative_error_index - : (int)(relative_error_index - lp.num_col_)); - fprintf(file, "%8s%s\n", "", - relative_error_value <= kGlpsolHighQuality ? "High quality" - : relative_error_value <= kGlpsolMediumQuality ? "Medium quality" - : relative_error_value <= kGlpsolLowQuality - ? "Low quality" - : "PRIMAL SOLUTION IS INFEASIBLE"); - highsFprintfString(file, log_options, "\n"); + ss.str(std::string()); + ss << highsFormatToString(" max.rel.err = %.2e on %s %d\n", + relative_error_value, on_col ? "column" : "row", + relative_error_index <= lp.num_col_ + ? (int)relative_error_index + : (int)(relative_error_index - lp.num_col_)); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); + ss << highsFormatToString( + "%8s%s\n", "", + relative_error_value <= kGlpsolHighQuality ? "High quality" + : relative_error_value <= kGlpsolMediumQuality ? "Medium quality" + : relative_error_value <= kGlpsolLowQuality + ? "Low quality" + : "PRIMAL SOLUTION IS INFEASIBLE"); + ss << "\n"; + highsFprintfString(file, log_options, ss.str()); if (have_dual) { // Dual residual @@ -1046,17 +1104,19 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, relative_error_index = errors.max_dual_residual.relative_index + 1; if (!absolute_error_value) absolute_error_index = 0; if (!relative_error_value) relative_error_index = 0; - fprintf(file, "KKT.DE: max.abs.err = %.2e on column %d\n", - absolute_error_value, (int)absolute_error_index); - fprintf(file, " max.rel.err = %.2e on column %d\n", - relative_error_value, (int)relative_error_index); - fprintf(file, "%8s%s\n", "", - relative_error_value <= kGlpsolHighQuality ? "High quality" - : relative_error_value <= kGlpsolMediumQuality ? "Medium quality" - : relative_error_value <= kGlpsolLowQuality - ? "Low quality" - : "DUAL SOLUTION IS WRONG"); - highsFprintfString(file, log_options, "\n"); + ss.str(std::string()); + ss << highsFormatToString("KKT.DE: max.abs.err = %.2e on column %d\n", + absolute_error_value, (int)absolute_error_index); + ss << highsFormatToString(" max.rel.err = %.2e on column %d\n", + relative_error_value, (int)relative_error_index); + ss << highsFormatToString( + "%8s%s\n", "", + relative_error_value <= kGlpsolHighQuality ? "High quality" + : relative_error_value <= kGlpsolMediumQuality ? "Medium quality" + : relative_error_value <= kGlpsolLowQuality ? "Low quality" + : "DUAL SOLUTION IS WRONG"); + ss << "\n"; + highsFprintfString(file, log_options, ss.str()); // Dual infeasibility absolute_error_value = errors.max_dual_infeasibility.absolute_value; @@ -1067,30 +1127,37 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, if (!relative_error_value) relative_error_index = 0; bool on_col = absolute_error_index > 0 && absolute_error_index <= lp.num_col_; - fprintf(file, "KKT.DB: max.abs.err = %.2e on %s %d\n", absolute_error_value, - on_col ? "column" : "row", - absolute_error_index <= lp.num_col_ - ? (int)absolute_error_index - : (int)(absolute_error_index - lp.num_col_)); + ss.str(std::string()); + ss << highsFormatToString("KKT.DB: max.abs.err = %.2e on %s %d\n", + absolute_error_value, on_col ? "column" : "row", + absolute_error_index <= lp.num_col_ + ? (int)absolute_error_index + : (int)(absolute_error_index - lp.num_col_)); + highsFprintfString(file, log_options, ss.str()); on_col = relative_error_index > 0 && relative_error_index <= lp.num_col_; - fprintf(file, " max.rel.err = %.2e on %s %d\n", relative_error_value, - on_col ? "column" : "row", - relative_error_index <= lp.num_col_ - ? (int)relative_error_index - : (int)(relative_error_index - lp.num_col_)); - fprintf(file, "%8s%s\n", "", - relative_error_value <= kGlpsolHighQuality ? "High quality" - : relative_error_value <= kGlpsolMediumQuality ? "Medium quality" - : relative_error_value <= kGlpsolLowQuality - ? "Low quality" - : "DUAL SOLUTION IS INFEASIBLE"); - highsFprintfString(file, log_options, "\n"); + ss.str(std::string()); + ss << highsFormatToString(" max.rel.err = %.2e on %s %d\n", + relative_error_value, on_col ? "column" : "row", + relative_error_index <= lp.num_col_ + ? (int)relative_error_index + : (int)(relative_error_index - lp.num_col_)); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); + ss << highsFormatToString( + "%8s%s\n", "", + relative_error_value <= kGlpsolHighQuality ? "High quality" + : relative_error_value <= kGlpsolMediumQuality ? "Medium quality" + : relative_error_value <= kGlpsolLowQuality + ? "Low quality" + : "DUAL SOLUTION IS INFEASIBLE"); + ss << "\n"; + highsFprintfString(file, log_options, ss.str()); } highsFprintfString(file, log_options, "End of output\n"); } void writeOldRawSolution(FILE* file, const HighsLogOptions& log_options, - const HighsLp& lp, const HighsBasis& basis, + const HighsLp& lp, const HighsBasis& basis, const HighsSolution& solution) { const bool have_value = solution.value_valid; const bool have_dual = solution.dual_valid; @@ -1114,44 +1181,59 @@ void writeOldRawSolution(FILE* file, const HighsLogOptions& log_options, use_row_status = basis.row_status; } if (!have_value && !have_dual && !have_basis) return; - fprintf(file, + highsFprintfString( + file, log_options, + highsFormatToString( "%" HIGHSINT_FORMAT " %" HIGHSINT_FORMAT " : Number of columns and rows for primal or dual solution " "or basis\n", - lp.num_col_, lp.num_row_); + lp.num_col_, lp.num_row_)); + std::stringstream ss; + ss.str(std::string()); if (have_value) { - fprintf(file, "T"); + ss << highsFormatToString("T"); } else { - fprintf(file, "F"); + ss << highsFormatToString("F"); } - fprintf(file, " Primal solution\n"); + ss << highsFormatToString(" Primal solution\n"); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); if (have_dual) { - fprintf(file, "T"); + ss << highsFormatToString("T"); } else { - fprintf(file, "F"); + ss << highsFormatToString("F"); } - fprintf(file, " Dual solution\n"); + ss << highsFormatToString(" Dual solution\n"); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); if (have_basis) { - fprintf(file, "T"); + ss << highsFormatToString("T"); } else { - fprintf(file, "F"); + ss << highsFormatToString("F"); } - fprintf(file, " Basis\n"); + ss << highsFormatToString(" Basis\n"); + highsFprintfString(file, log_options, ss.str()); highsFprintfString(file, log_options, "Columns\n"); for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { - if (have_value) fprintf(file, "%.15g ", use_col_value[iCol]); - if (have_dual) fprintf(file, "%.15g ", use_col_dual[iCol]); + ss.str(std::string()); + if (have_value) ss << highsFormatToString("%.15g ", use_col_value[iCol]); + if (have_dual) ss << highsFormatToString("%.15g ", use_col_dual[iCol]); if (have_basis) - fprintf(file, "%" HIGHSINT_FORMAT "", (HighsInt)use_col_status[iCol]); - fprintf(file, "\n"); + ss << highsFormatToString("%" HIGHSINT_FORMAT "", + (HighsInt)use_col_status[iCol]); + ss << highsFormatToString("\n"); + highsFprintfString(file, log_options, ss.str()); } highsFprintfString(file, log_options, "Rows\n"); for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { - if (have_value) fprintf(file, "%.15g ", use_row_value[iRow]); - if (have_dual) fprintf(file, "%.15g ", use_row_dual[iRow]); + ss.str(std::string()); + if (have_value) ss << highsFormatToString("%.15g ", use_row_value[iRow]); + if (have_dual) ss << highsFormatToString("%.15g ", use_row_dual[iRow]); if (have_basis) - fprintf(file, "%" HIGHSINT_FORMAT "", (HighsInt)use_row_status[iRow]); - fprintf(file, "\n"); + ss << highsFormatToString("%" HIGHSINT_FORMAT "", + (HighsInt)use_row_status[iRow]); + ss << highsFormatToString("\n"); + highsFprintfString(file, log_options, ss.str()); } } diff --git a/src/lp_data/HighsModelUtils.h b/src/lp_data/HighsModelUtils.h index ac369ab5cf..110e668f5a 100644 --- a/src/lp_data/HighsModelUtils.h +++ b/src/lp_data/HighsModelUtils.h @@ -31,35 +31,33 @@ bool hasNamesWithSpaces(const HighsLogOptions& log_options, const HighsInt num_name, const std::vector& names); void writeModelBoundSolution( - FILE* file, const HighsLogOptions& log_options, - const bool columns, const HighsInt dim, - const std::vector& lower, const std::vector& upper, - const std::vector& names, const bool have_primal, - const std::vector& primal, const bool have_dual, - const std::vector& dual, const bool have_basis, - const std::vector& status, + FILE* file, const HighsLogOptions& log_options, const bool columns, + const HighsInt dim, const std::vector& lower, + const std::vector& upper, const std::vector& names, + const bool have_primal, const std::vector& primal, + const bool have_dual, const std::vector& dual, + const bool have_basis, const std::vector& status, const HighsVarType* integrality = NULL); void writeModelObjective(FILE* file, const HighsLogOptions& log_options, - const HighsModel& model, + const HighsModel& model, const std::vector& primal_solution); void writeLpObjective(FILE* file, const HighsLogOptions& log_options, - const HighsLp& lp, + const HighsLp& lp, const std::vector& primal_solution); void writeObjectiveValue(FILE* file, const HighsLogOptions& log_options, - const double objective_value); + const double objective_value); void writePrimalSolution(FILE* file, const HighsLogOptions& log_options, - const HighsLp& lp, + const HighsLp& lp, const std::vector& primal_solution, const bool sparse = false); void writeModelSolution(FILE* file, const HighsLogOptions& log_options, - const HighsModel& model, - const HighsSolution& solution, const HighsInfo& info, - const bool sparse = false); + const HighsModel& model, const HighsSolution& solution, + const HighsInfo& info, const bool sparse = false); HighsInt maxNameLength(const HighsInt num_name, const std::vector& names); @@ -74,7 +72,8 @@ void writeSolutionFile(FILE* file, const HighsOptions& options, const HighsModelStatus model_status, const HighsInt style); -void writeGlpsolCostRow(FILE* file, const bool raw, const bool is_mip, +void writeGlpsolCostRow(FILE* file, const HighsLogOptions& log_options, + const bool raw, const bool is_mip, const HighsInt row_id, const std::string objective_name, const double objective_function_value); @@ -85,7 +84,7 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, const HighsInfo& info, const bool raw); void writeOldRawSolution(FILE* file, const HighsLogOptions& log_options, - const HighsLp& lp, const HighsBasis& basis, + const HighsLp& lp, const HighsBasis& basis, const HighsSolution& solution); HighsBasisStatus checkedVarHighsNonbasicStatus( diff --git a/src/mip/HighsMipSolverData.cpp b/src/mip/HighsMipSolverData.cpp index eafb952cf7..d1ec3b890a 100644 --- a/src/mip/HighsMipSolverData.cpp +++ b/src/mip/HighsMipSolverData.cpp @@ -1942,10 +1942,11 @@ void HighsMipSolverData::saveReportMipSolution(const double new_upper_limit) { FILE* file = mipsolver.improving_solution_file_; if (file) { writeLpObjective(file, mipsolver.options_mip_->log_options, - *(mipsolver.orig_model_), mipsolver.solution_); - writePrimalSolution(file, mipsolver.options_mip_->log_options, - *(mipsolver.orig_model_), mipsolver.solution_, - mipsolver.options_mip_->mip_improving_solution_report_sparse); + *(mipsolver.orig_model_), mipsolver.solution_); + writePrimalSolution( + file, mipsolver.options_mip_->log_options, *(mipsolver.orig_model_), + mipsolver.solution_, + mipsolver.options_mip_->mip_improving_solution_report_sparse); } } From abcce09c4c3cc5d393c3f24202d86b14163ee897 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 10 Jul 2024 16:14:31 +0100 Subject: [PATCH 038/194] Identified that HighsMipSolverData::checkLimits is not called for a long time before new call before mipdata_->evaluateRootNode(); --- src/mip/HighsMipSolver.cpp | 7 ++++++- src/mip/HighsMipSolverData.cpp | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/mip/HighsMipSolver.cpp b/src/mip/HighsMipSolver.cpp index 2108729c3e..28811b0324 100644 --- a/src/mip/HighsMipSolver.cpp +++ b/src/mip/HighsMipSolver.cpp @@ -137,6 +137,11 @@ void HighsMipSolver::run() { mipdata_->runSetup(); restart: if (modelstatus_ == HighsModelStatus::kNotset) { + // Check limits have not been reached before evaluating root node + if (mipdata_->checkLimits()) { + cleanupSolve(); + return; + } mipdata_->evaluateRootNode(); // age 5 times to remove stored but never violated cuts after root // separation @@ -146,7 +151,7 @@ void HighsMipSolver::run() { mipdata_->cutpool.performAging(); mipdata_->cutpool.performAging(); } - if (mipdata_->nodequeue.empty()) { + if (mipdata_->nodequeue.empty() || mipdata_->checkLimits()) { cleanupSolve(); return; } diff --git a/src/mip/HighsMipSolverData.cpp b/src/mip/HighsMipSolverData.cpp index d1ec3b890a..9dcd8129b6 100644 --- a/src/mip/HighsMipSolverData.cpp +++ b/src/mip/HighsMipSolverData.cpp @@ -1870,6 +1870,8 @@ bool HighsMipSolverData::checkLimits(int64_t nodeOffset) const { return true; } + // const double time = mipsolver.timer_.read(mipsolver.timer_.solve_clock); + // printf("checkLimits: time = %g\n", time); if (mipsolver.timer_.read(mipsolver.timer_.solve_clock) >= options.time_limit) { if (mipsolver.modelstatus_ == HighsModelStatus::kNotset) { From 6096006369d5a08318a5db7df03722c87e346262 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 10 Jul 2024 16:24:04 +0100 Subject: [PATCH 039/194] Added some time reporting to HighsMipSolver::run() when not submip --- src/mip/HighsMipSolver.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/mip/HighsMipSolver.cpp b/src/mip/HighsMipSolver.cpp index 28811b0324..4724f1a33b 100644 --- a/src/mip/HighsMipSolver.cpp +++ b/src/mip/HighsMipSolver.cpp @@ -115,6 +115,10 @@ void HighsMipSolver::run() { mipdata_ = decltype(mipdata_)(new HighsMipSolverData(*this)); mipdata_->init(); mipdata_->runPresolve(options_mip_->presolve_reduction_limit); + if (!submip) + highsLogUser(options_mip_->log_options, HighsLogType::kInfo, + "MIP-Timing: After %6.4fs - completed mipdata_->runPresolve\n", + timer_.read(timer_.solve_clock)); // Identify whether time limit has been reached (in presolve) if (modelstatus_ == HighsModelStatus::kNotset && timer_.read(timer_.solve_clock) >= options_mip_->time_limit) @@ -134,7 +138,15 @@ void HighsMipSolver::run() { return; } + if (!submip) + highsLogUser(options_mip_->log_options, HighsLogType::kInfo, + "MIP-Timing: After %6.4fs - reached mipdata_->runSetup()\n", + timer_.read(timer_.solve_clock)); mipdata_->runSetup(); + if (!submip) + highsLogUser(options_mip_->log_options, HighsLogType::kInfo, + "MIP-Timing: After %6.4fs - completed mipdata_->runSetup()\n", + timer_.read(timer_.solve_clock)); restart: if (modelstatus_ == HighsModelStatus::kNotset) { // Check limits have not been reached before evaluating root node @@ -142,7 +154,17 @@ void HighsMipSolver::run() { cleanupSolve(); return; } + if (!submip) + highsLogUser( + options_mip_->log_options, HighsLogType::kInfo, + "MIP-Timing: After %6.4fs - reached mipdata_->evaluateRootNode()\n", + timer_.read(timer_.solve_clock)); mipdata_->evaluateRootNode(); + if (!submip) + highsLogUser( + options_mip_->log_options, HighsLogType::kInfo, + "MIP-Timing: After %6.4fs - completed mipdata_->evaluateRootNode()\n", + timer_.read(timer_.solve_clock)); // age 5 times to remove stored but never violated cuts after root // separation mipdata_->cutpool.performAging(); From 8d269a21d614422923ba64fa91f7799a7bec5148 Mon Sep 17 00:00:00 2001 From: jajhall Date: Thu, 11 Jul 2024 16:51:05 +0100 Subject: [PATCH 040/194] Added setSolution for sparse primal solution and test to check-set-mip-solution --- check/TestCheckSolution.cpp | 42 +++++++++++++++++++++++++++++++++++-- src/Highs.h | 7 +++++++ src/lp_data/Highs.cpp | 8 +++++++ 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/check/TestCheckSolution.cpp b/check/TestCheckSolution.cpp index c7ca67da2b..3ef82eb0bc 100644 --- a/check/TestCheckSolution.cpp +++ b/check/TestCheckSolution.cpp @@ -5,7 +5,7 @@ #include "SpecialLps.h" #include "catch.hpp" -const bool dev_run = false; +const bool dev_run = true; void runWriteReadCheckSolution(Highs& highs, const std::string model, const HighsModelStatus require_model_status, @@ -89,7 +89,7 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") { highs.clear(); - const bool other_tests = true; + const bool other_tests = false;//true; const bool test0 = other_tests; bool valid, integral, feasible; if (test0) { @@ -229,6 +229,44 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") { highs.clear(); } + const bool test6 = true;//other_tests; + if (test6) { + if (dev_run) + printf( + "\n***************************\nSolving from sparse integer " + "solution\n"); + HighsInt num_integer_variable = 0; + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + if (lp.integrality_[iCol] == HighsVarType::kInteger) num_integer_variable++; + + + highs.setOptionValue("output_flag", dev_run); + highs.readModel(model_file); + + std::vector is_set; + is_set.assign(lp.num_col_, false); + std::vector index; + std::vector value; + HighsInt num_to_set = std::max(10, (8*num_integer_variable)/10); + assert(num_to_set>0); + HighsRandom random; + for (HighsInt iSet = 0; iSet < num_to_set;) { + HighsInt iCol = random.integer(lp.num_col_); + if (lp.integrality_[iCol] != HighsVarType::kInteger) continue; + if (is_set[iCol]) continue; + index.push_back(iCol); + value.push_back(optimal_solution.col_value[iCol]); + iSet++; + } + HighsInt num_entries = index.size(); + assert(num_entries == num_to_set); + return_status = highs.setSolution(num_entries, index.data(), value.data()); + REQUIRE(return_status == HighsStatus::kOk); + highs.run(); + REQUIRE(info.mip_node_count < scratch_num_nodes); + highs.clear(); + } + assert(other_tests); std::remove(solution_file.c_str()); } diff --git a/src/Highs.h b/src/Highs.h index 7b3c08ca26..3a1c637642 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -1063,6 +1063,13 @@ class Highs { */ HighsStatus setSolution(const HighsSolution& solution); + /** + * @brief Pass a sparse primal solution + */ + HighsStatus setSolution(const HighsInt num_entries, + const HighsInt* index, + const double* value); + /** * @brief Set the callback method to use for HiGHS */ diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index fa600bba14..691af85ade 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -1965,6 +1965,14 @@ HighsStatus Highs::setSolution(const HighsSolution& solution) { return returnFromHighs(return_status); } +HighsStatus Highs::setSolution(const HighsInt num_entries, + const HighsInt* index, + const double* value) { + HighsStatus return_status = HighsStatus::kOk; + return_status = HighsStatus::kError; + return returnFromHighs(return_status); +} + HighsStatus Highs::setCallback(HighsCallbackFunctionType user_callback, void* user_callback_data) { this->callback_.clear(); From 7fd28a11519ea40cea74115ad409a82198189bf3 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sun, 14 Jul 2024 17:29:08 +0100 Subject: [PATCH 041/194] Now clearing solution when completeSolutionFromDiscreteAssignment decides it's not worth it --- check/TestCallbacks.cpp | 2 +- check/TestCheckSolution.cpp | 33 ++++-- src/Highs.h | 5 +- src/lp_data/HConst.h | 1 + src/lp_data/Highs.cpp | 197 +++++++++++++++++++++++++++--------- 5 files changed, 178 insertions(+), 60 deletions(-) diff --git a/check/TestCallbacks.cpp b/check/TestCallbacks.cpp index 62e9dc105f..437fdaf7b3 100644 --- a/check/TestCallbacks.cpp +++ b/check/TestCallbacks.cpp @@ -6,7 +6,7 @@ #include "catch.hpp" #include "lp_data/HighsCallback.h" -const bool dev_run = true; +const bool dev_run = false; const double egout_optimal_objective = 568.1007; const double egout_objective_target = 610; diff --git a/check/TestCheckSolution.cpp b/check/TestCheckSolution.cpp index 3ef82eb0bc..d0f2309b1c 100644 --- a/check/TestCheckSolution.cpp +++ b/check/TestCheckSolution.cpp @@ -5,7 +5,7 @@ #include "SpecialLps.h" #include "catch.hpp" -const bool dev_run = true; +const bool dev_run = false; void runWriteReadCheckSolution(Highs& highs, const std::string model, const HighsModelStatus require_model_status, @@ -89,7 +89,7 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") { highs.clear(); - const bool other_tests = false;//true; + const bool other_tests = true; const bool test0 = other_tests; bool valid, integral, feasible; if (test0) { @@ -229,7 +229,7 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") { highs.clear(); } - const bool test6 = true;//other_tests; + const bool test6 = other_tests; if (test6) { if (dev_run) printf( @@ -237,28 +237,41 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") { "solution\n"); HighsInt num_integer_variable = 0; for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) - if (lp.integrality_[iCol] == HighsVarType::kInteger) num_integer_variable++; + if (lp.integrality_[iCol] == HighsVarType::kInteger) + num_integer_variable++; - highs.setOptionValue("output_flag", dev_run); highs.readModel(model_file); + std::vector index; + std::vector value; + // Check that duplicate values are spotted + index.push_back(0); + value.push_back(0); + index.push_back(1); + value.push_back(1); + index.push_back(0); + value.push_back(2); + HighsInt num_entries = index.size(); + return_status = highs.setSolution(num_entries, index.data(), value.data()); + REQUIRE(return_status == HighsStatus::kWarning); + index.clear(); + value.clear(); std::vector is_set; is_set.assign(lp.num_col_, false); - std::vector index; - std::vector value; - HighsInt num_to_set = std::max(10, (8*num_integer_variable)/10); - assert(num_to_set>0); + HighsInt num_to_set = 2; + assert(num_to_set > 0); HighsRandom random; for (HighsInt iSet = 0; iSet < num_to_set;) { HighsInt iCol = random.integer(lp.num_col_); if (lp.integrality_[iCol] != HighsVarType::kInteger) continue; if (is_set[iCol]) continue; + is_set[iCol] = true; index.push_back(iCol); value.push_back(optimal_solution.col_value[iCol]); iSet++; } - HighsInt num_entries = index.size(); + num_entries = index.size(); assert(num_entries == num_to_set); return_status = highs.setSolution(num_entries, index.data(), value.data()); REQUIRE(return_status == HighsStatus::kOk); diff --git a/src/Highs.h b/src/Highs.h index 3a1c637642..f104d72f1d 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -1066,9 +1066,8 @@ class Highs { /** * @brief Pass a sparse primal solution */ - HighsStatus setSolution(const HighsInt num_entries, - const HighsInt* index, - const double* value); + HighsStatus setSolution(const HighsInt num_entries, const HighsInt* index, + const double* value); /** * @brief Set the callback method to use for HiGHS diff --git a/src/lp_data/HConst.h b/src/lp_data/HConst.h index e9359556e2..d7980f4cc8 100644 --- a/src/lp_data/HConst.h +++ b/src/lp_data/HConst.h @@ -27,6 +27,7 @@ const size_t kHighsSize_tInf = std::numeric_limits::max(); const HighsInt kHighsIInf = std::numeric_limits::max(); const HighsInt kHighsIInf32 = std::numeric_limits::max(); const double kHighsInf = std::numeric_limits::infinity(); +const double kHighsUndefined = kHighsInf; const double kHighsTiny = 1e-14; const double kHighsMacheps = std::ldexp(1, -52); const double kHighsZero = 1e-50; diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index 691af85ade..bf3c72b384 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -1966,11 +1966,53 @@ HighsStatus Highs::setSolution(const HighsSolution& solution) { } HighsStatus Highs::setSolution(const HighsInt num_entries, - const HighsInt* index, - const double* value) { + const HighsInt* index, const double* value) { HighsStatus return_status = HighsStatus::kOk; - return_status = HighsStatus::kError; - return returnFromHighs(return_status); + // Warn about duplicates in index + HighsInt num_duplicates = 0; + std::vector is_set; + is_set.assign(model_.lp_.num_col_, false); + for (HighsInt iX = 0; iX < num_entries; iX++) { + HighsInt iCol = index[iX]; + if (iCol < 0 || iCol > model_.lp_.num_col_) { + highsLogUser(options_.log_options, HighsLogType::kError, + "setSolution: User solution index %d has value %d out of " + "range [0, %d)", + int(iX), int(iCol), int(model_.lp_.num_col_)); + return HighsStatus::kError; + } else if (value[iX] < model_.lp_.col_lower_[iCol] - + options_.primal_feasibility_tolerance || + model_.lp_.col_upper_[iCol] + + options_.primal_feasibility_tolerance < + value[iX]) { + highsLogUser(options_.log_options, HighsLogType::kError, + "setSolution: User solution value %d of %g is infeasible " + "for bounds [%g, %g]", + int(iX), value[iX], model_.lp_.col_lower_[iCol], + model_.lp_.col_upper_[iCol]); + return HighsStatus::kError; + } + if (is_set[iCol]) num_duplicates++; + is_set[iCol] = true; + } + if (num_duplicates > 0) { + highsLogUser(options_.log_options, HighsLogType::kWarning, + "setSolution: User set of indices has %d duplicate%s: last " + "value used\n", + int(num_duplicates), num_duplicates > 1 ? "s" : ""); + return_status = HighsStatus::kWarning; + } + + // Clear the solution, indicate the values not determined by the + // user, and insert the values determined by the user + HighsSolution new_solution; + new_solution.col_value.assign(model_.lp_.num_col_, kHighsUndefined); + for (HighsInt iX = 0; iX < num_entries; iX++) { + HighsInt iCol = index[iX]; + new_solution.col_value[iCol] = value[iX]; + } + return interpretCallStatus(options_.log_options, setSolution(new_solution), + return_status, "setSolution"); } HighsStatus Highs::setCallback(HighsCallbackFunctionType user_callback, @@ -3356,62 +3398,125 @@ void Highs::invalidateEkk() { ekk_instance_.invalidate(); } HighsStatus Highs::completeSolutionFromDiscreteAssignment() { // Determine whether the current solution of a MIP is feasible and, - // if not, try to assign values to continuous variables to achieve a - // feasible solution. Valuable in the case where users make a - // heuristic assignment of discrete variables + // if not, try to assign values to continuous variables and discrete + // variables not at integer values to achieve a feasible + // solution. Valuable in the case where users make a heuristic + // (partial) assignment of discrete variables assert(model_.isMip() && solution_.value_valid); HighsLp& lp = model_.lp_; - bool valid, integral, feasible; - // Determine whether this solution is feasible, or just integer feasible - HighsStatus return_status = assessLpPrimalSolution(options_, lp, solution_, - valid, integral, feasible); - assert(return_status != HighsStatus::kError); - assert(valid); - // If the current solution is feasible, then solution can be used by - // MIP solver to get a primal bound - if (feasible) return HighsStatus::kOk; + // Determine whether the solution contains undefined values, in + // order to decide whether to check its feasibility + bool contains_undefined_values = false; + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + if (solution_.col_value[iCol] == kHighsUndefined) { + contains_undefined_values = true; + break; + } + } + if (!contains_undefined_values) { + bool valid, integral, feasible; + // Determine whether this solution is integer feasible + HighsStatus return_status = assessLpPrimalSolution( + options_, lp, solution_, valid, integral, feasible); + assert(return_status != HighsStatus::kError); + assert(valid); + // If the current solution is integer feasible, then it can be + // used by MIP solver to get a primal bound + if (feasible) return HighsStatus::kOk; + } // Save the column bounds and integrality in preparation for fixing - // the non-continuous variables when user-supplied values are + // the discrete variables when user-supplied values are // integer std::vector save_col_lower = lp.col_lower_; std::vector save_col_upper = lp.col_upper_; std::vector save_integrality = lp.integrality_; const bool have_integrality = (lp.integrality_.size() != 0); - bool is_integer = true; + assert(have_integrality); + // Count the number of fixed and unfixed discrete variables + HighsInt num_fixed_discrete_variable = 0; + HighsInt num_unfixed_discrete_variable = 0; for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { - if (lp.integrality_[iCol] == HighsVarType::kContinuous) continue; - // Fix non-continuous variable if it has integer value const double primal = solution_.col_value[iCol]; - const double lower = lp.col_lower_[iCol]; - const double upper = lp.col_upper_[iCol]; - const HighsVarType type = - have_integrality ? lp.integrality_[iCol] : HighsVarType::kContinuous; - double col_infeasibility = 0; - double integer_infeasibility = 0; - assessColPrimalSolution(options_, primal, lower, upper, type, - col_infeasibility, integer_infeasibility); - if (integer_infeasibility > options_.mip_feasibility_tolerance) { - // Variable is not integer feasible, so record that a MIP will - // have to be solved - is_integer = false; + // Default value is lower bound, unless primal is integer for a + // discrete variable + solution_.col_value[iCol] = lp.col_lower_[iCol]; + if (lp.integrality_[iCol] == HighsVarType::kContinuous) continue; + // Fix discrete variable if its value is defined and integer + if (primal == kHighsUndefined) { + num_unfixed_discrete_variable++; } else { - // Variable is integer feasible, so fix it at this value and - // remove its integrality - lp.col_lower_[iCol] = solution_.col_value[iCol]; - lp.col_upper_[iCol] = solution_.col_value[iCol]; - lp.integrality_[iCol] = HighsVarType::kContinuous; + const double lower = lp.col_lower_[iCol]; + const double upper = lp.col_upper_[iCol]; + const HighsVarType type = + have_integrality ? lp.integrality_[iCol] : HighsVarType::kContinuous; + double col_infeasibility = 0; + double integer_infeasibility = 0; + assessColPrimalSolution(options_, primal, lower, upper, type, + col_infeasibility, integer_infeasibility); + if (integer_infeasibility > options_.mip_feasibility_tolerance) { + num_unfixed_discrete_variable++; + } else { + // Variable is integer feasible, so fix it at this value and + // remove its integrality + num_fixed_discrete_variable++; + lp.col_lower_[iCol] = primal; + lp.col_upper_[iCol] = primal; + lp.integrality_[iCol] = HighsVarType::kContinuous; + } } } - // If the solution is integer valued, only an LP needs to be solved, - // so clear all integrality - if (is_integer) lp.integrality_.clear(); + const HighsInt num_discrete_variable = + num_unfixed_discrete_variable + num_fixed_discrete_variable; + const HighsInt num_continuous_variable = lp.num_col_ - num_discrete_variable; + assert(num_continuous_variable >= 0); + bool call_run = true; + if (num_unfixed_discrete_variable == 0) { + // Solution is integer valued + if (num_continuous_variable == 0) { + // There are no continuous variables, so no feasible solution can be + // deduced + highsLogUser(options_.log_options, HighsLogType::kInfo, + "User-supplied values of discrete variables cannot yield " + "feasible solution\n"); + call_run = false; + } else { + // Solve an LP, so clear all integrality + lp.integrality_.clear(); + highsLogUser( + options_.log_options, HighsLogType::kInfo, + "Attempting to find feasible solution " + "by solving LP for user-supplied values of discrete variables\n"); + } + } else { + // There are unfixed discrete variables + if (10 * num_fixed_discrete_variable < num_discrete_variable) { + // Too few discrete variables are fixed + highsLogUser(options_.log_options, HighsLogType::kInfo, + "User-supplied values fix only %d / %d discrete variables, " + "so not attempting to complete a feasible solution\n", + int(num_fixed_discrete_variable), + int(num_discrete_variable)); + call_run = false; + } else { + highsLogUser(options_.log_options, HighsLogType::kInfo, + "Attempting to find feasible solution " + "by solving MIP for user-supplied values of %d / %d " + "discrete variables\n", + int(num_fixed_discrete_variable), + int(num_discrete_variable)); + } + } + HighsStatus return_status = HighsStatus::kOk; + // Clear the current solution since either the user solution has + // been used to fix (a subset of) discrete variables - so a valid + // solution will be obtained from run() if the local model is + // feasible - or it's not worth using the user solution solution_.clear(); - basis_.clear(); - // Solve the model - highsLogUser(options_.log_options, HighsLogType::kInfo, - "Attempting to find feasible solution " - "for (partial) user-supplied values of discrete variables\n"); - return_status = this->run(); + if (call_run) { + // Solve the model + basis_.clear(); + return_status = this->run(); + } // Recover the column bounds and integrality lp.col_lower_ = save_col_lower; lp.col_upper_ = save_col_upper; From 39569be5ae58ef62d2f93cd7fd4909ecbe310156 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sun, 14 Jul 2024 17:47:06 +0100 Subject: [PATCH 042/194] Added highs_setSolution and highs_setSparseSolution to highs_bindings --- src/highs_bindings.cpp | 21 ++++++++++++++++++++- src/lp_data/HStruct.h | 1 + src/lp_data/Highs.cpp | 1 + src/lp_data/HighsSolution.cpp | 6 ++++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/highs_bindings.cpp b/src/highs_bindings.cpp index 4883bd6c25..e7f9e49cac 100644 --- a/src/highs_bindings.cpp +++ b/src/highs_bindings.cpp @@ -285,6 +285,24 @@ HighsStatus highs_deleteRows(Highs* h, HighsInt num_set_entries, std::vectordeleteRows(num_set_entries, indices.data()); } + +HighsStatus highs_setSolution(Highs* h, HighsSolution& solution) { + return h->setSolution(solution); +} + +HighsStatus highs_setSparseSolution(Highs* h, HighsInt num_entries, + py::array_t index, + py::array_t value) { + py::buffer_info index_info = index.request(); + py::buffer_info value_info = value.request(); + + HighsInt* index_ptr = reinterpret_cast(index_info.ptr); + double* value_ptr = static_cast(value_info.ptr); + + return h->setSolution(num_entries, index_ptr, value_ptr); +} + + HighsStatus highs_setBasis(Highs* h, HighsBasis& basis) { return h->setBasis(basis); } @@ -935,7 +953,8 @@ PYBIND11_MODULE(_core, m) { .def("deleteCols", &highs_deleteCols) .def("deleteVars", &highs_deleteCols) // alias .def("deleteRows", &highs_deleteRows) - .def("setSolution", &Highs::setSolution) + .def("setSolution", &highs_setSolution) + .def("setSolution", &highs_setSparseSolution) .def("setBasis", &highs_setBasis) .def("setBasis", &highs_setLogicalBasis) .def("modelStatusToString", &Highs::modelStatusToString) diff --git a/src/lp_data/HStruct.h b/src/lp_data/HStruct.h index 863020bfde..e54657d406 100644 --- a/src/lp_data/HStruct.h +++ b/src/lp_data/HStruct.h @@ -33,6 +33,7 @@ struct HighsSolution { std::vector col_dual; std::vector row_value; std::vector row_dual; + bool hasUndefined(); void invalidate(); void clear(); }; diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index bf3c72b384..fee0502b58 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -3413,6 +3413,7 @@ HighsStatus Highs::completeSolutionFromDiscreteAssignment() { break; } } + assert(solution_.hasUndefined() == contains_undefined_values); if (!contains_undefined_values) { bool valid, integral, feasible; // Determine whether this solution is integer feasible diff --git a/src/lp_data/HighsSolution.cpp b/src/lp_data/HighsSolution.cpp index 4ad8617ddd..77d3daa8ce 100644 --- a/src/lp_data/HighsSolution.cpp +++ b/src/lp_data/HighsSolution.cpp @@ -1438,6 +1438,12 @@ bool isBasisRightSize(const HighsLp& lp, const HighsBasis& basis) { basis.row_status.size() == static_cast(lp.num_row_); } +bool HighsSolution::hasUndefined() { + for (HighsInt iCol = 0; iCol < HighsInt(this->col_value.size()); iCol++) + if (this->col_value[iCol] == kHighsUndefined) return true; + return false; +} + void HighsSolution::invalidate() { this->value_valid = false; this->dual_valid = false; From faeb3f3972761cf2e1a86fcefd5395d770e66136 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sun, 14 Jul 2024 17:48:56 +0100 Subject: [PATCH 043/194] Formatted HighsSolution::hasUndefined() --- src/lp_data/HighsSolution.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lp_data/HighsSolution.cpp b/src/lp_data/HighsSolution.cpp index 77d3daa8ce..29e40168a0 100644 --- a/src/lp_data/HighsSolution.cpp +++ b/src/lp_data/HighsSolution.cpp @@ -1439,7 +1439,7 @@ bool isBasisRightSize(const HighsLp& lp, const HighsBasis& basis) { } bool HighsSolution::hasUndefined() { - for (HighsInt iCol = 0; iCol < HighsInt(this->col_value.size()); iCol++) + for (HighsInt iCol = 0; iCol < HighsInt(this->col_value.size()); iCol++) if (this->col_value[iCol] == kHighsUndefined) return true; return false; } From 6e9f9bdf6421d838586066570dbffc4c407395be Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sun, 14 Jul 2024 18:11:18 +0100 Subject: [PATCH 044/194] Added Highs_setSparseSolution to C API --- src/interfaces/highs_c_api.cpp | 5 +++++ src/interfaces/highs_c_api.h | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/interfaces/highs_c_api.cpp b/src/interfaces/highs_c_api.cpp index 0642b73283..59b73668b6 100644 --- a/src/interfaces/highs_c_api.cpp +++ b/src/interfaces/highs_c_api.cpp @@ -668,6 +668,11 @@ HighsInt Highs_setSolution(void* highs, const double* col_value, return (HighsInt)((Highs*)highs)->setSolution(solution); } +HighsInt Highs_setSparseSolution(void* highs, const HighsInt num_entries, + const HighsInt* index, const double* value) { + return (HighsInt)((Highs*)highs)->setSolution(num_entries, index, value); +} + HighsInt Highs_setCallback(void* highs, HighsCCallbackType user_callback, void* user_callback_data) { auto status = static_cast(highs)->setCallback(user_callback, diff --git a/src/interfaces/highs_c_api.h b/src/interfaces/highs_c_api.h index 1a43dd8e81..801ddf332e 100644 --- a/src/interfaces/highs_c_api.h +++ b/src/interfaces/highs_c_api.h @@ -1139,6 +1139,19 @@ HighsInt Highs_setSolution(void* highs, const double* col_value, const double* row_value, const double* col_dual, const double* row_dual); +/** + * Set a partial primal solution by passing values for a set of variables + * + * @param highs A pointer to the Highs instance. + * @param num_entries Number of variables in the set + * @param index Indices of variables in the set + * @param value Values of variables in the set + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ +HighsInt Highs_setSparseSolution(void* highs, const HighsInt num_entries, + const HighsInt* index, const double* value); + /** * Set the callback method to use for HiGHS * From 3c343537206787d7e8d01702af58bd24908a3022 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sun, 14 Jul 2024 18:39:15 +0100 Subject: [PATCH 045/194] Now using solution_.hasUndefined(); updated FEATURES.md and silenced unti tests --- FEATURES.md | 7 +++++++ check/TestCallbacks.cpp | 4 ++-- src/lp_data/Highs.cpp | 10 ++-------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/FEATURES.md b/FEATURES.md index 9d7043302e..a5e198a407 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -4,3 +4,10 @@ Added `int64_t mip_total_lp_iterations` to `HighsCallbackDataOut` and modified accessor function +`Highs::writeSolution` and `Highs::writeBasis` now being done via `HighsIO` logging, so can be redirected to logging callback. + +Introduced `const double kHighsUndefined` as value of undefined values in a user solution. It's equal to `kHighsInf` + +Added `Highs::setSolution(const HighsInt num_entries, const HighsInt* index, const double* value);` to allow a sparse primal solution to be defined. + + diff --git a/check/TestCallbacks.cpp b/check/TestCallbacks.cpp index 437fdaf7b3..d97bc0d271 100644 --- a/check/TestCallbacks.cpp +++ b/check/TestCallbacks.cpp @@ -277,8 +277,8 @@ TEST_CASE("highs-callback-solution-basis-logging", "[highs-callback]") { highs.run(); highs.setCallback(userInterruptCallback, p_user_callback_data); highs.startCallback(kCallbackLogging); - highs.writeSolution("", kSolutionStylePretty); - highs.writeBasis(""); + if (dev_run) highs.writeSolution("", kSolutionStylePretty); + if (dev_run) highs.writeBasis(""); } TEST_CASE("highs-callback-simplex-interrupt", "[highs-callback]") { diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index fee0502b58..ac8196715a 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -3406,14 +3406,7 @@ HighsStatus Highs::completeSolutionFromDiscreteAssignment() { HighsLp& lp = model_.lp_; // Determine whether the solution contains undefined values, in // order to decide whether to check its feasibility - bool contains_undefined_values = false; - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { - if (solution_.col_value[iCol] == kHighsUndefined) { - contains_undefined_values = true; - break; - } - } - assert(solution_.hasUndefined() == contains_undefined_values); + const bool contains_undefined_values = solution_.hasUndefined(); if (!contains_undefined_values) { bool valid, integral, feasible; // Determine whether this solution is integer feasible @@ -3466,6 +3459,7 @@ HighsStatus Highs::completeSolutionFromDiscreteAssignment() { } } } + assert(!solution_.hasUndefined()); const HighsInt num_discrete_variable = num_unfixed_discrete_variable + num_fixed_discrete_variable; const HighsInt num_continuous_variable = lp.num_col_ - num_discrete_variable; From 49f320a14869f43e877591fec00803ef793f7e78 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 15 Jul 2024 11:02:05 +0100 Subject: [PATCH 046/194] Added option mip_max_start_nodes to be used for mip_max_nodes when a MIP is solved to complete a partial solution to avoid excessive cost --- FEATURES.md | 3 ++- src/lp_data/Highs.cpp | 24 ++++++++++++++++-------- src/lp_data/HighsOptions.h | 11 ++++++++++- src/presolve/HighsSymmetry.cpp | 4 ++-- 4 files changed, 30 insertions(+), 12 deletions(-) diff --git a/FEATURES.md b/FEATURES.md index a5e198a407..c6db2f0380 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -8,6 +8,7 @@ Added `int64_t mip_total_lp_iterations` to `HighsCallbackDataOut` and modified a Introduced `const double kHighsUndefined` as value of undefined values in a user solution. It's equal to `kHighsInf` -Added `Highs::setSolution(const HighsInt num_entries, const HighsInt* index, const double* value);` to allow a sparse primal solution to be defined. +Added `Highs::setSolution(const HighsInt num_entries, const HighsInt* index, const double* value);` to allow a sparse primal solution to be defined. When a MIP is solved to do this, the value of (new) option `mip_max_start_nodes` is used for `mip_max_nodes` to avoid excessive cost + diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index ac8196715a..acf2780b39 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -3465,6 +3465,8 @@ HighsStatus Highs::completeSolutionFromDiscreteAssignment() { const HighsInt num_continuous_variable = lp.num_col_ - num_discrete_variable; assert(num_continuous_variable >= 0); bool call_run = true; + const bool few_fixed_discrete_variables = + 10 * num_fixed_discrete_variable < num_discrete_variable; if (num_unfixed_discrete_variable == 0) { // Solution is integer valued if (num_continuous_variable == 0) { @@ -3484,14 +3486,14 @@ HighsStatus Highs::completeSolutionFromDiscreteAssignment() { } } else { // There are unfixed discrete variables - if (10 * num_fixed_discrete_variable < num_discrete_variable) { - // Too few discrete variables are fixed - highsLogUser(options_.log_options, HighsLogType::kInfo, - "User-supplied values fix only %d / %d discrete variables, " - "so not attempting to complete a feasible solution\n", - int(num_fixed_discrete_variable), - int(num_discrete_variable)); - call_run = false; + if (few_fixed_discrete_variables) { + // Too few discrete variables are fixed so warn, but still + // attempt to complete a feasible solution + highsLogUser( + options_.log_options, HighsLogType::kWarning, + "User-supplied values fix only %d / %d discrete variables, " + "so attempt to complete a feasible solution may be expensive\n", + int(num_fixed_discrete_variable), int(num_discrete_variable)); } else { highsLogUser(options_.log_options, HighsLogType::kInfo, "Attempting to find feasible solution " @@ -3508,9 +3510,15 @@ HighsStatus Highs::completeSolutionFromDiscreteAssignment() { // feasible - or it's not worth using the user solution solution_.clear(); if (call_run) { + // Solve the model, using mip_max_start_nodes for + // mip_max_nodes... + const HighsInt mip_max_nodes = options_.mip_max_nodes; + options_.mip_max_nodes = options_.mip_max_start_nodes; // Solve the model basis_.clear(); return_status = this->run(); + // ... remembering to recover the original value of mip_max_nodes + options_.mip_max_nodes = mip_max_nodes; } // Recover the column bounds and integrality lp.col_lower_ = save_col_lower; diff --git a/src/lp_data/HighsOptions.h b/src/lp_data/HighsOptions.h index 9a9bb29aa6..7c939583ae 100644 --- a/src/lp_data/HighsOptions.h +++ b/src/lp_data/HighsOptions.h @@ -401,6 +401,7 @@ struct HighsOptionsStruct { bool mip_allow_restart; HighsInt mip_max_nodes; HighsInt mip_max_stall_nodes; + HighsInt mip_max_start_nodes; HighsInt mip_max_leaves; HighsInt mip_max_improving_sols; HighsInt mip_lp_age_limit; @@ -527,6 +528,7 @@ struct HighsOptionsStruct { mip_allow_restart(false), mip_max_nodes(0), mip_max_stall_nodes(0), + mip_max_start_nodes(0), mip_max_leaves(0), mip_max_improving_sols(0), mip_lp_age_limit(0), @@ -916,6 +918,13 @@ class HighsOptions : public HighsOptionsStruct { "MIP solver max number of nodes where estimate is above cutoff bound", advanced, &mip_max_stall_nodes, 0, kHighsIInf, kHighsIInf); records.push_back(record_int); + + record_int = new OptionRecordInt( + "mip_max_start_nodes", + "MIP solver max number of nodes when completing a partial MIP start", + advanced, &mip_max_start_nodes, 0, 500, kHighsIInf); + records.push_back(record_int); + #ifdef HIGHS_DEBUGSOL record_string = new OptionRecordString( "mip_debug_solution_file", @@ -944,7 +953,7 @@ class HighsOptions : public HighsOptionsStruct { records.push_back(record_string); record_int = new OptionRecordInt( - "mip_max_leaves", "MIP solver max number of leave nodes", advanced, + "mip_max_leaves", "MIP solver max number of leaf nodes", advanced, &mip_max_leaves, 0, kHighsIInf, kHighsIInf); records.push_back(record_int); diff --git a/src/presolve/HighsSymmetry.cpp b/src/presolve/HighsSymmetry.cpp index a34d1d7902..53f998ef45 100644 --- a/src/presolve/HighsSymmetry.cpp +++ b/src/presolve/HighsSymmetry.cpp @@ -1078,7 +1078,7 @@ bool HighsSymmetryDetection::distinguishVertex(HighsInt targetCell) { void HighsSymmetryDetection::backtrack(HighsInt backtrackStackNewEnd, HighsInt backtrackStackEnd) { - // we assume that we always backtrack from a leave node, i.e. a discrete + // we assume that we always backtrack from a leaf node, i.e. a discrete // partition therefore we do not need to remember the values of the hash // contributions as it is the indentity for each position and all new cells // are on the cell creation stack. @@ -1805,7 +1805,7 @@ void HighsSymmetryDetection::run(HighsSymmetries& symmetries) { // than the current best leave, because its prefix length is smaller // than the best leaves and it would have been already pruned if // it's certificate value was larger unless it is equal to the first - // leave nodes certificate value which is caught by the first case + // leaf nodes certificate value which is caught by the first case // of the if condition. Hence, having a lexicographically smaller // certificate value than the best leave is the only way to get // here. From e3708c754034c793de684ccb076782366cf76574 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Tue, 16 Jul 2024 23:15:39 +0200 Subject: [PATCH 047/194] Update relevant substitutions if a variable is transformed --- src/mip/HighsImplications.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/mip/HighsImplications.h b/src/mip/HighsImplications.h index ac66aaf771..793374b3de 100644 --- a/src/mip/HighsImplications.h +++ b/src/mip/HighsImplications.h @@ -119,6 +119,10 @@ class HighsImplications { vlbs[col].for_each(transformVbd); vubs[col].for_each(transformVbd); + + for (auto& substitution : substitutions) { + if (substitution.substcol == col) substitution.offset -= constant; + } } std::pair getBestVub(HighsInt col, From 91da631610639462e5e4d6cd7ee8e479e488007c Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 17 Jul 2024 14:26:55 +0200 Subject: [PATCH 048/194] Take scale into account --- src/mip/HighsImplications.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/mip/HighsImplications.h b/src/mip/HighsImplications.h index 793374b3de..ddbb0d2d04 100644 --- a/src/mip/HighsImplications.h +++ b/src/mip/HighsImplications.h @@ -121,7 +121,10 @@ class HighsImplications { vubs[col].for_each(transformVbd); for (auto& substitution : substitutions) { - if (substitution.substcol == col) substitution.offset -= constant; + if (substitution.substcol == col) { + substitution.offset -= substitution.scale * constant; + substitution.scale /= scale; + } } } From 8966bc0e8c6f09e66119456ccd606cb17603aa0a Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 17 Jul 2024 23:30:21 +0200 Subject: [PATCH 049/194] Update analogous to variable bounds --- src/mip/HighsImplications.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mip/HighsImplications.h b/src/mip/HighsImplications.h index ddbb0d2d04..1240762121 100644 --- a/src/mip/HighsImplications.h +++ b/src/mip/HighsImplications.h @@ -122,7 +122,8 @@ class HighsImplications { for (auto& substitution : substitutions) { if (substitution.substcol == col) { - substitution.offset -= substitution.scale * constant; + substitution.offset -= constant; + substitution.offset /= scale; substitution.scale /= scale; } } From d318f6500fcc3dc800b142e95275e2d06d7e3087 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 17 Jul 2024 23:34:49 +0200 Subject: [PATCH 050/194] Add comment --- src/mip/HighsImplications.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mip/HighsImplications.h b/src/mip/HighsImplications.h index 1240762121..33e04a87ac 100644 --- a/src/mip/HighsImplications.h +++ b/src/mip/HighsImplications.h @@ -109,6 +109,7 @@ class HighsImplications { double vlbconstant); void columnTransformed(HighsInt col, double scale, double constant) { + // Update variable bounds affected by transformation if (scale < 0) std::swap(vubs[col], vlbs[col]); auto transformVbd = [&](HighsInt, VarBound& vbd) { @@ -120,6 +121,7 @@ class HighsImplications { vlbs[col].for_each(transformVbd); vubs[col].for_each(transformVbd); + // Update substitutions affected by transformation for (auto& substitution : substitutions) { if (substitution.substcol == col) { substitution.offset -= constant; From 322e298e2ed7453d6162cabd2e4ad7e6abac91bf Mon Sep 17 00:00:00 2001 From: Mathemalsky <78656598+Mathemalsky@users.noreply.github.com> Date: Mon, 22 Jul 2024 11:32:16 +0200 Subject: [PATCH 051/194] Update CONTRIBUTING.md Fix broken link to documentation subdirectory. --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 225f329932..e45876de54 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,7 +12,7 @@ Contact [Julian](https://github.com/jajhall) (General issues and solvers), [Ivet ## Improve the documentation -The top level [documentation](https://ergo-code.github.io/HiGHS/) is created using [Docsy](https://www.docsy.dev/), with the files held on the [HiGHS repository](https://github.com/ERGO-Code/HiGHS/tree/docsy). If your change is small (like fixing typos, or one or two sentence corrections), the easiest way to do this is to fork the branch and create a pull request. (See *Contribute code to HiGHS* below for more on this.) If your change is larger, or touches multiple files, please raise an issue describing what you want to do. +The top level [documentation](https://ergo-code.github.io/HiGHS/) is created using [Docsy](https://www.docsy.dev/), with the files held on the [HiGHS repository](https://github.com/ERGO-Code/HiGHS/tree/master/docs). If your change is small (like fixing typos, or one or two sentence corrections), the easiest way to do this is to fork the branch and create a pull request. (See *Contribute code to HiGHS* below for more on this.) If your change is larger, or touches multiple files, please raise an issue describing what you want to do. ## Raise an issue From 79727d10a2d1a24729aefcf478bd122255131c1f Mon Sep 17 00:00:00 2001 From: jajhall Date: Mon, 22 Jul 2024 12:05:47 +0100 Subject: [PATCH 052/194] Added IPM reference and commented on absence of references for QP and MIP --- docs/src/index.md | 13 +++++++++++++ docs/src/terminology.md | 1 + 2 files changed, 14 insertions(+) diff --git a/docs/src/index.md b/docs/src/index.md index 59e799a4da..697b79eae1 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -59,6 +59,19 @@ The C interface cannot make use of the C++ structures and enums, and its methods For LPs, HiGHS has implementations of both the revised simplex and interior point methods. MIPs are solved by branch-and-cut, and QPs by active set. +For LP, the novel features of the dual simplex solver are described in +_Parallelizing the dual revised simplex method_, Q. Huangfu and +J. A. J. Hall, Mathematical Programming Computation, 10 (1), 119-142, +2018 [DOI: +10.1007/s12532-017-0130-5](https://link.springer.com/article/10.1007/s12532-017-0130-5). For +the interior point solver, the reference is _Implementation of an +interior point method with basis preconditioning_, Mathematical +Programming Computation, 12, 603-635, 2020. [DOI: +10.1007/s12532-020-00181-8](https://link.springer.com/article/10.1007/s12532-020-00181-8). There +are no rspecific eferences to the techniques used in the MIP or QP +solvers. + + ## Citing HiGHS If you use HiGHS in an academic context, please cite the following article: diff --git a/docs/src/terminology.md b/docs/src/terminology.md index fba4e7849c..4faabb40bd 100644 --- a/docs/src/terminology.md +++ b/docs/src/terminology.md @@ -139,3 +139,4 @@ relative to the primal bound is a better measure. When the gap reaches zero then the MIP is solved to optimality. However, it is often preferable to stop the MIP solver when the relative gap is below a specified tolerance. + From a0e50ed9f6868042fb3ea06da0b6fe5845371401 Mon Sep 17 00:00:00 2001 From: Mathemalsky <78656598+Mathemalsky@users.noreply.github.com> Date: Tue, 23 Jul 2024 10:05:43 +0200 Subject: [PATCH 053/194] Update index.md Fix typo --- docs/src/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/index.md b/docs/src/index.md index 697b79eae1..1af6ab7ee6 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -68,7 +68,7 @@ the interior point solver, the reference is _Implementation of an interior point method with basis preconditioning_, Mathematical Programming Computation, 12, 603-635, 2020. [DOI: 10.1007/s12532-020-00181-8](https://link.springer.com/article/10.1007/s12532-020-00181-8). There -are no rspecific eferences to the techniques used in the MIP or QP +are no specific references to the techniques used in the MIP or QP solvers. From 3b5c3da9af4d2bd6c848f8001a909221f6295fb4 Mon Sep 17 00:00:00 2001 From: Luke Marshall <52978038+mathgeekcoder@users.noreply.github.com> Date: Tue, 23 Jul 2024 12:07:28 -0700 Subject: [PATCH 054/194] Refactor highspy for enhanced usability Refactor highspy for enhanced usability This commit significantly improves the `Highs` class within `highs.py`, focusing on enhancing usability, efficiency, and robustness. Key changes include: - Added comprehensive docstrings. - Improved methods for adding, deleting, and retrieving multiple variables and constraints, for a more flexible and efficient API. - Standardized some API conventions. Note, this is a breaking change for the constraint value/dual methods. - Updated tests and examples. --- examples/chip.py | 36 +-- examples/distillation.py | 48 ++- examples/minimal.py | 37 ++- examples/network_flow.py | 37 +++ examples/nqueens.py | 29 ++ src/highspy/highs.py | 631 +++++++++++++++++++++++++++++---------- tests/test_highspy.py | 324 +++++++++++++------- 7 files changed, 824 insertions(+), 318 deletions(-) create mode 100644 examples/network_flow.py create mode 100644 examples/nqueens.py diff --git a/examples/chip.py b/examples/chip.py index dae469cd7d..3391b78954 100644 --- a/examples/chip.py +++ b/examples/chip.py @@ -9,23 +9,12 @@ h = highspy.Highs() h.silent() -varNames = list() -varNames.append('Tables') -varNames.append('Sets of chairs') +items = ['Tables', 'Sets of chairs'] +x = h.addVariables(items, obj = [10, 20], name = items) -x1 = h.addVariable(obj = 10, name = varNames[0]) -x2 = h.addVariable(obj = 25, name = varNames[1]) - -vars = list() -vars.append(x1) -vars.append(x2) - -constrNames = list() -constrNames.append('Assembly') -constrNames.append('Finishing') - -h.addConstr(x1 + 2*x2 <= 80, name = constrNames[0]) -h.addConstr(x1 + 4*x2 <= 120, name = constrNames[1]) +constrNames = ['Assembly', 'Finishing'] +cons = h.addConstrs(x['Tables'] + 2*x['Sets of chairs'] <= 80, + x['Tables'] + 4*x['Sets of chairs'] <= 120, name = constrNames) h.setMaximize() @@ -36,16 +25,17 @@ h.solve() -for var in vars: + +for n, var in x.items(): print('Make', h.variableValue(var), h.variableName(var), ': Reduced cost', h.variableDual(var)) -print('Make', h.variableValues(vars), 'of', h.variableNames(vars)) + +print('Make', h.variableValues(x.values()), 'of', h.variableNames(x.values())) print('Make', h.allVariableValues(), 'of', h.allVariableNames()) -for name in constrNames: - print('Constraint', name, 'has value', h.constrValue(name), 'and dual', h.constrDual(name)) - -print('Constraints have values', h.constrValues(constrNames), 'and duals', h.constrDuals(constrNames)) - +for c in cons: + print('Constraint', c.name, 'has value', h.constrValue(c), 'and dual', h.constrDual(c)) + +print('Constraints have values', h.constrValues(cons), 'and duals', h.constrDuals(cons)) print('Constraints have values', h.allConstrValues(), 'and duals', h.allConstrDuals()) print('Optimal objective value is', h.getObjectiveValue()) diff --git a/examples/distillation.py b/examples/distillation.py index 5e375ab496..ab85c7b08b 100644 --- a/examples/distillation.py +++ b/examples/distillation.py @@ -15,25 +15,13 @@ h = highspy.Highs() h.silent() -variableNames = list() -variableNames.append('TypeA') -variableNames.append('TypeB') +variableNames = ['TypeA', 'TypeB'] +x = h.addVariables(variableNames, obj = [8, 10], name = variableNames[0]) -useTypeA = h.addVariable(obj = 8, name = variableNames[0]) -useTypeB = h.addVariable(obj = 10, name = variableNames[1]) - -vars = list() -vars.append(useTypeA) -vars.append(useTypeB) - -constrNames = list() -constrNames.append('Product1') -constrNames.append('Product2') -constrNames.append('Product3') - -h.addConstr(2*useTypeA + 2*useTypeB >= 7, name = constrNames[0]) -h.addConstr(3*useTypeA + 4*useTypeB >= 12, name = constrNames[1]) -h.addConstr(2*useTypeA + useTypeB >= 6, name = constrNames[2]) +constrNames = ['Product1', 'Product2', 'Product3'] +cons = h.addConstrs(2*x['TypeA'] + 2*x['TypeB'] >= 7, + 3*x['TypeA'] + 4*x['TypeB'] >= 12, + 2*x['TypeA'] + x['TypeB'] >= 6, name = constrNames) h.setMinimize() @@ -47,31 +35,31 @@ h.solve() -for var in vars: +for name, var in x.items(): print('Use {0:.1f} of {1:s}: reduced cost {2:.6f}'.format(h.variableValue(var), h.variableName(var), h.variableDual(var))) -print('Use', h.variableValues(vars), 'of', h.variableNames(vars)) + +print('Use', h.variableValues(x.values()), 'of', h.variableNames(x.values())) print('Use', h.allVariableValues(), 'of', h.allVariableNames()) -for name in constrNames: - print(f"Constraint {name} has value {h.constrValue(name):{width}.{precision}} and dual {h.constrDual(name):{width}.{precision}}") - -print('Constraints have values', h.constrValues(constrNames), 'and duals', h.constrDuals(constrNames)) +for c in cons: + print(f"Constraint {c.name} has value {h.constrValue(c):{width}.{precision}} and dual {h.constrDual(c):{width}.{precision}}") +print('Constraints have values', h.constrValues(cons), 'and duals', h.constrDuals(cons)) print('Constraints have values', h.allConstrValues(), 'and duals', h.allConstrDuals()) -for var in vars: +for var in x.values(): print(f"Use {h.variableValue(var):{width}.{precision}} of {h.variableName(var)}") print(f"Optimal objective value is {h.getObjectiveValue():{width}.{precision}}") print() print('Solve as MIP') -for var in vars: +for var in x.values(): h.setInteger(var) h.solve() -for var in vars: +for var in x.values(): print(f"Use {h.variableValue(var):{width}.{precision}} of {h.variableName(var)}") print(f"Optimal objective value is {h.getObjectiveValue():{width}.{precision}}") @@ -79,14 +67,14 @@ print('Solve as LP with Gomory cut') # Make the variables continuous -for var in vars: +for var in x.values(): h.setContinuous(var) # Add Gomory cut -h.addConstr(useTypeA + useTypeB >= 4, name = "Gomory") +h.addConstr(x['TypeA'] + x['TypeB'] >= 4, name = "Gomory") h.solve() -for var in vars: +for var in x.values(): print(f"Use {h.variableValue(var):{width}.{precision}} of {h.variableName(var)}") print(f"Optimal objective value is {h.getObjectiveValue():{width}.{precision}}") diff --git a/examples/minimal.py b/examples/minimal.py index 943e231105..48cf840107 100644 --- a/examples/minimal.py +++ b/examples/minimal.py @@ -1,11 +1,40 @@ import highspy +import time +import networkx as nx +from random import randint h = highspy.Highs() -x1 = h.addVariable(lb = -h.inf) -x2 = h.addVariable(lb = -h.inf) +(x1, x2) = h.addVariables(2, lb = -h.inf) -h.addConstr(x2 - x1 >= 2) -h.addConstr(x1 + x2 >= 0) +h.addConstrs(x2 - x1 >= 2, + x1 + x2 >= 0) h.minimize(x2) + + + +h = highspy.Highs() + + +G = nx.circular_ladder_graph(5).to_directed() +nx.set_edge_attributes(G, {e: {'weight': randint(1, 9)} for e in G.edges}) + +d = h.addBinaries(G.edges, obj=nx.get_edge_attributes(G, 'weight')) + +h.addConstrs(sum(d[e] for e in G.in_edges(i)) - sum(d[e] for e in G.out_edges(i)) == 0 for i in G.nodes) + +h = highspy.Highs() + +ts = time.time() +perf1 = [h.addBinary() for _ in range(1000000)] +t1 = time.time() - ts +print(t1) + +h = highspy.Highs() + +ts = time.time() +perf2 = h.addVariables(1000000) +t2 = time.time() - ts +print(t2) + diff --git a/examples/network_flow.py b/examples/network_flow.py new file mode 100644 index 0000000000..2d01827423 --- /dev/null +++ b/examples/network_flow.py @@ -0,0 +1,37 @@ +# Example of a shortest path network flow in a graph +# Shows integration of highspy with networkx + +import highspy +import networkx as nx + +orig, dest = ('A', 'D') + +# create directed graph with edge weights (distances) +G = nx.DiGraph() +G.add_weighted_edges_from([('A', 'B', 2.0), ('B', 'C', 3.0), ('A', 'C', 1.5), ('B', 'D', 2.5), ('C', 'D', 1.0)]) + +h = highspy.Highs() +h.silent() + +x = h.addBinaries(G.edges, obj=nx.get_edge_attributes(G, 'weight')) + +# add flow conservation constraints +# { 1 if n = orig +# sum(out) - sum(in) = { -1 if n = dest +# { 0 otherwise +rhs = lambda n: 1 if n == orig else -1 if n == dest else 0 +flow = lambda E: sum((x[e] for e in E)) + +h.addConstrs(flow(G.out_edges(n)) - flow(G.in_edges(n)) == rhs(n) for n in G.nodes) +h.minimize() + +# Print the solution +print('Shortest path from', orig, 'to', dest, 'is: ', end = '') +sol = h.vals(x) + +n = orig +while n != dest: + print(n, end=' ') + n = next(e[1] for e in G.out_edges(n) if sol[e] > 0.5) + +print(dest) diff --git a/examples/nqueens.py b/examples/nqueens.py new file mode 100644 index 0000000000..84e07bb626 --- /dev/null +++ b/examples/nqueens.py @@ -0,0 +1,29 @@ +# This is an example of the N-Queens problem, which is a classic combinatorial problem. +# The problem is to place N queens on an N x N chessboard so that no two queens attack each other. +# +# We show how to model the problem as a MIP and solve it using highspy. +# Using numpy can simplify the construction of the constraints (i.e., diagonal). + +import highspy +import numpy as np + +N = 8 +h = highspy.Highs() +h.silent() + +x = np.reshape(h.addBinaries(N*N), (N, N)) + +h.addConstrs(sum(x[i,:]) == 1 for i in range(N)) # each row has exactly one queen +h.addConstrs(sum(x[:,j]) == 1 for j in range(N)) # each col has exactly one queen + +y = np.fliplr(x) +h.addConstrs(x.diagonal(k).sum() <= 1 for k in range(-N + 1, N)) # each diagonal has at most one queen +h.addConstrs(y.diagonal(k).sum() <= 1 for k in range(-N + 1, N)) # each 'reverse' diagonal has at most one queen + +h.solve() +sol = np.array(h.vals(x)) + +print('Queens:') + +for i in range(N): + print(''.join('Q' if sol[i, j] > 0.5 else '*' for j in range(N))) \ No newline at end of file diff --git a/src/highspy/highs.py b/src/highspy/highs.py index 9abf20ea26..4d4f4e05c2 100644 --- a/src/highspy/highs.py +++ b/src/highspy/highs.py @@ -32,32 +32,48 @@ kHighsIInf, ) - -from itertools import groupby +from collections.abc import Mapping +from itertools import groupby, product from operator import itemgetter from decimal import Decimal class Highs(_Highs): """HiGHS solver interface""" - __slots__ = ['_batch', '_vars', '_cons'] def __init__(self): super().__init__() - - self._batch = highs_batch(self) - self._vars = [] - self._cons = [] # Silence logging def silent(self): + """Disables solver output to the console.""" super().setOptionValue("output_flag", False) # solve def solve(self): + """Runs the solver on the current problem. + + Returns: + A HighsStatus object containing the solve status. + """ + return super().run() + + def optimize(self): + """Alias for the solve method.""" return super().run() # reset the objective and sense, then solve def minimize(self, obj=None): + """Solves a minimization of the objective and optionally updates the costs. + + Args: + obj: An optional highs_linear_expression representing the new objective function. + + Raises: + Exception: If obj is an inequality or not a highs_linear_expression. + + Returns: + A HighsStatus object containing the solve status after minimization. + """ if obj != None: # if we have a single variable, wrap it in a linear expression if isinstance(obj, highs_var) == True: @@ -67,7 +83,6 @@ def minimize(self, obj=None): raise Exception('Objective cannot be an inequality') # reset objective - self.update() super().changeColsCost(self.numVariables, range(self.numVariables), [0]*self.numVariables) # if we have duplicate variables, add the vals @@ -80,6 +95,17 @@ def minimize(self, obj=None): # reset the objective and sense, then solve def maximize(self, obj=None): + """Solves a maximization of the objective and optionally updates the costs. + + Args: + obj: An optional highs_linear_expression representing the new objective function. + + Raises: + Exception: If obj is an inequality or not a highs_linear_expression. + + Returns: + A HighsStatus object containing the solve status after maximization. + """ if obj != None: # if we have a single variable, wrap it in a linear expression if isinstance(obj, highs_var) == True: @@ -89,7 +115,6 @@ def maximize(self, obj=None): raise Exception('Objective cannot be an inequality') # reset objective - self.update() super().changeColsCost(self.numVariables, range(self.numVariables), [0]*self.numVariables) # if we have duplicate variables, add the vals @@ -100,31 +125,52 @@ def maximize(self, obj=None): super().changeObjectiveSense(ObjSense.kMaximize) return super().run() - - # update variables - def update(self): - current_batch_size = len(self._batch.obj) - if current_batch_size > 0: - names = [self._batch.name[i] for i in range(current_batch_size)] - super().addVars(int(current_batch_size), self._batch.lb, self._batch.ub) - super().changeColsCost(current_batch_size, self._batch.idx, self._batch.obj) - - # only set integrality if we have non-continuous variables - if any([t != HighsVarType.kContinuous for t in self._batch.type]): - super().changeColsIntegrality(current_batch_size, self._batch.idx, self._batch.type) - - for i in range(current_batch_size): - super().passColName(int(self._batch.idx[i]), str(names[i])) - self._batch = highs_batch(self) + def internal_get_value(self, var_index_collection, col_value): + """Internal method to get the value of a variable in the solution. Could be value or dual.""" + if isinstance(var_index_collection, int): + return col_value[var_index_collection] + elif isinstance(var_index_collection, highs_var): + return col_value[var_index_collection.index] + elif isinstance(var_index_collection, Mapping): + return {k: col_value[v.index] for k,v in var_index_collection.items()} + else: + return [col_value[v.index] for v in var_index_collection] def val(self, var): + """Gets the value of a variable in the solution. + + Args: + var: A highs_var object representing the variable. + + Returns: + The value of the variable in the solution. + """ return super().getSolution().col_value[var.index] def vals(self, vars): - sol = super().getSolution() - return [sol.col_value[v.index] for v in vars] + """Gets the values of multiple variables in the solution. + + Args: + vars: A collection of highs_var objects representing the variables. Can be a Mapping (e.g., dict) where keys are variable names and values are highs_var objects, or an iterable of highs_var objects. + + Returns: + If vars is a Mapping, returns a dict where keys are the same keys from the input vars and values are the solution values of the corresponding variables. If vars is an iterable, returns a list of solution values for the variables. + """ + col_value = super().getSolution().col_value + return {k: self.internal_get_value(v, col_value) for k,v in vars.items()} if isinstance(vars, Mapping) else [self.internal_get_value(v, col_value) for v in vars] def variableName(self, var): + """Retrieves the name of a specific variable. + + Args: + var: A highs_var object representing the variable. + + Raises: + Exception: If the variable name cannot be found. + + Returns: + The name of the specified variable. + """ [status, name] = super().getColName(var.index) failed = status != HighsStatus.kOk if failed: @@ -132,168 +178,474 @@ def variableName(self, var): return name def variableNames(self, vars): - names = list() - for v in vars: - [status, name] = super().getColName(v.index) - failed = status != HighsStatus.kOk - if failed: - raise Exception('Variable name not found') - names.append(name) - return names + """Retrieves the names of multiple variables. + + Args: + vars: An iterable of highs_var objects or a mapping where keys are identifiers and values are highs_var objects. + + Raises: + Exception: If any variable name cannot be found. + + Returns: + If vars is a mapping, returns a dict where keys are the same keys from the input vars and values are the names of the corresponding variables. + If vars is an iterable, returns a list of names for the specified variables. + """ + if isinstance(vars, Mapping): + return { key: v.name for key, v in vars.items() } + else: + return [v.name for v in vars] def allVariableNames(self): + """Retrieves the names of all variables in the model. + + Returns: + A list of strings representing the names of all variables. + """ return super().getLp().col_names_ def variableValue(self, var): + """Retrieves the value of a specific variable in the solution. + + Args: + var: A highs_var object representing the variable. + + Returns: + The value of the specified variable in the solution. + """ return super().getSolution().col_value[var.index] def variableValues(self, vars): - col_value = super().getSolution().col_value - return [col_value[v.index] for v in vars] + """Retrieves the values of multiple variables in the solution. + + Args: + vars: A collection of highs_var objects representing the variables. Can be a Mapping (e.g., dict) where keys are variable names and values are highs_var objects, or an iterable of highs_var objects. + + Returns: + If vars is a Mapping, returns a dict where keys are the same keys from the input vars and values are the solution values of the corresponding variables. If vars is an iterable, returns a list of solution values for the variables. + """ + return self.vals(vars) + def allVariableValues(self): + """Retrieves the values of all variables in the solution. + + Returns: + A list of values for all variables in the solution. + """ return super().getSolution().col_value def variableDual(self, var): + """Retrieves the dual value of a specific variable in the solution. + + Args: + var: A highs_var object representing the variable. + + Returns: + The dual value of the specified variable in the solution. + """ return super().getSolution().col_dual[var.index] def variableDuals(self, vars): + """Retrieves the dual values of multiple variables in the solution. + + Args: + vars: A collection of highs_var objects representing the variables. Can be a Mapping (e.g., dict) where keys are variable names and values are highs_var objects, or an iterable of highs_var objects. + + Returns: + If vars is a Mapping, returns a dict where keys are the same keys from the input vars and values are the dual values of the corresponding variables. If vars is an iterable, returns a list of dual values for the variables. + """ col_dual = super().getSolution() - return [col_dual[v.index] for v in vars] + return {k: self.internal_get_value(v, col_dual) for k,v in vars.items()} if isinstance(vars, Mapping) else [self.internal_get_value(v, col_dual) for v in vars] + def allVariableDuals(self): + """Retrieves the dual values of all variables in the solution. + + Returns: + A list of dual values for all variables in the solution. + """ return super().getSolution().col_dual - def constrValue(self, constr_name): - status_index = super().getRowByName(constr_name) - failed = status_index[0] != HighsStatus.kOk - if failed: - raise Exception('Constraint name not found') - return super().getSolution().row_value[status_index[1]] + def constrValue(self, con): + """Retrieves the value of a specific constraint in the solution. + + Args: + con: A highs_con object representing the constraint. + + Returns: + The value of the specified constraint in the solution. + """ + return super().getSolution().row_value[con.index] - def constrValues(self, constr_names): + def constrValues(self, cons): + """Retrieves the values of multiple constraints in the solution. + + Args: + cons: A collection of highs_con objects representing the constraints. Can be a Mapping (e.g., dict) where keys are constraint names and values are highs_con objects, or an iterable of highs_con objects. + + Returns: + If cons is a Mapping, returns a dict where keys are the same keys from the input cons and values are the solution values of the corresponding constraints. If cons is an iterable, returns a list of solution values for the constraints. + """ row_value = super().getSolution().row_value - index = list() - for name in constr_names: - status_index = super().getRowByName(name) - failed = status_index[0] != HighsStatus.kOk - if failed: - raise Exception('Constraint name not found') - index.append(status_index[1]) - return [row_value[index[v]] for v in range(len(index))] + return {k: row_value[c.index] for k,c in cons.items()} if isinstance(cons, Mapping) else [row_value[c.index] for c in cons] + def allConstrValues(self): + """Retrieves the values of all constraints in the solution. + + Returns: + A list of values for all constraints in the solution. + """ return super().getSolution().row_value + + def constrDual(self, con): + """Retrieves the dual value of a specific constraint in the solution. - def constrDual(self, constr_name): - status_index = super().getRowByName(constr_name) - failed = status_index[0] != HighsStatus.kOk - if failed: - raise Exception('Constraint name not found') - return super().getSolution().row_dual[status_index[1]] + Args: + con: A highs_con object representing the constraint. + + Returns: + The dual value of the specified constraint in the solution. + """ + return super().getSolution().row_dual[con.index] + + def constrDuals(self, cons): + """Retrieves the dual values of multiple constraints in the solution. - def constrDuals(self, constr_names): + Args: + cons: A collection of highs_con objects representing the constraints. Can be a Mapping (e.g., dict) where keys are constraint names and values are highs_con objects, or an iterable of highs_con objects. + + Returns: + If cons is a Mapping, returns a dict where keys are the same keys from the input cons and values are the dual values of the corresponding constraints. If cons is an iterable, returns a list of dual values for the constraints. + """ row_dual = super().getSolution().row_dual - index = list() - for name in constr_names: - status_index = super().getRowByName(name) - failed = status_index[0] != HighsStatus.kOk - if failed: - raise Exception('Constraint name not found') - index.append(status_index[1]) - return [row_dual[index[v]] for v in range(len(index))] + return {k: row_dual[c.index] for k,c in cons.items()} if isinstance(cons, Mapping) else [row_dual[c.index] for c in cons] def allConstrDuals(self): + """Retrieves the dual values of all constraints in the solution. + + Returns: + A list of dual values for all constraints in the solution. + """ return super().getSolution().row_dual - # - # add variable & useful constants - # - # Change the name of addVar to addVariable to prevent shadowing of - # highspy binding to Highs::addVar def addVariable(self, lb = 0, ub = kHighsInf, obj = 0, type=HighsVarType.kContinuous, name = None): - var = self._batch.add(obj, lb, ub, type, name, self) - self._vars.append(var) - # No longer acumulate a batch of variables so that addVariable - # behaves like Highs::addVar and highspy bindings modifying - # column data and adding rows can be used - self.update() + """Adds a variable to the model. + + Args: + lb: Lower bound of the variable (default is 0). + ub: Upper bound of the variable (default is infinity). + obj: Objective coefficient of the variable (default is 0). + type: Type of the variable (continuous, integer; default is continuous). + name: Optional name for the variable. + + Returns: + A highs_var object representing the added variable. + """ + status = super().addCol(obj, lb, ub, 0, [], []) + + if status != HighsStatus.kOk: + raise Exception("Failed to add variable to the model.") + + var = highs_var(self.numVariables - 1, self) + + if type != HighsVarType.kContinuous: + super().changeColIntegrality(var.index, type) + + if name != None: + super().passColName(var.index, name) + return var + def addVariables(self, *nvars, **kwargs): + """Adds multiple variables to the model. + + Args: + *args: A sequence of variables to be added. Can be a collection of scalars or indices (or mix). + + **kwargs: Optional keyword arguments. Can be scalars, arrays, or mappings. + lb: Lower bound of the variables (default is 0). + ub: Upper bound of the variables (default is infinity). + obj: Objective coefficient of the variables (default is 0). + type: Type of the variables (continuous, integer; default is continuous). + name: A collection of names for the variables (list or mapping). + name_prefix: Prefix for the variable names. Constructed name will be name_prefix + index. + out_array: Return an array of highs_var objects instead of a dictionary. + + Returns: + A highs_var collection (array or dictionary) representing the added variables. + """ + if len(nvars) == 0: + return None + + lb = kwargs.get('lb', 0) + ub = kwargs.get('ub', kHighsInf) + obj = kwargs.get('obj', 0) + vartype = kwargs.get('type', HighsVarType.kContinuous) + name_prefix = kwargs.get('name_prefix', None) + name = kwargs.get('name', None) + out_array = kwargs.get('out_array', len(nvars) == 1 and isinstance(nvars[0], int)) + + nvars = [range(n) if isinstance(n, int) else n for n in nvars] + indices = list(nvars[0] if len(nvars) == 1 else product(*nvars)) # unpack tuple if needed + N = len(indices) + + # parameter can be scalar, array, or mapping lookup (i.e., dictionary, custom class, etc.) + # scalar: repeat for all N, array: use as is, lookup: convert to array using indices + R = lambda x: [x[i] for i in indices] if isinstance(x, Mapping) else (x if hasattr(x, "__getitem__") else [x] * N) + + start_idx = self.numVariables + idx = range(start_idx, start_idx + N) + status = super().addCols(N, R(obj), R(lb), R(ub), 0, [], [], []) + + if status != HighsStatus.kOk: + raise Exception("Failed to add columns to the model.") + + # only set integrality if we have non-continuous variables + if vartype != HighsVarType.kContinuous: + super().changeColsIntegrality(N, idx, R(vartype)) + + if name or name_prefix: + names = name or [f"{name_prefix}{i}" for i in indices] + + for i,n in zip(idx, names): + super().passColName(int(i), str(n)) + + return [highs_var(i, self) for i in idx] if out_array == True else {index: highs_var(i, self) for index,i in zip(indices, idx)} + + def addIntegrals(self, *nvars, **kwargs): + """Alias for the addVariables method, for integer variables.""" + kwargs.setdefault('type', HighsVarType.kInteger) + return self.addVariables(*nvars, **kwargs) + + def addBinaries(self, *nvars, **kwargs): + """Alias for the addVariables method, for binary variables.""" + kwargs.setdefault('lb', 0) + kwargs.setdefault('ub', 1) + kwargs.setdefault('type', HighsVarType.kInteger) + + return self.addVariables(*nvars, **kwargs) + def addIntegral(self, lb = 0, ub = kHighsInf, obj = 0, name = None): + """Alias for the addVariable method, for integer variables.""" return self.addVariable(lb, ub, obj, HighsVarType.kInteger, name) def addBinary(self, obj = 0, name = None): + """Alias for the addVariable method, for binary variables.""" return self.addVariable(0, 1, obj, HighsVarType.kInteger, name) - # Change the name of removeVar to deleteVariable - def deleteVariable(self, var): - for i in self._vars[var.index+1:]: - i.index -= 1 - - del self._vars[var.index] - - # only delete from model if it exists - if var.index < self.numVariables: - super().deleteVars(1, [var.index]) + def deleteVariable(self, var_or_index, *args): + """Deletes a variable from the model and updates the indices of subsequent variables in provided collections. + + Args: + var_or_index: A highs_var object or an index representing the variable to be deleted. + *args: Optional collections (lists, dicts, etc.) of highs_var objects whose indices need to be updated. + """ + # Determine the index of the variable to delete + index = var_or_index.index if isinstance(var_or_index, highs_var) else var_or_index + + # Delete the variable from the model if it exists + if index < self.numVariables: + super().deleteVars(1, [index]) + + # Update the indices of variables in the provided collections + for collection in args: + if isinstance(collection, dict): + # Update indices in a dictionary of variables + for key, var in collection.items(): + if var.index > index: + var.index -= 1 + elif hasattr(collection, '__iter__'): + # Update indices in an iterable of variables + for var in collection: + if var.index > index: + var.index -= 1 + # If the collection is a single highs_var object, check and update if necessary + elif isinstance(collection, highs_var) and collection.index > index: + collection.index -= 1 - # Change the name of getVars to getVariables def getVariables(self): - return self._vars + """Retrieves all variables in the model. + + Returns: + A list of highs_var objects, each representing a variable in the model. + """ + return [highs_var(i, self) for i in range(self.numVariables)] @property def inf(self): + """Represents infinity in the context of the solver. + + Returns: + The value used to represent infinity. + """ return kHighsInf @property def numVariables(self): + """Gets the number of variables in the model. + + Returns: + The number of variables. + """ return super().getNumCol() @property def numConstrs(self): + """Gets the number of constraints in the model. + + Returns: + The number of constraints. + """ return super().getNumRow() # # add constraints # def addConstr(self, cons, name=None): - self.update() + """Adds a constraint to the model. + + Args: + cons: A highs_linear_expression to be added. + name: Optional name of the constraint. + Returns: + A highs_con object representing the added constraint. + """ # if we have duplicate variables, add the vals vars,vals = zip(*[(var, sum(v[1] for v in Vals)) for var, Vals in groupby(sorted(zip(cons.vars, cons.vals)), key=itemgetter(0))]) super().addRow(cons.LHS - cons.constant, cons.RHS - cons.constant, len(vars), vars, vals) + con = highs_cons(self.numConstrs - 1, self) + + if name != None: + super().passRowName(con.index, name) + + return con + + def addConstrs(self, *args, **kwargs): + """Adds multiple constraints to the model. + + Args: + *args: A sequence of highs_linear_expression to be added. + + **kwargs: Optional keyword arguments. + name_prefix: Prefix for the constraint names. Constructed name will be name_prefix + index. + name: A collection of names for the constraints (list or mapping). + + Returns: + A highs_con collection array representing the added constraints. + """ + name_prefix = kwargs.get('name_prefix', None) + name = kwargs.get('name', None) + generator = args + + # unpack generator if needed + if len(args) == 1 and hasattr(args[0], "__iter__") == True: + generator = args[0] + + lower = [] + upper = [] + starts = [0] + indices = [] + values = [] + nnz = 0; + + for cons in generator: + # if we have duplicate variables, add the vals together + vars,vals = zip(*[(var, sum(v[1] for v in Vals)) for var, Vals in groupby(sorted(zip(cons.vars, cons.vals)), key=itemgetter(0))]) + + indices.extend(vars) + values.extend(vals) + nnz += len(vars) + + lower.append(cons.LHS - cons.constant) + upper.append(cons.RHS - cons.constant) + starts.append(nnz) + + new_rows = len(lower) + super().addRows(new_rows, lower, upper, nnz, starts, indices, values); + cons = [highs_cons(self.numConstrs - new_rows + n, self) for n in range(new_rows)] + + if name or name_prefix: + names = name or [f"{name_prefix}{n}" for n in range(new_rows)] + + for c,n in zip(cons, names): + super().passRowName(int(c.index), str(n)) - cons = highs_cons(self.numConstrs - 1, self, name) - self._cons.append(cons) return cons + def chgCoeff(self, cons, var, val): + """Changes the coefficient of a variable in a constraint. + + Args: + cons: A highs_con object representing the constraint. + var: A highs_var object representing the variable. + val: The new coefficient value for the variable in the constraint. + """ super().changeCoeff(cons.index, var.index, val) def getConstrs(self): - return self._cons - - def removeConstr(self, cons): - for i in self._cons[cons.index+1:]: - i.index -= 1 + """Retrieves all constraints in the model. + + Returns: + A list of highs_cons objects, each representing a constraint in the model. + """ + return [highs_cons(i, self) for i in range(self.numConstrs)] + + def removeConstr(self, cons_or_index, *args): + """Removes a constraint from the model and updates the indices of subsequent constraints in provided collections. + + Args: + cons_or_index: A highs_cons object or an index representing the constraint to be removed. + *args: Optional collections (lists, dicts, etc.) of highs_cons objects whose indices need to be updated after the removal. + """ + # Determine the index of the constraint to delete + index = cons_or_index.index if isinstance(cons_or_index, highs_cons) else cons_or_index + + # Delete the variable from the model if it exists + if index < self.numConstrs: + super().deleteRows(1, [index]) + + # Update the indices of constraints in the provided collections + for collection in args: + if isinstance(collection, dict): + # Update indices in a dictionary of constraints + for key, con in collection.items(): + if con.index > index: + con.index -= 1 + elif hasattr(collection, '__iter__'): + # Update indices in an iterable of constraints + for con in collection: + if con.index > index: + con.index -= 1 + # If the collection is a single highs_cons object, check and update if necessary + elif isinstance(collection, highs_cons) and collection.index > index: + collection.index -= 1 - del self._cons[cons.index] - super().deleteRows(1, [cons.index]) - # set to minimization def setMinimize(self): + """Sets the objective sense of the model to minimization.""" super().changeObjectiveSense(ObjSense.kMinimize) - # set to maximization def setMaximize(self): + """Sets the objective sense of the model to maximization.""" super().changeObjectiveSense(ObjSense.kMaximize) - # Set to integer def setInteger(self, var): + """Sets a variable's type to integer. + + Args: + var: A highs_var object representing the variable to be set as integer. + """ super().changeColIntegrality(var.index, HighsVarType.kInteger) - # Set to continuous def setContinuous(self, var): + """Sets a variable's type to continuous. + + Args: + var: A highs_var object representing the variable to be set as continuous. + """ super().changeColIntegrality(var.index, HighsVarType.kContinuous) ## The following classes keep track of variables @@ -302,31 +654,26 @@ def setContinuous(self, var): # highs variable class highs_var(object): """Basic constraint builder for HiGHS""" - __slots__ = ['index', '_variableName', 'highs'] + __slots__ = ['index', 'highs'] - def __init__(self, i, highs, name=None): + def __init__(self, i, highs): self.index = i self.highs = highs - self.name = f"__v{i}" if name == None else name def __repr__(self): - return f"{self.name}" + return f"highs_var({self.index})" @property def name(self): - if self.index < self.highs.numVariables and self.highs.numVariables > 0: - return self.highs.getLp().col_names_[self.index] - else: - return self._variableName + status, name = self.highs.getColName(self.index) + + if status != HighsStatus.kOk: + raise Exception("Error retrieving variable name.") + return name @name.setter def name(self, value): - if value == None or len(value) == 0: - raise Exception('Name cannot be empty') - - self._variableName = value - if self.index < self.highs.numVariables and self.highs.numVariables > 0: - self.highs.passColName(self.index, self._variableName) + self.highs.passColName(self.index, value) def __hash__(self): return self.index @@ -364,27 +711,26 @@ def __sub__(self, other): # highs constraint class highs_cons(object): """Basic constraint for HiGHS""" - __slots__ = ['index', '_constrName', 'highs'] + __slots__ = ['index', 'highs'] - def __init__(self, i, highs, name): + def __init__(self, i, highs): self.index = i self.highs = highs - self.name = f"__c{i}" if name == None else name def __repr__(self): - return f"{self.name}" + return f"highs_cons({self.index})" @property def name(self): - return self._constrName + status, name = self.highs.getRowName(self.index) + + if status != HighsStatus.kOk: + raise Exception("Error retrieving constraint name.") + return name @name.setter def name(self, value): - if value == None or len(value) == 0: - raise Exception('Name cannot be empty') - - self._constrName = value - self.highs.passRowName(self.index, self._constrName) + self.highs.passRowName(self.index, value) # highs constraint builder @@ -467,7 +813,6 @@ def __eq__(self, other): # (other.LHS <= other <= other.RHS) <= (LHS <= self <= RHS) def __ge__(self, other): - if isinstance(other, highs_linear_expression): return other <= self @@ -533,29 +878,3 @@ def __sub__(self, other): return self + (-1.0 * other) else: return NotImplemented - -# used to batch add new variables -class highs_batch(object): - """Batch constraint builder for HiGHS""" - __slots__ = ['obj', 'lb', 'ub', 'type', 'name', 'highs', 'idx'] - - def __init__(self, highs): - self.highs = highs - - self.obj = [] - self.lb = [] - self.ub = [] - self.type = [] - self.idx = [] - self.name = [] - - def add(self, obj, lb, ub, type, name, solver): - self.obj.append(obj) - self.lb.append(lb) - self.ub.append(ub) - self.type.append(type) - self.name.append(name) - - newIndex = self.highs.numVariables + len(self.obj)-1 - self.idx.append(newIndex) - return highs_var(newIndex, solver, name) diff --git a/tests/test_highspy.py b/tests/test_highspy.py index 7991f91fd6..8d8a27174f 100644 --- a/tests/test_highspy.py +++ b/tests/test_highspy.py @@ -5,7 +5,6 @@ from io import StringIO from sys import platform - class TestHighsPy(unittest.TestCase): def get_basic_model(self): """ @@ -54,33 +53,33 @@ def get_example_model(self): h.passModel(lp) return h - # def test_example_model_builder(self): - # """ - # minimize f = x0 + x1 - # subject to x1 <= 7 - # 5 <= x0 + 2x1 <= 15 - # 6 <= 3x0 + 2x1 - # 0 <= x0 <= 4; 1 <= x1 - # """ - # h = highspy.Highs() - - # x0 = h.addVariable(lb=0, ub=4, obj=1) - # x1 = h.addVariable(lb=1, ub=7, obj=1) - - # h.addConstr(5 <= x0 + 2*x1 <= 15) - # h.addConstr(6 <= 3*x0 + 2*x1) - - # lp = h.getLp() - - # self.assertEqual(lp.num_col_, 2) - # self.assertEqual(lp.num_row_, 2) - # self.assertAlmostEqual(lp.col_cost_[0], 1) - # self.assertAlmostEqual(lp.col_lower_[0], 0) - # self.assertAlmostEqual(lp.col_upper_[0], 4) - # self.assertAlmostEqual(lp.row_lower_[0], 5) - # self.assertAlmostEqual(lp.row_upper_[0], 15) - # self.assertAlmostEqual(lp.row_lower_[1], 6) - # self.assertAlmostEqual(lp.row_upper_[1], highspy.kHighsInf) + def test_example_model_builder(self): + """ + minimize f = x0 + x1 + subject to x1 <= 7 + 5 <= x0 + 2x1 <= 15 + 6 <= 3x0 + 2x1 + 0 <= x0 <= 4; 1 <= x1 + """ + h = highspy.Highs() + + x0 = h.addVariable(lb=0, ub=4, obj=1) + x1 = h.addVariable(lb=1, ub=7, obj=1) + + h.addConstr(5 <= x0 + 2*x1 <= 15) + h.addConstr(6 <= 3*x0 + 2*x1) + + lp = h.getLp() + + self.assertEqual(lp.num_col_, 2) + self.assertEqual(lp.num_row_, 2) + self.assertAlmostEqual(lp.col_cost_[0], 1) + self.assertAlmostEqual(lp.col_lower_[0], 0) + self.assertAlmostEqual(lp.col_upper_[0], 4) + self.assertAlmostEqual(lp.row_lower_[0], 5) + self.assertAlmostEqual(lp.row_upper_[0], 15) + self.assertAlmostEqual(lp.row_lower_[1], 6) + self.assertAlmostEqual(lp.row_upper_[1], highspy.kHighsInf) def get_infeasible_model(self): inf = highspy.kHighsInf @@ -462,91 +461,88 @@ def test_infeasible_model(self): status = h.getModelStatus() self.assertEqual(status, highspy.HighsModelStatus.kInfeasible) - # failing? - - # def test_basics_builder(self): - # h = highspy.Highs() - # h.setOptionValue('output_flag', False) - - # x = h.addVariable(lb=highspy.kHighsInf) - # y = h.addVariable(lb=highspy.kHighsInf) - - # c1 = h.addConstr(-x + y >= 2) - # c2 = h.addConstr(x + y >= 0) - - # h.minimize(y) - - # self.assertAlmostEqual(h.val(x), -1) - # self.assertAlmostEqual(h.val(y), 1) - - # """ - # min y - # s.t. - # -x + y >= 3 - # x + y >= 0 - # """ - # h.changeRowBounds(0, 3, highspy.kHighsInf) - # h.run() - - # self.assertAlmostEqual(h.val(x), -1.5) - # self.assertAlmostEqual(h.val(y), 1.5) - - # # now make y integer - # h.changeColsIntegrality(1, np.array([1]), np.array([highspy.HighsVarType.kInteger])) - # h.run() - # sol = h.getSolution() - # self.assertAlmostEqual(sol.col_value[0], -1) - # self.assertAlmostEqual(sol.col_value[1], 2) - - # """ - # now delete the first constraint and add a new one + def test_basics_builder(self): + h = highspy.Highs() + h.setOptionValue('output_flag', False) + + x = h.addVariable(lb=-highspy.kHighsInf) + y = h.addVariable(lb=-highspy.kHighsInf) + + c1 = h.addConstr(-x + y >= 2) + c2 = h.addConstr(x + y >= 0) + + h.minimize(y) + + self.assertAlmostEqual(h.val(x), -1) + self.assertAlmostEqual(h.val(y), 1) + + """ + min y + s.t. + -x + y >= 3 + x + y >= 0 + """ + h.changeRowBounds(0, 3, highspy.kHighsInf) + h.run() + + self.assertAlmostEqual(h.val(x), -1.5) + self.assertAlmostEqual(h.val(y), 1.5) + + # now make y integer + h.changeColsIntegrality(1, np.array([1]), np.array([highspy.HighsVarType.kInteger])) + h.run() + sol = h.getSolution() + self.assertAlmostEqual(sol.col_value[0], -1) + self.assertAlmostEqual(sol.col_value[1], 2) + + """ + now delete the first constraint and add a new one - # min y - # s.t. - # x + y >= 0 - # -x + y >= 0 - # """ - # h.removeConstr(c1) + min y + s.t. + x + y >= 0 + -x + y >= 0 + """ + h.removeConstr(c1) - # c1 = h.addConstr(-x + y >= 0) + c1 = h.addConstr(-x + y >= 0) - # h.run() + h.run() - # self.assertAlmostEqual(h.val(x), 0) - # self.assertAlmostEqual(h.val(y), 0) + self.assertAlmostEqual(h.val(x), 0) + self.assertAlmostEqual(h.val(y), 0) - # # change the upper bound of x to -5 - # h.changeColsBounds(1, np.array([0]), np.array([-highspy.kHighsInf], dtype=np.double), - # np.array([-5], dtype=np.double)) - # h.run() - # self.assertAlmostEqual(h.val(x), -5) - # self.assertAlmostEqual(h.val(y), 5) + # change the upper bound of x to -5 + h.changeColsBounds(1, np.array([0]), np.array([-highspy.kHighsInf], dtype=np.double), + np.array([-5], dtype=np.double)) + h.run() + self.assertAlmostEqual(h.val(x), -5) + self.assertAlmostEqual(h.val(y), 5) - # # now maximize - # h.changeRowBounds(1, -highspy.kHighsInf, 0) - # h.changeRowBounds(0, -highspy.kHighsInf, 0) - # h.minimize(-y) + # now maximize + h.changeRowBounds(1, -highspy.kHighsInf, 0) + h.changeRowBounds(0, -highspy.kHighsInf, 0) + h.minimize(-y) - # self.assertAlmostEqual(h.val(x), -5) - # self.assertAlmostEqual(h.val(y), -5) + self.assertAlmostEqual(h.val(x), -5) + self.assertAlmostEqual(h.val(y), -5) - # self.assertEqual(h.getObjectiveSense()[1], highspy.ObjSense.kMinimize) - # h.maximize(y) - # self.assertEqual(h.getObjectiveSense()[1], highspy.ObjSense.kMaximize) + self.assertEqual(h.getObjectiveSense()[1], highspy.ObjSense.kMinimize) + h.maximize(y) + self.assertEqual(h.getObjectiveSense()[1], highspy.ObjSense.kMaximize) - # self.assertAlmostEqual(h.val(x), -5) - # self.assertAlmostEqual(h.val(y), -5) + self.assertAlmostEqual(h.val(x), -5) + self.assertAlmostEqual(h.val(y), -5) - # self.assertAlmostEqual(h.getObjectiveValue(), -5) + self.assertAlmostEqual(h.getObjectiveValue(), -5) - # h.maximize(y + 1) - # self.assertAlmostEqual(h.getObjectiveOffset()[1], 1) - # self.assertAlmostEqual(h.getObjectiveValue(), -4) + h.maximize(y + 1) + self.assertAlmostEqual(h.getObjectiveOffset()[1], 1) + self.assertAlmostEqual(h.getObjectiveValue(), -4) def test_addVariable(self): h = highspy.Highs() h.addVariable() - h.update() self.assertEqual(h.numVariables, 1) def test_addConstr(self): @@ -606,7 +602,6 @@ def test_var_name(self): self.assertEqual(x.name, 'y') # add to the model - h.update() self.assertEqual(h.numVariables, 1) self.assertEqual(h.getLp().col_names_[0], 'y') @@ -618,13 +613,7 @@ def test_var_name(self): h.passColName(0, 'a') self.assertEqual(h.getLp().col_names_[0], 'a') self.assertEqual(x.name, 'a') - - # change name to none or empty string - def change_name(n): - x.name = n - self.assertRaises(Exception, change_name, None) - self.assertRaises(Exception, change_name, '') def test_binary(self): h = highspy.Highs() @@ -756,6 +745,131 @@ def test_constraint_builder(self): h.addConstr(c1) self.assertAlmostEqual((h.getLp().row_lower_[0], h.getLp().row_upper_[0]), (4.5, 4.5)) + def test_add_multiple_variables(self): + # test basic functionality + h = highspy.Highs() + x = h.addVariables(2) + self.assertEqual(h.numVariables, 2) + + # test multiple dimensions + h = highspy.Highs() + x = h.addVariables(2,3,4) + self.assertEqual(h.numVariables, 2*3*4) + self.assertEqual(isinstance(x, dict), True) + + # test multiple dimensions array + h = highspy.Highs() + x = h.addVariables(2,3,4, out_array=True) + self.assertEqual(h.numVariables, 2*3*4) + self.assertEqual(isinstance(x, list), True) + + # test binary variables with objective and names + h = highspy.Highs() + x = h.addBinaries(20, obj=range(20), name_prefix='t_') + self.assertEqual(h.numVariables, 20) + self.assertEqual(h.getLp().col_names_[0], 't_0') + self.assertEqual(h.getLp().col_names_[19], 't_19') + self.assertEqual(h.getLp().col_cost_[0], 0) + self.assertEqual(h.getLp().col_cost_[19], 19) + + # test prefix item with indices, not variable offset + h = highspy.Highs() + x = h.addVariables('a','b','c', name_prefix='t_') # ('a', 'b', 'c') + self.assertEqual(h.numVariables, 1) + self.assertEqual(x['a','b','c'].name, "t_('a', 'b', 'c')") + + # Testing different ways of adding variables + # Some are unlikely to be used, but this is expected behaviour + N = 0 + h = highspy.Highs() + x1 = h.addVariables(('a','b','c'), obj={ 'b': 20, 'c': 10, 'a': 50 }) + N += 3 + self.assertEqual(h.numVariables, N) + lp = h.getLp() + self.assertEqual(lp.col_cost_[0], 50) + self.assertEqual(lp.col_cost_[1], 20) + self.assertEqual(lp.col_cost_[2], 10) + + x2 = h.addVariables(['a','b','c','d']) # 'a', 'b', 'c', 'd' + N += 4 + self.assertEqual(h.numVariables, N) + + x3 = h.addVariables('abc') # 'a', 'b', 'c' + N += 3 + self.assertEqual(h.numVariables, N) + + x4 = h.addVariables(('ab','b','c','d')) # 'ab', 'b', 'c', 'd' + N += 4 + self.assertEqual(h.numVariables, N) + + x5 = h.addVariables('ab','b','c','d') # ('a', 'b', 'c', 'd'), ('b', 'b', 'c', 'd') + N += 2 + self.assertEqual(h.numVariables, N) + self.assertTrue(('a', 'b', 'c', 'd') in x5.keys()) + self.assertTrue(('b', 'b', 'c', 'd') in x5.keys()) + + x6 = h.addVariables(5, 'a', 2, 'b', 'c') # range(5), 'a', range(2), 'b', 'c' + N += 5*2 + self.assertEqual(h.numVariables, N) + + x7 = h.addVariables([5, 'a', 2, 'b', 'c']) # 5, 'a', 2, 'b', 'c' + N += 5 + self.assertEqual(h.numVariables, N) + + x8 = h.addVariables([(20, 1), (1,2), (2,6)], ub=[3,2,1], name_prefix='t') # (20, 1), (1,2), (2,6) + N += 3 + self.assertEqual(h.numVariables, N) + + x9 = h.addBinaries((20, 1), (1,2), (2,6)) # product((20, 1), (1,2), (2,6))) = (20, 1, 2), ..., (1, 2, 6) + N += 8 + self.assertEqual(h.numVariables, N) + + def test_add_single_constraints(self): + h = highspy.Highs() + (x,y) = h.addVariables(2) + added_constraints = h.addConstrs([2*x + 3*y <= 5]) + self.assertEqual(len(added_constraints), 1) + self.assertEqual(h.numConstrs, 1) + + def test_add_multiple_constraints(self): + # test manual constraints + h = highspy.Highs() + (x,y,z) = h.addVariables(3) + added_constraints = h.addConstrs([ + x + y <= 5, + 2*x + 3*y >= 10, + x - z == 2]) + self.assertEqual(len(added_constraints), 3) + self.assertEqual(h.numConstrs, 3) + + # test list comprehension constraints + h = highspy.Highs() + x = h.addVariables(5) + h.addConstr(sum(x) == 1) + self.assertEqual(h.numConstrs, 1) + + h.addConstrs(x[i] + x[j] <= 1 for i in range(5) for j in range(5)) + self.assertEqual(h.numConstrs, 26) + + # test names and sequence types (list, tuple, nothing) + h = highspy.Highs() + (x1, x2, x3) = h.addVariables(3) + + h.addConstrs((x2 - x1 >= 2), name_prefix='a') + self.assertEqual(h.numConstrs, 1) + h.addConstrs((x2 - x1 >= 2)) + self.assertEqual(h.numConstrs, 2) + + h.addConstrs(x2 - x1 >= 2, name_prefix='b') + self.assertEqual(h.numConstrs, 3) + h.addConstrs(x2 - x1 >= 2) + self.assertEqual(h.numConstrs, 4) + + h.addConstrs([x2 - x1 >= 2], name_prefix='c') + self.assertEqual(h.numConstrs, 5) + h.addConstrs([x2 - x1 >= 2]) + self.assertEqual(h.numConstrs, 6) + # r/w basis tests below works on unix but not windows? def test_write_basis_before_running(self): From 66f4892f3212ba09189a72435a742679a098307d Mon Sep 17 00:00:00 2001 From: Luke Marshall <52978038+mathgeekcoder@users.noreply.github.com> Date: Tue, 23 Jul 2024 12:14:33 -0700 Subject: [PATCH 055/194] Update minimal.py Updated minimal.py (removed test code) --- examples/minimal.py | 33 ++------------------------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/examples/minimal.py b/examples/minimal.py index 48cf840107..d4c55e4774 100644 --- a/examples/minimal.py +++ b/examples/minimal.py @@ -1,40 +1,11 @@ import highspy -import time -import networkx as nx -from random import randint h = highspy.Highs() -(x1, x2) = h.addVariables(2, lb = -h.inf) +x1 = h.addVariable(lb = -h.inf) +x2 = h.addVariable(lb = -h.inf) h.addConstrs(x2 - x1 >= 2, x1 + x2 >= 0) h.minimize(x2) - - - -h = highspy.Highs() - - -G = nx.circular_ladder_graph(5).to_directed() -nx.set_edge_attributes(G, {e: {'weight': randint(1, 9)} for e in G.edges}) - -d = h.addBinaries(G.edges, obj=nx.get_edge_attributes(G, 'weight')) - -h.addConstrs(sum(d[e] for e in G.in_edges(i)) - sum(d[e] for e in G.out_edges(i)) == 0 for i in G.nodes) - -h = highspy.Highs() - -ts = time.time() -perf1 = [h.addBinary() for _ in range(1000000)] -t1 = time.time() - ts -print(t1) - -h = highspy.Highs() - -ts = time.time() -perf2 = h.addVariables(1000000) -t2 = time.time() - ts -print(t2) - From 76a7e766ad0d5568b3751925225832ba6768567e Mon Sep 17 00:00:00 2001 From: jajhall Date: Fri, 26 Jul 2024 22:57:09 +0100 Subject: [PATCH 056/194] Added options write_presolved_model_to_file and write_presolved_model_file, and writes out OK unless file name is empty --- app/RunHighs.cpp | 18 ++++++++++++++++++ src/lp_data/HighsOptions.h | 15 +++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/app/RunHighs.cpp b/app/RunHighs.cpp index 183bb917c8..d5ceafb835 100644 --- a/app/RunHighs.cpp +++ b/app/RunHighs.cpp @@ -62,6 +62,24 @@ int main(int argc, char** argv) { return (int)read_solution_status; } } + if (options.write_presolved_model_to_file) { + // Run presolve and write the presolved model to a file + HighsStatus status = highs.presolve(); + if (status == HighsStatus::kError) return int(status); + HighsPresolveStatus model_presolve_status = highs.getModelPresolveStatus(); + const bool ok_to_write = + model_presolve_status == HighsPresolveStatus::kNotReduced || + model_presolve_status == HighsPresolveStatus::kReduced || + model_presolve_status == HighsPresolveStatus::kReducedToEmpty || + model_presolve_status == HighsPresolveStatus::kTimeout; + if (!ok_to_write) { + highsLogUser(log_options, HighsLogType::kInfo, + "No presolved model to write to file\n"); + return int(status); + } + status = highs.writePresolvedModel(options.write_presolved_model_file); + return int(status); + } // Solve the model HighsStatus run_status = highs.run(); if (run_status == HighsStatus::kError) return int(run_status); diff --git a/src/lp_data/HighsOptions.h b/src/lp_data/HighsOptions.h index 7c939583ae..6b4a6ad840 100644 --- a/src/lp_data/HighsOptions.h +++ b/src/lp_data/HighsOptions.h @@ -276,6 +276,7 @@ const string kSolutionFileString = "solution_file"; const string kRangingString = "ranging"; const string kVersionString = "version"; const string kWriteModelFileString = "write_model_file"; +const string kWritePresolvedModelFileString = "write_presolved_model_file"; const string kReadSolutionFileString = "read_solution_file"; // String for HiGHS log file option @@ -320,9 +321,11 @@ struct HighsOptionsStruct { std::string log_file; bool write_model_to_file; + bool write_presolved_model_to_file; bool write_solution_to_file; HighsInt write_solution_style; HighsInt glpsol_cost_row_location; + std::string write_presolved_model_file; // Control of HiGHS log bool output_flag; @@ -434,6 +437,7 @@ struct HighsOptionsStruct { time_limit(0.0), solution_file(""), write_model_file(""), + write_presolved_model_file(""), random_seed(0), ranging(""), infinite_cost(0.0), @@ -461,6 +465,7 @@ struct HighsOptionsStruct { simplex_max_concurrency(0), log_file(""), write_model_to_file(false), + write_presolved_model_to_file(false), write_solution_to_file(false), write_solution_style(0), glpsol_cost_row_location(0), @@ -898,6 +903,16 @@ class HighsOptions : public HighsOptionsStruct { advanced, &write_model_to_file, false); records.push_back(record_bool); + record_string = new OptionRecordString( + kWritePresolvedModelFileString, "Write presolved model file", advanced, + &write_presolved_model_file, kHighsFilenameDefault); + records.push_back(record_string); + + record_bool = new OptionRecordBool( + "write_presolved_model_to_file", "Write the presolved model to a file", + advanced, &write_presolved_model_to_file, false); + records.push_back(record_bool); + record_bool = new OptionRecordBool( "mip_detect_symmetry", "Whether MIP symmetry should be detected", advanced, &mip_detect_symmetry, true); From ce2da3868e5f6e90001ecf497d7a95cd6ae40f5f Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 31 Jul 2024 00:30:17 +0100 Subject: [PATCH 057/194] Highs::reportModel now uses const HighsModel& model argument so it can report the presolved LP --- src/Highs.h | 2 +- src/lp_data/Highs.cpp | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Highs.h b/src/Highs.h index f104d72f1d..6a87f6e5e6 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -1374,7 +1374,7 @@ class Highs { HighsStatus openWriteFile(const string filename, const string method_name, FILE*& file, HighsFileType& file_type) const; - void reportModel(); + void reportModel(const HighsModel& model); void newHighsBasis(); void forceHighsSolutionBasisSize(); // diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index acf2780b39..82f068171a 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -710,7 +710,7 @@ HighsStatus Highs::writeLocalModel(HighsModel& model, } if (filename == "") { // Empty file name: report model on logging stream - reportModel(); + reportModel(model); return_status = HighsStatus::kOk; } else { Filereader* writer = @@ -4019,13 +4019,13 @@ void Highs::logHeader() { return; } -void Highs::reportModel() { - reportLp(options_.log_options, model_.lp_, HighsLogType::kVerbose); - if (model_.hessian_.dim_) { - const HighsInt dim = model_.hessian_.dim_; - reportHessian(options_.log_options, dim, model_.hessian_.start_[dim], - model_.hessian_.start_.data(), model_.hessian_.index_.data(), - model_.hessian_.value_.data()); +void Highs::reportModel(const HighsModel& model) { + reportLp(options_.log_options, model.lp_, HighsLogType::kVerbose); + if (model.hessian_.dim_) { + const HighsInt dim = model.hessian_.dim_; + reportHessian(options_.log_options, dim, model.hessian_.start_[dim], + model.hessian_.start_.data(), model.hessian_.index_.data(), + model.hessian_.value_.data()); } } From 0025d67e4e845f3b8968b9d4dec09188b57f4a09 Mon Sep 17 00:00:00 2001 From: Niels Wouda Date: Tue, 6 Aug 2024 22:05:44 +0200 Subject: [PATCH 058/194] Add type stub file --- pyproject.toml | 1 + src/highspy/_core.pyi | 554 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 555 insertions(+) create mode 100644 src/highspy/_core.pyi diff --git a/pyproject.toml b/pyproject.toml index e3173dfedf..94f2d5bbc6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,7 @@ wheel.packages = ["src/highspy"] # gitignore syntax. sdist.include = [ "src/highspy/highs.py", + "src/highspy/_core.pyi", "tests/test_highspy.py", "Version.txt", "LICENSE", diff --git a/src/highspy/_core.pyi b/src/highspy/_core.pyi new file mode 100644 index 0000000000..a3a6fcea9f --- /dev/null +++ b/src/highspy/_core.pyi @@ -0,0 +1,554 @@ +import numpy +from typing import Callable, ClassVar, overload + +HIGHS_VERSION_MAJOR: int +HIGHS_VERSION_MINOR: int +HIGHS_VERSION_PATCH: int +kBasisValidityInvalid: BasisValidity +kBasisValidityValid: BasisValidity +kHighsIInf: int +kHighsInf: float +kSolutionStatusFeasible: SolutionStatus +kSolutionStatusInfeasible: SolutionStatus +kSolutionStatusNone: SolutionStatus + +class BasisValidity: + __members__: ClassVar[dict] = ... # read-only + __entries: ClassVar[dict] = ... + kBasisValidityInvalid: ClassVar[BasisValidity] = ... + kBasisValidityValid: ClassVar[BasisValidity] = ... + def __init__(self, value: int) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __index__(self) -> int: ... + def __int__(self) -> int: ... + def __ne__(self, other: object) -> bool: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + +class HessianFormat: + __members__: ClassVar[dict] = ... # read-only + __entries: ClassVar[dict] = ... + kSquare: ClassVar[HessianFormat] = ... + kTriangular: ClassVar[HessianFormat] = ... + def __init__(self, value: int) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __index__(self) -> int: ... + def __int__(self) -> int: ... + def __ne__(self, other: object) -> bool: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + +class HighsBasis: + alien: bool + col_status: list[HighsBasisStatus] + debug_id: int + debug_origin_name: str + debug_update_count: int + row_status: list[HighsBasisStatus] + valid: bool + was_alien: bool + def __init__(self) -> None: ... + +class HighsBasisStatus: + __members__: ClassVar[dict] = ... # read-only + __entries: ClassVar[dict] = ... + kBasic: ClassVar[HighsBasisStatus] = ... + kLower: ClassVar[HighsBasisStatus] = ... + kNonbasic: ClassVar[HighsBasisStatus] = ... + kUpper: ClassVar[HighsBasisStatus] = ... + kZero: ClassVar[HighsBasisStatus] = ... + def __init__(self, value: int) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __index__(self) -> int: ... + def __int__(self) -> int: ... + def __ne__(self, other: object) -> bool: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + +class HighsHessian: + dim_: int + format_: HessianFormat + index_: list[int] + start_: list[int] + value_: list[float] + def __init__(self) -> None: ... + +class HighsInfo: + basis_validity: int + crossover_iteration_count: int + dual_solution_status: int + ipm_iteration_count: int + max_complementarity_violation: float + max_dual_infeasibility: float + max_integrality_violation: float + max_primal_infeasibility: float + mip_dual_bound: float + mip_gap: float + mip_node_count: int + num_dual_infeasibilities: int + num_primal_infeasibilities: int + objective_function_value: float + pdlp_iteration_count: int + primal_solution_status: int + qp_iteration_count: int + simplex_iteration_count: int + sum_complementarity_violations: float + sum_dual_infeasibilities: float + sum_primal_infeasibilities: float + valid: bool + def __init__(self) -> None: ... + +class HighsInfoType: + __members__: ClassVar[dict] = ... # read-only + __entries: ClassVar[dict] = ... + kDouble: ClassVar[HighsInfoType] = ... + kInt: ClassVar[HighsInfoType] = ... + kInt64: ClassVar[HighsInfoType] = ... + def __init__(self, value: int) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __index__(self) -> int: ... + def __int__(self) -> int: ... + def __ne__(self, other: object) -> bool: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + +class HighsLogType: + __members__: ClassVar[dict] = ... # read-only + __entries: ClassVar[dict] = ... + kDetailed: ClassVar[HighsLogType] = ... + kError: ClassVar[HighsLogType] = ... + kInfo: ClassVar[HighsLogType] = ... + kVerbose: ClassVar[HighsLogType] = ... + kWarning: ClassVar[HighsLogType] = ... + def __init__(self, value: int) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __index__(self) -> int: ... + def __int__(self) -> int: ... + def __ne__(self, other: object) -> bool: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + +class HighsLp: + a_matrix_: HighsSparseMatrix + col_cost_: list[float] + col_lower_: list[float] + col_names_: list[str] + col_upper_: list[float] + integrality_: list[HighsVarType] + is_moved_: bool + is_scaled_: bool + model_name_: str + mods_: HighsLpMods + num_col_: int + num_row_: int + offset_: float + row_lower_: list[float] + row_names_: list[str] + row_upper_: list[float] + scale_: HighsScale + sense_: ObjSense + def __init__(self) -> None: ... + +class HighsModel: + hessian_: HighsHessian + lp_: HighsLp + def __init__(self) -> None: ... + +class HighsModelStatus: + __members__: ClassVar[dict] = ... # read-only + __entries: ClassVar[dict] = ... + kInfeasible: ClassVar[HighsModelStatus] = ... + kInterrupt: ClassVar[HighsModelStatus] = ... + kIterationLimit: ClassVar[HighsModelStatus] = ... + kLoadError: ClassVar[HighsModelStatus] = ... + kMemoryLimit: ClassVar[HighsModelStatus] = ... + kModelEmpty: ClassVar[HighsModelStatus] = ... + kModelError: ClassVar[HighsModelStatus] = ... + kNotset: ClassVar[HighsModelStatus] = ... + kObjectiveBound: ClassVar[HighsModelStatus] = ... + kObjectiveTarget: ClassVar[HighsModelStatus] = ... + kOptimal: ClassVar[HighsModelStatus] = ... + kPostsolveError: ClassVar[HighsModelStatus] = ... + kPresolveError: ClassVar[HighsModelStatus] = ... + kSolutionLimit: ClassVar[HighsModelStatus] = ... + kSolveError: ClassVar[HighsModelStatus] = ... + kTimeLimit: ClassVar[HighsModelStatus] = ... + kUnbounded: ClassVar[HighsModelStatus] = ... + kUnboundedOrInfeasible: ClassVar[HighsModelStatus] = ... + kUnknown: ClassVar[HighsModelStatus] = ... + def __init__(self, value: int) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __index__(self) -> int: ... + def __int__(self) -> int: ... + def __ne__(self, other: object) -> bool: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + +class HighsObjectiveSolution: + col_value: list[float] + objective: float + def __init__(self) -> None: ... + +class HighsOptionType: + __members__: ClassVar[dict] = ... # read-only + __entries: ClassVar[dict] = ... + kBool: ClassVar[HighsOptionType] = ... + kDouble: ClassVar[HighsOptionType] = ... + kInt: ClassVar[HighsOptionType] = ... + kString: ClassVar[HighsOptionType] = ... + def __init__(self, value: int) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __index__(self) -> int: ... + def __int__(self) -> int: ... + def __ne__(self, other: object) -> bool: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + +class HighsOptions: + allow_unbounded_or_infeasible: bool + allowed_matrix_scale_factor: int + dual_feasibility_tolerance: float + highs_analysis_level: int + highs_debug_level: int + infinite_bound: float + infinite_cost: float + ipm_iteration_limit: int + ipm_optimality_tolerance: float + ipx_dualize_strategy: int + large_matrix_value: float + log_dev_level: int + log_file: str + log_to_console: bool + mip_abs_gap: float + mip_detect_symmetry: bool + mip_feasibility_tolerance: float + mip_heuristic_effort: float + mip_lp_age_limit: int + mip_max_improving_sols: int + mip_max_leaves: int + mip_max_nodes: int + mip_max_stall_nodes: int + mip_min_cliquetable_entries_for_parallelism: int + mip_min_logging_interval: float + mip_pool_age_limit: int + mip_pool_soft_limit: int + mip_pscost_minreliable: int + mip_rel_gap: float + mip_report_level: int + objective_bound: float + objective_target: float + output_flag: bool + parallel: str + presolve: str + primal_feasibility_tolerance: float + random_seed: int + ranging: str + run_crossover: str + simplex_crash_strategy: int + simplex_dual_edge_weight_strategy: int + simplex_dualize_strategy: int + simplex_iteration_limit: int + simplex_max_concurrency: int + simplex_min_concurrency: int + simplex_permute_strategy: int + simplex_price_strategy: int + simplex_primal_edge_weight_strategy: int + simplex_scale_strategy: int + simplex_strategy: int + simplex_update_limit: int + small_matrix_value: float + solution_file: str + solver: str + threads: int + time_limit: float + write_model_file: str + write_model_to_file: bool + write_solution_style: int + write_solution_to_file: bool + def __init__(self) -> None: ... + +class HighsPresolveStatus: + __members__: ClassVar[dict] = ... # read-only + __entries: ClassVar[dict] = ... + kInfeasible: ClassVar[HighsPresolveStatus] = ... + kNotPresolved: ClassVar[HighsPresolveStatus] = ... + kNotReduced: ClassVar[HighsPresolveStatus] = ... + kNullError: ClassVar[HighsPresolveStatus] = ... + kOptionsError: ClassVar[HighsPresolveStatus] = ... + kReduced: ClassVar[HighsPresolveStatus] = ... + kReducedToEmpty: ClassVar[HighsPresolveStatus] = ... + kTimeout: ClassVar[HighsPresolveStatus] = ... + kUnboundedOrInfeasible: ClassVar[HighsPresolveStatus] = ... + def __init__(self, value: int) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __index__(self) -> int: ... + def __int__(self) -> int: ... + def __ne__(self, other: object) -> bool: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + +class HighsRanging: + col_bound_dn: HighsRangingRecord + col_bound_up: HighsRangingRecord + col_cost_dn: HighsRangingRecord + col_cost_up: HighsRangingRecord + row_bound_dn: HighsRangingRecord + row_bound_up: HighsRangingRecord + valid: bool + def __init__(self) -> None: ... + +class HighsRangingRecord: + in_var_: list[int] + objective_: list[float] + ou_var_: list[int] + value_: list[float] + def __init__(self) -> None: ... + +class HighsSolution: + col_dual: list[float] + col_value: list[float] + dual_valid: bool + row_dual: list[float] + row_value: list[float] + value_valid: bool + def __init__(self) -> None: ... + +class HighsSparseMatrix: + format_: MatrixFormat + index_: list[int] + num_col_: int + num_row_: int + p_end_: list[int] + start_: list[int] + value_: list[float] + def __init__(self) -> None: ... + +class HighsStatus: + __members__: ClassVar[dict] = ... # read-only + __entries: ClassVar[dict] = ... + kError: ClassVar[HighsStatus] = ... + kOk: ClassVar[HighsStatus] = ... + kWarning: ClassVar[HighsStatus] = ... + def __init__(self, value: int) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __index__(self) -> int: ... + def __int__(self) -> int: ... + def __ne__(self, other: object) -> bool: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + +class HighsVarType: + __members__: ClassVar[dict] = ... # read-only + __entries: ClassVar[dict] = ... + kContinuous: ClassVar[HighsVarType] = ... + kInteger: ClassVar[HighsVarType] = ... + kSemiContinuous: ClassVar[HighsVarType] = ... + kSemiInteger: ClassVar[HighsVarType] = ... + def __init__(self, value: int) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __index__(self) -> int: ... + def __int__(self) -> int: ... + def __ne__(self, other: object) -> bool: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + +class MatrixFormat: + __members__: ClassVar[dict] = ... # read-only + __entries: ClassVar[dict] = ... + kColwise: ClassVar[MatrixFormat] = ... + kRowwise: ClassVar[MatrixFormat] = ... + kRowwisePartitioned: ClassVar[MatrixFormat] = ... + def __init__(self, value: int) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __index__(self) -> int: ... + def __int__(self) -> int: ... + def __ne__(self, other: object) -> bool: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + +class ObjSense: + __members__: ClassVar[dict] = ... # read-only + __entries: ClassVar[dict] = ... + kMaximize: ClassVar[ObjSense] = ... + kMinimize: ClassVar[ObjSense] = ... + def __init__(self, value: int) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __index__(self) -> int: ... + def __int__(self) -> int: ... + def __ne__(self, other: object) -> bool: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + +class SolutionStatus: + __members__: ClassVar[dict] = ... # read-only + __entries: ClassVar[dict] = ... + kSolutionStatusFeasible: ClassVar[SolutionStatus] = ... + kSolutionStatusInfeasible: ClassVar[SolutionStatus] = ... + kSolutionStatusNone: ClassVar[SolutionStatus] = ... + def __init__(self, value: int) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __index__(self) -> int: ... + def __int__(self) -> int: ... + def __ne__(self, other: object) -> bool: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + +class _Highs: + def __init__(self) -> None: ... + def addCol(self, arg0: float, arg1: float, arg2: float, arg3: int, arg4: numpy.ndarray[numpy.int32], arg5: numpy.ndarray[numpy.float64]) -> HighsStatus: ... + def addCols(self, arg0: int, arg1: numpy.ndarray[numpy.float64], arg2: numpy.ndarray[numpy.float64], arg3: numpy.ndarray[numpy.float64], arg4: int, arg5: numpy.ndarray[numpy.int32], arg6: numpy.ndarray[numpy.int32], arg7: numpy.ndarray[numpy.float64]) -> HighsStatus: ... + def addRow(self, arg0: float, arg1: float, arg2: int, arg3: numpy.ndarray[numpy.int32], arg4: numpy.ndarray[numpy.float64]) -> HighsStatus: ... + def addRows(self, arg0: int, arg1: numpy.ndarray[numpy.float64], arg2: numpy.ndarray[numpy.float64], arg3: int, arg4: numpy.ndarray[numpy.int32], arg5: numpy.ndarray[numpy.int32], arg6: numpy.ndarray[numpy.float64]) -> HighsStatus: ... + def addVar(self, arg0: float, arg1: float) -> HighsStatus: ... + def addVars(self, arg0: int, arg1: numpy.ndarray[numpy.float64], arg2: numpy.ndarray[numpy.float64]) -> HighsStatus: ... + def basisStatusToString(self, arg0: HighsBasisStatus) -> str: ... + def basisValidityToString(self, arg0: int) -> str: ... + def changeCoeff(self, arg0: int, arg1: int, arg2: float) -> HighsStatus: ... + def changeColBounds(self, arg0: int, arg1: float, arg2: float) -> HighsStatus: ... + def changeColCost(self, arg0: int, arg1: float) -> HighsStatus: ... + def changeColIntegrality(self, arg0: int, arg1: HighsVarType) -> HighsStatus: ... + def changeColsBounds(self, arg0: int, arg1: numpy.ndarray[numpy.int32], arg2: numpy.ndarray[numpy.float64], arg3: numpy.ndarray[numpy.float64]) -> HighsStatus: ... + def changeColsCost(self, arg0: int, arg1: numpy.ndarray[numpy.int32], arg2: numpy.ndarray[numpy.float64]) -> HighsStatus: ... + def changeColsIntegrality(self, arg0: int, arg1: numpy.ndarray[numpy.int32], arg2: numpy.ndarray[numpy.uint8]) -> HighsStatus: ... + def changeObjectiveOffset(self, arg0: float) -> HighsStatus: ... + def changeObjectiveSense(self, arg0: ObjSense) -> HighsStatus: ... + def changeRowBounds(self, arg0: int, arg1: float, arg2: float) -> HighsStatus: ... + def clear(self) -> HighsStatus: ... + def clearModel(self) -> HighsStatus: ... + def clearSolver(self) -> HighsStatus: ... + def crossover(self, arg0: HighsSolution) -> HighsStatus: ... + def deleteCols(self, arg0: int, arg1: list[int]) -> HighsStatus: ... + def deleteRows(self, arg0: int, arg1: list[int]) -> HighsStatus: ... + def deleteVars(self, arg0: int, arg1: list[int]) -> HighsStatus: ... + def getBasis(self) -> HighsBasis: ... + def getCol(self, arg0: int) -> tuple[HighsStatus, float, float, float, int]: ... + def getColByName(self, arg0: str) -> tuple[HighsStatus, int]: ... + def getColEntries(self, arg0: int) -> tuple[HighsStatus, numpy.ndarray[numpy.int32], numpy.ndarray[numpy.float64]]: ... + def getColName(self, arg0: int) -> tuple[HighsStatus, str]: ... + def getCols(self, arg0: int, arg1: numpy.ndarray[numpy.int32]) -> tuple[HighsStatus, int, numpy.ndarray[numpy.float64], numpy.ndarray[numpy.float64], numpy.ndarray[numpy.float64], int]: ... + def getColsEntries(self, arg0: int, arg1: numpy.ndarray[numpy.int32]) -> tuple[HighsStatus, numpy.ndarray[numpy.int32], numpy.ndarray[numpy.int32], numpy.ndarray[numpy.float64]]: ... + def getHessianNumNz(self) -> int: ... + def getInfinity(self) -> float: ... + def getInfo(self) -> HighsInfo: ... + def getInfoType(self, arg0: str) -> tuple[HighsStatus, HighsInfoType]: ... + def getInfoValue(self, arg0: str) -> tuple[HighsStatus, object]: ... + def getLp(self) -> HighsLp: ... + def getModel(self) -> HighsModel: ... + def getModelPresolveStatus(self) -> HighsPresolveStatus: ... + def getModelStatus(self) -> HighsModelStatus: ... + def getNumCol(self) -> int: ... + def getNumNz(self) -> int: ... + def getNumRow(self) -> int: ... + def getObjectiveOffset(self) -> tuple[HighsStatus, float]: ... + def getObjectiveSense(self) -> tuple[HighsStatus, ObjSense]: ... + def getObjectiveValue(self) -> float: ... + def getOptionType(self, arg0: str) -> tuple[HighsStatus, HighsOptionType]: ... + def getOptionValue(self, arg0: str) -> tuple[HighsStatus, object]: ... + def getOptions(self) -> HighsOptions: ... + def getPresolvedLp(self) -> HighsLp: ... + def getRanging(self) -> tuple[HighsStatus, HighsRanging]: ... + def getRow(self, arg0: int) -> tuple[HighsStatus, float, float, int]: ... + def getRowByName(self, arg0: str) -> tuple[HighsStatus, int]: ... + def getRowEntries(self, arg0: int) -> tuple[HighsStatus, numpy.ndarray[numpy.int32], numpy.ndarray[numpy.float64]]: ... + def getRowName(self, arg0: int) -> tuple[HighsStatus, str]: ... + def getRows(self, arg0: int, arg1: numpy.ndarray[numpy.int32]) -> tuple[HighsStatus, int, numpy.ndarray[numpy.float64], numpy.ndarray[numpy.float64], int]: ... + def getRowsEntries(self, arg0: int, arg1: numpy.ndarray[numpy.int32]) -> tuple[HighsStatus, numpy.ndarray[numpy.int32], numpy.ndarray[numpy.int32], numpy.ndarray[numpy.float64]]: ... + def getRunTime(self) -> float: ... + def getSavedMipSolutions(self) -> list[HighsObjectiveSolution]: ... + def getSolution(self) -> HighsSolution: ... + def githash(self) -> str: ... + def modelStatusToString(self, arg0: HighsModelStatus) -> str: ... + def passColName(self, arg0: int, arg1: str) -> HighsStatus: ... + @overload + def passHessian(self, arg0: HighsHessian) -> HighsStatus: ... + @overload + def passHessian(self, arg0: int, arg1: int, arg2: int, arg3: numpy.ndarray[numpy.int32], arg4: numpy.ndarray[numpy.int32], arg5: numpy.ndarray[numpy.float64]) -> HighsStatus: ... + @overload + def passModel(self, arg0: HighsModel) -> HighsStatus: ... + @overload + def passModel(self, arg0: int, arg1: int, arg2: int, arg3: int, arg4: int, arg5: int, arg6: int, arg7: float, arg8: numpy.ndarray[numpy.float64], arg9: numpy.ndarray[numpy.float64], arg10: numpy.ndarray[numpy.float64], arg11: numpy.ndarray[numpy.float64], arg12: numpy.ndarray[numpy.float64], arg13: numpy.ndarray[numpy.int32], arg14: numpy.ndarray[numpy.int32], arg15: numpy.ndarray[numpy.float64], arg16: numpy.ndarray[numpy.int32], arg17: numpy.ndarray[numpy.int32], arg18: numpy.ndarray[numpy.float64], arg19: numpy.ndarray[numpy.int32]) -> HighsStatus: ... + @overload + def passModel(self, arg0: HighsLp) -> HighsStatus: ... + @overload + def passModel(self, arg0: int, arg1: int, arg2: int, arg3: int, arg4: int, arg5: float, arg6: numpy.ndarray[numpy.float64], arg7: numpy.ndarray[numpy.float64], arg8: numpy.ndarray[numpy.float64], arg9: numpy.ndarray[numpy.float64], arg10: numpy.ndarray[numpy.float64], arg11: numpy.ndarray[numpy.int32], arg12: numpy.ndarray[numpy.int32], arg13: numpy.ndarray[numpy.float64], arg14: numpy.ndarray[numpy.int32]) -> HighsStatus: ... + def passOptions(self, arg0: HighsOptions) -> HighsStatus: ... + def passRowName(self, arg0: int, arg1: str) -> HighsStatus: ... + @overload + def postsolve(self, arg0: HighsSolution, arg1: HighsBasis) -> HighsStatus: ... + @overload + def postsolve(self, arg0: HighsSolution) -> HighsStatus: ... + def presolve(self) -> HighsStatus: ... + def readBasis(self, arg0: str) -> HighsStatus: ... + def readModel(self, arg0: str) -> HighsStatus: ... + def readOptions(self, arg0: str) -> HighsStatus: ... + def readSolution(self, arg0: str, arg1: int) -> HighsStatus: ... + def resetOptions(self) -> HighsStatus: ... + def run(self) -> HighsStatus: ... + @overload + def setBasis(self, arg0: HighsBasis) -> HighsStatus: ... + @overload + def setBasis(self) -> HighsStatus: ... + def setCallback(self, arg0: Callable[[int, str, HighsCallbackDataOut, HighsCallbackDataIn, capsule], None], arg1: capsule) -> HighsStatus: ... + @overload + def setOptionValue(self, arg0: str, arg1: bool) -> HighsStatus: ... + @overload + def setOptionValue(self, arg0: str, arg1: int) -> HighsStatus: ... + @overload + def setOptionValue(self, arg0: str, arg1: float) -> HighsStatus: ... + @overload + def setOptionValue(self, arg0: str, arg1: str) -> HighsStatus: ... + def setSolution(self, arg0: HighsSolution) -> HighsStatus: ... + def solutionStatusToString(self, arg0: int) -> str: ... + def startCallback(self, arg0: HighsCallbackType) -> HighsStatus: ... + def startCallbackInt(self, arg0: int) -> HighsStatus: ... + def stopCallback(self, arg0: HighsCallbackType) -> HighsStatus: ... + def stopCallbackInt(self, arg0: int) -> HighsStatus: ... + def version(self) -> str: ... + def versionMajor(self) -> int: ... + def versionMinor(self) -> int: ... + def versionPatch(self) -> int: ... + def writeBasis(self, arg0: str) -> HighsStatus: ... + def writeInfo(self, arg0: str) -> HighsStatus: ... + def writeModel(self, arg0: str) -> HighsStatus: ... + def writeOptions(self, arg0: str) -> HighsStatus: ... + def writePresolvedModel(self, arg0: str) -> HighsStatus: ... + def writeSolution(self, arg0: str, arg1: int) -> HighsStatus: ... From a785025f437dece5b25ffd040cd52cbdc62c2ec5 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 12 Aug 2024 19:11:17 +0100 Subject: [PATCH 059/194] Suspended ray options and use_sensitivity_filter = false --- check/TestIis.cpp | 17 +++++++------- src/lp_data/HConst.h | 8 +++---- src/lp_data/HighsIis.cpp | 41 ++++++++++++++++++++-------------- src/lp_data/HighsInterface.cpp | 6 ++--- src/lp_data/HighsOptions.h | 18 +++++++++------ 5 files changed, 51 insertions(+), 39 deletions(-) diff --git a/check/TestIis.cpp b/check/TestIis.cpp index b3442bf2f3..a746381b31 100644 --- a/check/TestIis.cpp +++ b/check/TestIis.cpp @@ -124,7 +124,7 @@ TEST_CASE("lp-get-iis", "[iis]") { TEST_CASE("lp-get-iis-woodinfe", "[iis]") { std::string model = "woodinfe"; testMps(model, kIisStrategyFromLpRowPriority); - testMps(model, kIisStrategyFromRayRowPriority); + // testMps(model, kIisStrategyFromRayRowPriority); } TEST_CASE("lp-get-iis-galenet", "[iis]") { @@ -170,7 +170,7 @@ TEST_CASE("lp-get-iis-galenet", "[iis]") { // Hence only empty columns can be removed std::string model = "galenet"; testMps(model, kIisStrategyFromLpRowPriority); - testMps(model, kIisStrategyFromRayRowPriority); + // testMps(model, kIisStrategyFromRayRowPriority); } TEST_CASE("lp-get-iis-avgas", "[iis]") { @@ -180,7 +180,8 @@ TEST_CASE("lp-get-iis-avgas", "[iis]") { testMps(model, kIisStrategyFromLpRowPriority, HighsModelStatus::kNotset); // For the ray calculation the model is solved, so its status is // known - testMps(model, kIisStrategyFromRayRowPriority, HighsModelStatus::kOptimal); + // testMps(model, kIisStrategyFromRayRowPriority, + // HighsModelStatus::kOptimal); } void testIis(const std::string& model, const HighsIis& iis) { @@ -311,11 +312,11 @@ void testMps(std::string& model, const HighsInt iis_strategy, highs.setOptionValue("output_flag", dev_run); REQUIRE(highs.readModel(model_file) == HighsStatus::kOk); - if (iis_strategy == kIisStrategyFromRayRowPriority || - iis_strategy == kIisStrategyFromRayColPriority) { - // For a ray strategy, solve the LP first - REQUIRE(highs.run() == HighsStatus::kOk); - } + // if (iis_strategy == kIisStrategyFromRayRowPriority || + // iis_strategy == kIisStrategyFromRayColPriority) { + // // For a ray strategy, solve the LP first + // REQUIRE(highs.run() == HighsStatus::kOk); + // } highs.setOptionValue("iis_strategy", iis_strategy); HighsIis iis; REQUIRE(highs.getIis(iis) == HighsStatus::kOk); diff --git a/src/lp_data/HConst.h b/src/lp_data/HConst.h index 0aef1ee3f3..e94745d715 100644 --- a/src/lp_data/HConst.h +++ b/src/lp_data/HConst.h @@ -267,10 +267,10 @@ enum PresolveRuleType : int { enum IisStrategy { kIisStrategyMin = 0, - kIisStrategyFromRayRowPriority = kIisStrategyMin, // 0 - kIisStrategyFromRayColPriority, // 1 - kIisStrategyFromLpRowPriority, // 2 - kIisStrategyFromLpColPriority, // 3 + kIisStrategyFromLpRowPriority = kIisStrategyMin, // 0 + kIisStrategyFromLpColPriority, // 1 + // kIisStrategyFromRayRowPriority, // 2 + // kIisStrategyFromRayColPriority, // 3 kIisStrategyMax = kIisStrategyFromLpColPriority }; diff --git a/src/lp_data/HighsIis.cpp b/src/lp_data/HighsIis.cpp index 6046d44ff0..9f244415ca 100644 --- a/src/lp_data/HighsIis.cpp +++ b/src/lp_data/HighsIis.cpp @@ -86,7 +86,7 @@ void HighsIis::removeRow(const HighsInt row) { bool HighsIis::trivial(const HighsLp& lp, const HighsOptions& options) { this->invalidate(); const bool col_priority = - options.iis_strategy == kIisStrategyFromRayColPriority || + // options.iis_strategy == kIisStrategyFromRayColPriority || options.iis_strategy == kIisStrategyFromLpColPriority; for (HighsInt k = 0; k < 2; k++) { if ((col_priority && k == 0) || (!col_priority && k == 1)) { @@ -219,7 +219,7 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, const HighsBasis* basis) { const HighsLogOptions& log_options = options.log_options; const bool row_priority = - options.iis_strategy == kIisStrategyFromRayRowPriority || + // options.iis_strategy == kIisStrategyFromRayRowPriority || options.iis_strategy == kIisStrategyFromLpRowPriority; // Initially all columns and rows are candidates for the IIS for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) this->addCol(iCol); @@ -242,7 +242,7 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, assert(run_status == HighsStatus::kOk); // Solve the LP if (basis) highs.setBasis(*basis); - const bool use_sensitivity_filter = true; + const bool use_sensitivity_filter = false; std::vector primal_phase1_dual; bool row_deletion = false; HighsInt iX = -1; @@ -275,19 +275,26 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, const HighsInt* basic_index = highs.getBasicVariablesArray(); std::vector rhs; rhs.assign(lp.num_row_, 0); - // Get duals for nonbasic rows, and initialise duals so that basic duals are zero - assert(101==202); + // Get duals for nonbasic rows, and initialise duals so that basic duals + // are zero + assert(101 == 202); for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { - HighsInt iVar = basic_index[iRow]; - const double lower = iVar < lp.num_col_ ? lp.col_lower_[iVar] : lp.row_lower_[iVar-lp.num_col_]; - const double upper = iVar < lp.num_col_ ? lp.col_upper_[iVar] : lp.row_upper_[iVar-lp.num_col_]; - const double value = iVar < lp.num_col_ ? solution.col_value[iVar] : solution.row_value[iVar-lp.num_col_]; - if (value < lower - options.primal_feasibility_tolerance) { - rhs[iRow] = -1; - } else if (value > upper + options.primal_feasibility_tolerance) { - rhs[iRow] = 1; - } + HighsInt iVar = basic_index[iRow]; + const double lower = iVar < lp.num_col_ + ? lp.col_lower_[iVar] + : lp.row_lower_[iVar - lp.num_col_]; + const double upper = iVar < lp.num_col_ + ? lp.col_upper_[iVar] + : lp.row_upper_[iVar - lp.num_col_]; + const double value = iVar < lp.num_col_ + ? solution.col_value[iVar] + : solution.row_value[iVar - lp.num_col_]; + if (value < lower - options.primal_feasibility_tolerance) { + rhs[iRow] = -1; + } else if (value > upper + options.primal_feasibility_tolerance) { + rhs[iRow] = 1; + } } HVector pi; pi.setup(lp.num_row_); @@ -295,9 +302,9 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options, pi.count = lp.num_row_; std::vector reduced_costs_value; std::vector reduced_costs_index; - lp.a_matrix_.productTransposeQuad(reduced_costs_value, reduced_costs_index, pi); - - + lp.a_matrix_.productTransposeQuad(reduced_costs_value, + reduced_costs_index, pi); + primal_phase1_dual = highs.getPrimalPhase1Dual(); HighsInt num_zero_dual = 0; for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 8b55762158..5b58574d69 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -1563,9 +1563,9 @@ HighsStatus Highs::getIisInterface() { this->iis_.valid_ = true; return HighsStatus::kOk; } - const bool ray_option = - options_.iis_strategy == kIisStrategyFromRayRowPriority || - options_.iis_strategy == kIisStrategyFromRayColPriority; + const bool ray_option = false; + // options_.iis_strategy == kIisStrategyFromRayRowPriority || + // options_.iis_strategy == kIisStrategyFromRayColPriority; if (this->model_status_ == HighsModelStatus::kInfeasible && ray_option && !ekk_instance_.status_.has_invert) { // Model is known to be infeasible, and a dual ray option is diff --git a/src/lp_data/HighsOptions.h b/src/lp_data/HighsOptions.h index 87542ecbc5..8f934b30ce 100644 --- a/src/lp_data/HighsOptions.h +++ b/src/lp_data/HighsOptions.h @@ -1099,13 +1099,17 @@ class HighsOptions : public HighsOptionsStruct { record_int = new OptionRecordInt( "iis_strategy", "Strategy for IIS calculation: " - "Use unbounded dual ray and prioritise low number of rows (default) / " - "Use ray and prioritise low numbers of columns / " - "Use LP and prioritise rows / " - "Use LP and prioritise columns" - " (/0/1/2/3)", - advanced, &iis_strategy, kIisStrategyMin, - kIisStrategyFromRayRowPriority, kIisStrategyMax); + // "Use LP and p" + "Prioritise rows (default) / " + // "Use LP and p" + "Prioritise columns" + // "Use unbounded dual ray and prioritise low number of rows + // (default) / " "Use ray and prioritise low numbers of columns " + " (0/1" + // "/2/3)", + ")", + advanced, &iis_strategy, kIisStrategyMin, kIisStrategyFromLpRowPriority, + kIisStrategyMax); records.push_back(record_int); // Fix the number of user settable options From fa94de15a63a04862785c3528a33c4ccc5a87db4 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 14 Aug 2024 14:29:44 +0100 Subject: [PATCH 060/194] Highs::computeInfeasibleRows renamed Highs::elasticityFilter and passing general penalties --- src/Highs.h | 15 ++++++++++-- src/lp_data/HighsInterface.cpp | 42 +++++++++++++++++++++++----------- 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/src/Highs.h b/src/Highs.h index c2044c7dc9..34d67e8423 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -500,6 +500,16 @@ class Highs { */ HighsStatus getRanging(HighsRanging& ranging); + /** + * @brief Solve the feasibility relaxation problem + */ + HighsStatus feasibilityRelaxation(const double global_lower_penalty, + const double global_upper_penalty, + const double global_rhs_penalty, + const double* local_lower_penalty = nullptr, + const double* local_upper_penalty = nullptr, + const double* local_rhs_penalty = nullptr); + /** * @brief Get the ill-conditioning information for the current basis */ @@ -1524,8 +1534,9 @@ class Highs { HighsStatus getIisInterface(); - HighsStatus computeInfeasibleRows(const bool elastic_columns, - std::vector& infeasible_row); + HighsStatus elasticityFilter(const double global_lower_penalty, const double global_upper_penalty, const double global_rhs_penalty, + const double* local_lower_penalty, const double* local_upper_penalty, const double* local_rhs_penalty, + const bool get_infeasible_row, std::vector& infeasible_row_subset); HighsStatus extractIis(HighsInt& num_iis_col, HighsInt& num_iis_row, HighsInt* iis_col_index, HighsInt* iis_row_index, HighsInt* iis_col_bound, HighsInt* iis_row_bound); diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 5b58574d69..ff35e3f1ef 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -1624,8 +1624,9 @@ HighsStatus Highs::getIisInterface() { HighsLp check_lp_before = this->model_.lp_; // Apply the elasticity filter to the whole model in order to // determine an infeasible subset of rows - HighsStatus return_status = - this->computeInfeasibleRows(false, infeasible_row_subset); + HighsStatus return_status = this->elasticityFilter(-1.0, -1.0, 1.0, + nullptr, nullptr, nullptr, true, + infeasible_row_subset); HighsLp check_lp_after = this->model_.lp_; assert(check_lp_before.equalButForScalingAndNames(check_lp_after)); if (return_status != HighsStatus::kOk) return return_status; @@ -1673,9 +1674,13 @@ HighsStatus Highs::getIisInterface() { return return_status; } -HighsStatus Highs::computeInfeasibleRows( - const bool elastic_columns, std::vector& infeasible_row_subset) { - // Elasticity filter +HighsStatus Highs::elasticityFilter(const double global_lower_penalty, const double global_upper_penalty, const double global_rhs_penalty, + const double* local_lower_penalty, const double* local_upper_penalty, const double* local_rhs_penalty, + const bool get_infeasible_row, std::vector& infeasible_row_subset) { + // Solve the feasibility relaxation problem for the given penalties, + // continuing to act as the elasticity filter get_infeasible_row is + // true, resulting in an infeasibility subset for further refinement + // as an IIS // // Construct the e-LP: // @@ -1688,13 +1693,10 @@ HighsStatus Highs::computeInfeasibleRows( // l <= x + e_l - e_u <= u, // // where the elastic variables are not used if the corresponding - // bound is infinite, and the elastic variables e_l and e_u are not - // used if elastic_columns is false + // bound is infinite or the local/global penalty is negative. // - // x is free, and the objective is the sum of the elastic variables. - // - // Determine the number of lower and upper elastic variables for - // columns and rows + // x is free, and the objective is the linear function of the + // elastic variables given by the local/global penalties // // col_of_ecol lists the column indices corresponding to the entries in // bound_of_col_of_ecol so that the results can be interpreted @@ -1722,17 +1724,31 @@ HighsStatus Highs::computeInfeasibleRows( HighsStatus run_status; const bool write_model = false; HighsInt col_ecol_offset; + // Take copies of the original model dimensions and column data + // vectors, as they will be modified in forming the e-LP const HighsInt original_num_col = lp.num_col_; const HighsInt original_num_row = lp.num_row_; const std::vector original_col_cost = lp.col_cost_; const std::vector original_col_lower = lp.col_lower_; const std::vector original_col_upper = lp.col_upper_; + // Zero the column costs std::vector zero_costs; zero_costs.assign(original_num_col, 0); run_status = this->changeColsCost(0, lp.num_col_ - 1, zero_costs.data()); assert(run_status == HighsStatus::kOk); - if (elastic_columns) { + const bool has_local_lower_penalty = local_lower_penalty; + const bool has_global_elastic_lower = global_lower_penalty >= 0; + const bool has_elastic_lower = has_local_lower_penalty || has_global_elastic_lower; + const bool has_local_upper_penalty = local_upper_penalty; + const bool has_global_elastic_upper = global_upper_penalty >= 0; + const bool has_elastic_upper = has_local_upper_penalty || has_global_elastic_upper; + const bool has_local_rhs_penalty = local_rhs_penalty; + const bool has_global_elastic_rhs = global_rhs_penalty >= 0; + const bool has_elastic_rows = has_local_rhs_penalty || has_global_elastic_rhs; + const bool has_elastic_columns = has_elastic_lower || has_elastic_upper; + + if (has_elastic_columns) { // When defining names, need to know the column number HighsInt previous_num_col = lp.num_col_; HighsInt previous_num_row = lp.num_row_; @@ -1913,7 +1929,7 @@ HighsStatus Highs::computeInfeasibleRows( if (kIisDevReport) printf("\nElasticity filter pass %d\n==============\n", int(loop_k)); HighsInt num_fixed = 0; - if (elastic_columns) { + if (has_elastic_columns) { for (HighsInt eCol = 0; eCol < col_of_ecol.size(); eCol++) { HighsInt iCol = col_of_ecol[eCol]; if (solution.col_value[col_ecol_offset + eCol] > From 787fe6232548bf58757c83eb425153ef5a56989a Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 14 Aug 2024 15:13:45 +0100 Subject: [PATCH 061/194] Now using elasticityFilterReturn --- src/Highs.h | 7 +++ src/lp_data/HighsInterface.cpp | 82 ++++++++++++++++++++++------------ 2 files changed, 61 insertions(+), 28 deletions(-) diff --git a/src/Highs.h b/src/Highs.h index 34d67e8423..4ef76c44b9 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -1534,6 +1534,13 @@ class Highs { HighsStatus getIisInterface(); + HighsStatus elasticityFilterReturn(const HighsStatus return_status, + const bool feasible_model, + const HighsInt original_num_col, + const HighsInt original_num_row, + const std::vector& original_col_cost, + const std::vector& original_col_lower, + const std::vector original_col_upper); HighsStatus elasticityFilter(const double global_lower_penalty, const double global_upper_penalty, const double global_rhs_penalty, const double* local_lower_penalty, const double* local_upper_penalty, const double* local_rhs_penalty, const bool get_infeasible_row, std::vector& infeasible_row_subset); diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index ff35e3f1ef..251097aafd 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -1674,6 +1674,41 @@ HighsStatus Highs::getIisInterface() { return return_status; } +HighsStatus Highs::elasticityFilterReturn(const HighsStatus return_status, + const bool feasible_model, + const HighsInt original_num_col, + const HighsInt original_num_row, + const std::vector& original_col_cost, + const std::vector& original_col_lower, + const std::vector original_col_upper) { + const HighsLp& lp = this->model_.lp_; + // Delete any additional rows and columns, and restore the original + // column costs and bounds + HighsStatus run_status; + run_status = this->deleteRows(original_num_row, lp.num_row_ - 1); + assert(run_status == HighsStatus::kOk); + + run_status = this->deleteCols(original_num_col, lp.num_col_ - 1); + assert(run_status == HighsStatus::kOk); + + run_status = + this->changeColsCost(0, original_num_col - 1, original_col_cost.data()); + assert(run_status == HighsStatus::kOk); + + run_status = + this->changeColsBounds(0, original_num_col - 1, original_col_lower.data(), + original_col_upper.data()); + assert(run_status == HighsStatus::kOk); + + assert(lp.num_col_ == original_num_col); + assert(lp.num_row_ == original_num_row); + + // If the model is feasible, then the status of model is not known + if (feasible_model) this->model_status_ = HighsModelStatus::kNotset; + + return return_status; + +} HighsStatus Highs::elasticityFilter(const double global_lower_penalty, const double global_upper_penalty, const double global_rhs_penalty, const double* local_lower_penalty, const double* local_upper_penalty, const double* local_rhs_penalty, const bool get_infeasible_row, std::vector& infeasible_row_subset) { @@ -1737,16 +1772,19 @@ HighsStatus Highs::elasticityFilter(const double global_lower_penalty, const dou run_status = this->changeColsCost(0, lp.num_col_ - 1, zero_costs.data()); assert(run_status == HighsStatus::kOk); + // For the columns const bool has_local_lower_penalty = local_lower_penalty; const bool has_global_elastic_lower = global_lower_penalty >= 0; const bool has_elastic_lower = has_local_lower_penalty || has_global_elastic_lower; const bool has_local_upper_penalty = local_upper_penalty; const bool has_global_elastic_upper = global_upper_penalty >= 0; const bool has_elastic_upper = has_local_upper_penalty || has_global_elastic_upper; + const bool has_elastic_columns = has_elastic_lower || has_elastic_upper; + // For the rows const bool has_local_rhs_penalty = local_rhs_penalty; const bool has_global_elastic_rhs = global_rhs_penalty >= 0; const bool has_elastic_rows = has_local_rhs_penalty || has_global_elastic_rhs; - const bool has_elastic_columns = has_elastic_lower || has_elastic_upper; + assert(has_elastic_columns || has_elastic_rows); if (has_elastic_columns) { // When defining names, need to know the column number @@ -1847,6 +1885,10 @@ HighsStatus Highs::elasticityFilter(const double global_lower_penalty, const dou ecol_start.push_back(0); const bool has_row_names = lp.row_names_.size() > 0; for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { + // Get the penalty for violating the bounds on this row + const double penalty = has_local_rhs_penalty ? local_rhs_penalty[iRow] : global_rhs_penalty; + // Negative penalty implies that the bounds cannot be violated + if (penalty < 0) continue; const double lower = lp.row_lower_[iRow]; const double upper = lp.row_upper_[iRow]; if (lower > -kHighsInf) { @@ -1860,6 +1902,7 @@ HighsStatus Highs::elasticityFilter(const double global_lower_penalty, const dou ecol_index.push_back(iRow); ecol_value.push_back(1); ecol_start.push_back(ecol_index.size()); + ecol_cost.push_back(penalty); evar_ix++; } if (upper < kHighsInf) { @@ -1873,12 +1916,12 @@ HighsStatus Highs::elasticityFilter(const double global_lower_penalty, const dou ecol_index.push_back(iRow); ecol_value.push_back(-1); ecol_start.push_back(ecol_index.size()); + ecol_cost.push_back(penalty); evar_ix++; } } HighsInt num_new_col = ecol_start.size() - 1; HighsInt num_new_nz = ecol_start[num_new_col]; - ecol_cost.assign(num_new_col, 1); ecol_lower.assign(num_new_col, 0); ecol_upper.assign(num_new_col, kHighsInf); HighsInt previous_num_col = lp.num_col_; @@ -1915,7 +1958,9 @@ HighsStatus Highs::elasticityFilter(const double global_lower_penalty, const dou }; run_status = solveLp(); - if (run_status != HighsStatus::kOk) return run_status; + if (run_status != HighsStatus::kOk) return elasticityFilterReturn(run_status, false, + original_num_col, original_num_row, + original_col_cost, original_col_lower, original_col_upper); if (kIisDevReport) this->writeSolution("", kSolutionStylePretty); // Model status should be optimal, unless model is unbounded assert(this->model_status_ == HighsModelStatus::kOptimal || @@ -1968,7 +2013,9 @@ HighsStatus Highs::elasticityFilter(const double global_lower_penalty, const dou break; } HighsStatus run_status = solveLp(); - if (run_status != HighsStatus::kOk) return run_status; + if (run_status != HighsStatus::kOk) return elasticityFilterReturn(run_status, feasible_model, + original_num_col, original_num_row, + original_col_cost, original_col_lower, original_col_upper); if (kIisDevReport) this->writeSolution("", kSolutionStylePretty); HighsModelStatus model_status = this->getModelStatus(); if (model_status == HighsModelStatus::kInfeasible) break; @@ -2018,30 +2065,9 @@ HighsStatus Highs::elasticityFilter(const double global_lower_penalty, const dou "rows\n", int(loop_k), int(num_enforced_col_ecol), int(num_enforced_row_ecol)); - // Delete any additional rows and columns, and restore the original - // column costs and bounds - run_status = this->deleteRows(original_num_row, lp.num_row_ - 1); - assert(run_status == HighsStatus::kOk); - - run_status = this->deleteCols(original_num_col, lp.num_col_ - 1); - assert(run_status == HighsStatus::kOk); - - run_status = - this->changeColsCost(0, original_num_col - 1, original_col_cost.data()); - assert(run_status == HighsStatus::kOk); - - run_status = - this->changeColsBounds(0, original_num_col - 1, original_col_lower.data(), - original_col_upper.data()); - assert(run_status == HighsStatus::kOk); - - assert(lp.num_col_ == original_num_col); - assert(lp.num_row_ == original_num_row); - - // If the model is feasible, then the status of model is not known - if (feasible_model) this->model_status_ = HighsModelStatus::kNotset; - - return HighsStatus::kOk; + return elasticityFilterReturn(HighsStatus::kOk, feasible_model, + original_num_col, original_num_row, + original_col_cost, original_col_lower, original_col_upper); } HighsStatus Highs::extractIis(HighsInt& num_iis_col, HighsInt& num_iis_row, From b57e6a2c6193ab9ccb5e67a0720996a8d0d4193c Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 14 Aug 2024 23:27:19 +0100 Subject: [PATCH 062/194] Really should clear integrality in changeLpIntegrality if all continuous --- check/TestIis.cpp | 21 +++++++++++++++++ src/Highs.h | 3 ++- src/lp_data/Highs.cpp | 16 +++++++++++++ src/lp_data/HighsInterface.cpp | 42 +++++++++++++++++++++++++++------- src/lp_data/HighsLp.cpp | 1 + 5 files changed, 74 insertions(+), 9 deletions(-) diff --git a/check/TestIis.cpp b/check/TestIis.cpp index a746381b31..3f96bb605c 100644 --- a/check/TestIis.cpp +++ b/check/TestIis.cpp @@ -184,6 +184,27 @@ TEST_CASE("lp-get-iis-avgas", "[iis]") { // HighsModelStatus::kOptimal); } +TEST_CASE("lp-feasibility-relaxation", "[iis]") { + HighsLp lp; + lp.num_col_ = 2; + lp.num_row_ = 3; + lp.col_cost_ = {1, -2}; + lp.col_lower_ = {5, -inf}; + lp.col_upper_ = {inf, inf}; + lp.row_lower_ = {2, -inf, -inf}; + lp.row_upper_ = {inf, 1, 20}; + lp.a_matrix_.start_ = {0, 3, 6}; + lp.a_matrix_.index_ = {0, 1, 2, 0, 1, 2}; + lp.a_matrix_.value_ = {-1, -3, 20, 21, 2, 1}; + lp.integrality_ = {HighsVarType::kInteger, HighsVarType::kInteger}; + Highs h; + h.passModel(lp); + // h.run(); + h.feasibilityRelaxation(1, 1, 1); + h.writeSolution("", 1); + +} + void testIis(const std::string& model, const HighsIis& iis) { HighsModelStatus model_status; std::string model_file = diff --git a/src/Highs.h b/src/Highs.h index 4ef76c44b9..d4342fba1f 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -1540,7 +1540,8 @@ class Highs { const HighsInt original_num_row, const std::vector& original_col_cost, const std::vector& original_col_lower, - const std::vector original_col_upper); + const std::vector original_col_upper, + const std::vector original_integrality); HighsStatus elasticityFilter(const double global_lower_penalty, const double global_upper_penalty, const double global_rhs_penalty, const double* local_lower_penalty, const double* local_upper_penalty, const double* local_rhs_penalty, const bool get_infeasible_row, std::vector& infeasible_row_subset); diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index 947db2fce4..92be9f459d 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -1666,6 +1666,22 @@ HighsStatus Highs::getRanging(HighsRanging& ranging) { return return_status; } +HighsStatus Highs::feasibilityRelaxation(const double global_lower_penalty, + const double global_upper_penalty, + const double global_rhs_penalty, + const double* local_lower_penalty, + const double* local_upper_penalty, + const double* local_rhs_penalty) { + std::vectorinfeasible_row_subset; + return elasticityFilter(global_lower_penalty, + global_upper_penalty, + global_rhs_penalty, + local_lower_penalty, + local_upper_penalty, + local_rhs_penalty, + false, infeasible_row_subset); +} + HighsStatus Highs::getIllConditioning(HighsIllConditioning& ill_conditioning, const bool constraint, const HighsInt method, diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 251097aafd..8131c18750 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -1680,7 +1680,8 @@ HighsStatus Highs::elasticityFilterReturn(const HighsStatus return_status, const HighsInt original_num_row, const std::vector& original_col_cost, const std::vector& original_col_lower, - const std::vector original_col_upper) { + const std::vector original_col_upper, + const std::vector original_integrality) { const HighsLp& lp = this->model_.lp_; // Delete any additional rows and columns, and restore the original // column costs and bounds @@ -1700,6 +1701,11 @@ HighsStatus Highs::elasticityFilterReturn(const HighsStatus return_status, original_col_upper.data()); assert(run_status == HighsStatus::kOk); + if (original_integrality.size()) { + this->changeColsIntegrality(0, original_num_col - 1, original_integrality.data()); + assert(run_status == HighsStatus::kOk); + } + assert(lp.num_col_ == original_num_col); assert(lp.num_row_ == original_num_row); @@ -1709,6 +1715,7 @@ HighsStatus Highs::elasticityFilterReturn(const HighsStatus return_status, return return_status; } + HighsStatus Highs::elasticityFilter(const double global_lower_penalty, const double global_upper_penalty, const double global_rhs_penalty, const double* local_lower_penalty, const double* local_upper_penalty, const double* local_rhs_penalty, const bool get_infeasible_row, std::vector& infeasible_row_subset) { @@ -1766,12 +1773,21 @@ HighsStatus Highs::elasticityFilter(const double global_lower_penalty, const dou const std::vector original_col_cost = lp.col_cost_; const std::vector original_col_lower = lp.col_lower_; const std::vector original_col_upper = lp.col_upper_; + const std::vector original_integrality = lp.integrality_; // Zero the column costs std::vector zero_costs; zero_costs.assign(original_num_col, 0); run_status = this->changeColsCost(0, lp.num_col_ - 1, zero_costs.data()); assert(run_status == HighsStatus::kOk); + // Set any integrality to continuous + if (lp.integrality_.size()) { + std::vector all_continuous; + all_continuous.assign(original_num_col, HighsVarType::kContinuous); + run_status = this->changeColsIntegrality(0, lp.num_col_ - 1, all_continuous.data()); + assert(run_status == HighsStatus::kOk); + } + // For the columns const bool has_local_lower_penalty = local_lower_penalty; const bool has_global_elastic_lower = global_lower_penalty >= 0; @@ -1958,14 +1974,21 @@ HighsStatus Highs::elasticityFilter(const double global_lower_penalty, const dou }; run_status = solveLp(); - if (run_status != HighsStatus::kOk) return elasticityFilterReturn(run_status, false, - original_num_col, original_num_row, - original_col_cost, original_col_lower, original_col_upper); + if (run_status != HighsStatus::kOk) + return elasticityFilterReturn(run_status, false, + original_num_col, original_num_row, + original_col_cost, original_col_lower, + original_col_upper, original_integrality); if (kIisDevReport) this->writeSolution("", kSolutionStylePretty); // Model status should be optimal, unless model is unbounded assert(this->model_status_ == HighsModelStatus::kOptimal || this->model_status_ == HighsModelStatus::kUnbounded); + if (!get_infeasible_row) + return elasticityFilterReturn(HighsStatus::kOk, false, + original_num_col, original_num_row, + original_col_cost, original_col_lower, + original_col_upper, original_integrality); const HighsSolution& solution = this->getSolution(); // Now fix e-variables that are positive and re-solve until e-LP is infeasible HighsInt loop_k = 0; @@ -2013,9 +2036,11 @@ HighsStatus Highs::elasticityFilter(const double global_lower_penalty, const dou break; } HighsStatus run_status = solveLp(); - if (run_status != HighsStatus::kOk) return elasticityFilterReturn(run_status, feasible_model, - original_num_col, original_num_row, - original_col_cost, original_col_lower, original_col_upper); + if (run_status != HighsStatus::kOk) + return elasticityFilterReturn(run_status, feasible_model, + original_num_col, original_num_row, + original_col_cost, original_col_lower, + original_col_upper, original_integrality); if (kIisDevReport) this->writeSolution("", kSolutionStylePretty); HighsModelStatus model_status = this->getModelStatus(); if (model_status == HighsModelStatus::kInfeasible) break; @@ -2067,7 +2092,8 @@ HighsStatus Highs::elasticityFilter(const double global_lower_penalty, const dou return elasticityFilterReturn(HighsStatus::kOk, feasible_model, original_num_col, original_num_row, - original_col_cost, original_col_lower, original_col_upper); + original_col_cost, original_col_lower, + original_col_upper, original_integrality); } HighsStatus Highs::extractIis(HighsInt& num_iis_col, HighsInt& num_iis_row, diff --git a/src/lp_data/HighsLp.cpp b/src/lp_data/HighsLp.cpp index f7e63312f4..842755a546 100644 --- a/src/lp_data/HighsLp.cpp +++ b/src/lp_data/HighsLp.cpp @@ -452,6 +452,7 @@ void HighsLp::deleteColsFromVectors( this->col_cost_.resize(new_num_col); this->col_lower_.resize(new_num_col); this->col_upper_.resize(new_num_col); + if (have_integrality) this->integrality_.resize(new_num_col); if (have_names) this->col_names_.resize(new_num_col); } From a4eac174c4687f70eaad3b20ac75b1f0da060608 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 14 Aug 2024 23:31:30 +0100 Subject: [PATCH 063/194] Fixed bug in HighsLp::deleteColsFromVectors --- check/TestIis.cpp | 1 - src/Highs.h | 36 +++++++++------- src/lp_data/Highs.cpp | 23 +++++----- src/lp_data/HighsInterface.cpp | 76 ++++++++++++++++++---------------- src/lp_data/HighsLpUtils.cpp | 3 ++ 5 files changed, 74 insertions(+), 65 deletions(-) diff --git a/check/TestIis.cpp b/check/TestIis.cpp index 3f96bb605c..ffd8a96b49 100644 --- a/check/TestIis.cpp +++ b/check/TestIis.cpp @@ -202,7 +202,6 @@ TEST_CASE("lp-feasibility-relaxation", "[iis]") { // h.run(); h.feasibilityRelaxation(1, 1, 1); h.writeSolution("", 1); - } void testIis(const std::string& model, const HighsIis& iis) { diff --git a/src/Highs.h b/src/Highs.h index d4342fba1f..c9c42f3a8c 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -504,11 +504,11 @@ class Highs { * @brief Solve the feasibility relaxation problem */ HighsStatus feasibilityRelaxation(const double global_lower_penalty, - const double global_upper_penalty, - const double global_rhs_penalty, - const double* local_lower_penalty = nullptr, - const double* local_upper_penalty = nullptr, - const double* local_rhs_penalty = nullptr); + const double global_upper_penalty, + const double global_rhs_penalty, + const double* local_lower_penalty = nullptr, + const double* local_upper_penalty = nullptr, + const double* local_rhs_penalty = nullptr); /** * @brief Get the ill-conditioning information for the current basis @@ -1534,17 +1534,21 @@ class Highs { HighsStatus getIisInterface(); - HighsStatus elasticityFilterReturn(const HighsStatus return_status, - const bool feasible_model, - const HighsInt original_num_col, - const HighsInt original_num_row, - const std::vector& original_col_cost, - const std::vector& original_col_lower, - const std::vector original_col_upper, - const std::vector original_integrality); - HighsStatus elasticityFilter(const double global_lower_penalty, const double global_upper_penalty, const double global_rhs_penalty, - const double* local_lower_penalty, const double* local_upper_penalty, const double* local_rhs_penalty, - const bool get_infeasible_row, std::vector& infeasible_row_subset); + HighsStatus elasticityFilterReturn( + const HighsStatus return_status, const bool feasible_model, + const HighsInt original_num_col, const HighsInt original_num_row, + const std::vector& original_col_cost, + const std::vector& original_col_lower, + const std::vector original_col_upper, + const std::vector original_integrality); + HighsStatus elasticityFilter(const double global_lower_penalty, + const double global_upper_penalty, + const double global_rhs_penalty, + const double* local_lower_penalty, + const double* local_upper_penalty, + const double* local_rhs_penalty, + const bool get_infeasible_row, + std::vector& infeasible_row_subset); HighsStatus extractIis(HighsInt& num_iis_col, HighsInt& num_iis_row, HighsInt* iis_col_index, HighsInt* iis_row_index, HighsInt* iis_col_bound, HighsInt* iis_row_bound); diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index 92be9f459d..76a4dbc2ac 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -1667,19 +1667,16 @@ HighsStatus Highs::getRanging(HighsRanging& ranging) { } HighsStatus Highs::feasibilityRelaxation(const double global_lower_penalty, - const double global_upper_penalty, - const double global_rhs_penalty, - const double* local_lower_penalty, - const double* local_upper_penalty, - const double* local_rhs_penalty) { - std::vectorinfeasible_row_subset; - return elasticityFilter(global_lower_penalty, - global_upper_penalty, - global_rhs_penalty, - local_lower_penalty, - local_upper_penalty, - local_rhs_penalty, - false, infeasible_row_subset); + const double global_upper_penalty, + const double global_rhs_penalty, + const double* local_lower_penalty, + const double* local_upper_penalty, + const double* local_rhs_penalty) { + std::vector infeasible_row_subset; + return elasticityFilter(global_lower_penalty, global_upper_penalty, + global_rhs_penalty, local_lower_penalty, + local_upper_penalty, local_rhs_penalty, false, + infeasible_row_subset); } HighsStatus Highs::getIllConditioning(HighsIllConditioning& ill_conditioning, diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 8131c18750..1d28dc2037 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -1624,9 +1624,9 @@ HighsStatus Highs::getIisInterface() { HighsLp check_lp_before = this->model_.lp_; // Apply the elasticity filter to the whole model in order to // determine an infeasible subset of rows - HighsStatus return_status = this->elasticityFilter(-1.0, -1.0, 1.0, - nullptr, nullptr, nullptr, true, - infeasible_row_subset); + HighsStatus return_status = + this->elasticityFilter(-1.0, -1.0, 1.0, nullptr, nullptr, nullptr, true, + infeasible_row_subset); HighsLp check_lp_after = this->model_.lp_; assert(check_lp_before.equalButForScalingAndNames(check_lp_after)); if (return_status != HighsStatus::kOk) return return_status; @@ -1674,14 +1674,13 @@ HighsStatus Highs::getIisInterface() { return return_status; } -HighsStatus Highs::elasticityFilterReturn(const HighsStatus return_status, - const bool feasible_model, - const HighsInt original_num_col, - const HighsInt original_num_row, - const std::vector& original_col_cost, - const std::vector& original_col_lower, - const std::vector original_col_upper, - const std::vector original_integrality) { +HighsStatus Highs::elasticityFilterReturn( + const HighsStatus return_status, const bool feasible_model, + const HighsInt original_num_col, const HighsInt original_num_row, + const std::vector& original_col_cost, + const std::vector& original_col_lower, + const std::vector original_col_upper, + const std::vector original_integrality) { const HighsLp& lp = this->model_.lp_; // Delete any additional rows and columns, and restore the original // column costs and bounds @@ -1702,7 +1701,8 @@ HighsStatus Highs::elasticityFilterReturn(const HighsStatus return_status, assert(run_status == HighsStatus::kOk); if (original_integrality.size()) { - this->changeColsIntegrality(0, original_num_col - 1, original_integrality.data()); + this->changeColsIntegrality(0, original_num_col - 1, + original_integrality.data()); assert(run_status == HighsStatus::kOk); } @@ -1713,12 +1713,14 @@ HighsStatus Highs::elasticityFilterReturn(const HighsStatus return_status, if (feasible_model) this->model_status_ = HighsModelStatus::kNotset; return return_status; - } -HighsStatus Highs::elasticityFilter(const double global_lower_penalty, const double global_upper_penalty, const double global_rhs_penalty, - const double* local_lower_penalty, const double* local_upper_penalty, const double* local_rhs_penalty, - const bool get_infeasible_row, std::vector& infeasible_row_subset) { +HighsStatus Highs::elasticityFilter( + const double global_lower_penalty, const double global_upper_penalty, + const double global_rhs_penalty, const double* local_lower_penalty, + const double* local_upper_penalty, const double* local_rhs_penalty, + const bool get_infeasible_row, + std::vector& infeasible_row_subset) { // Solve the feasibility relaxation problem for the given penalties, // continuing to act as the elasticity filter get_infeasible_row is // true, resulting in an infeasibility subset for further refinement @@ -1784,24 +1786,27 @@ HighsStatus Highs::elasticityFilter(const double global_lower_penalty, const dou if (lp.integrality_.size()) { std::vector all_continuous; all_continuous.assign(original_num_col, HighsVarType::kContinuous); - run_status = this->changeColsIntegrality(0, lp.num_col_ - 1, all_continuous.data()); + run_status = + this->changeColsIntegrality(0, lp.num_col_ - 1, all_continuous.data()); assert(run_status == HighsStatus::kOk); } // For the columns const bool has_local_lower_penalty = local_lower_penalty; const bool has_global_elastic_lower = global_lower_penalty >= 0; - const bool has_elastic_lower = has_local_lower_penalty || has_global_elastic_lower; + const bool has_elastic_lower = + has_local_lower_penalty || has_global_elastic_lower; const bool has_local_upper_penalty = local_upper_penalty; const bool has_global_elastic_upper = global_upper_penalty >= 0; - const bool has_elastic_upper = has_local_upper_penalty || has_global_elastic_upper; + const bool has_elastic_upper = + has_local_upper_penalty || has_global_elastic_upper; const bool has_elastic_columns = has_elastic_lower || has_elastic_upper; // For the rows const bool has_local_rhs_penalty = local_rhs_penalty; const bool has_global_elastic_rhs = global_rhs_penalty >= 0; const bool has_elastic_rows = has_local_rhs_penalty || has_global_elastic_rhs; assert(has_elastic_columns || has_elastic_rows); - + if (has_elastic_columns) { // When defining names, need to know the column number HighsInt previous_num_col = lp.num_col_; @@ -1902,7 +1907,8 @@ HighsStatus Highs::elasticityFilter(const double global_lower_penalty, const dou const bool has_row_names = lp.row_names_.size() > 0; for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { // Get the penalty for violating the bounds on this row - const double penalty = has_local_rhs_penalty ? local_rhs_penalty[iRow] : global_rhs_penalty; + const double penalty = + has_local_rhs_penalty ? local_rhs_penalty[iRow] : global_rhs_penalty; // Negative penalty implies that the bounds cannot be violated if (penalty < 0) continue; const double lower = lp.row_lower_[iRow]; @@ -1975,20 +1981,20 @@ HighsStatus Highs::elasticityFilter(const double global_lower_penalty, const dou run_status = solveLp(); if (run_status != HighsStatus::kOk) - return elasticityFilterReturn(run_status, false, - original_num_col, original_num_row, - original_col_cost, original_col_lower, - original_col_upper, original_integrality); + return elasticityFilterReturn(run_status, false, original_num_col, + original_num_row, original_col_cost, + original_col_lower, original_col_upper, + original_integrality); if (kIisDevReport) this->writeSolution("", kSolutionStylePretty); // Model status should be optimal, unless model is unbounded assert(this->model_status_ == HighsModelStatus::kOptimal || this->model_status_ == HighsModelStatus::kUnbounded); if (!get_infeasible_row) - return elasticityFilterReturn(HighsStatus::kOk, false, - original_num_col, original_num_row, - original_col_cost, original_col_lower, - original_col_upper, original_integrality); + return elasticityFilterReturn(HighsStatus::kOk, false, original_num_col, + original_num_row, original_col_cost, + original_col_lower, original_col_upper, + original_integrality); const HighsSolution& solution = this->getSolution(); // Now fix e-variables that are positive and re-solve until e-LP is infeasible HighsInt loop_k = 0; @@ -2038,9 +2044,9 @@ HighsStatus Highs::elasticityFilter(const double global_lower_penalty, const dou HighsStatus run_status = solveLp(); if (run_status != HighsStatus::kOk) return elasticityFilterReturn(run_status, feasible_model, - original_num_col, original_num_row, - original_col_cost, original_col_lower, - original_col_upper, original_integrality); + original_num_col, original_num_row, + original_col_cost, original_col_lower, + original_col_upper, original_integrality); if (kIisDevReport) this->writeSolution("", kSolutionStylePretty); HighsModelStatus model_status = this->getModelStatus(); if (model_status == HighsModelStatus::kInfeasible) break; @@ -2091,9 +2097,9 @@ HighsStatus Highs::elasticityFilter(const double global_lower_penalty, const dou int(loop_k), int(num_enforced_col_ecol), int(num_enforced_row_ecol)); return elasticityFilterReturn(HighsStatus::kOk, feasible_model, - original_num_col, original_num_row, - original_col_cost, original_col_lower, - original_col_upper, original_integrality); + original_num_col, original_num_row, + original_col_cost, original_col_lower, + original_col_upper, original_integrality); } HighsStatus Highs::extractIis(HighsInt& num_iis_col, HighsInt& num_iis_row, diff --git a/src/lp_data/HighsLpUtils.cpp b/src/lp_data/HighsLpUtils.cpp index 248c6aa842..ebe803bab0 100644 --- a/src/lp_data/HighsLpUtils.cpp +++ b/src/lp_data/HighsLpUtils.cpp @@ -1613,6 +1613,9 @@ void changeLpIntegrality(HighsLp& lp, if (mask && !col_mask[col]) continue; lp.integrality_[col] = new_integrality[usr_col]; } + // If integrality_ contains only HighsVarType::kContinuous then + // clear it + if (!lp.isMip()) lp.integrality_.clear(); } void changeLpCosts(HighsLp& lp, const HighsIndexCollection& index_collection, From c790a205c058461e330f28ae445ca893c8736ac2 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 15 Aug 2024 14:40:33 +0100 Subject: [PATCH 064/194] Passes test2 in lp-feasibility-relaxation --- check/TestIis.cpp | 86 +++++++++- src/lp_data/HighsIis.h | 2 +- src/lp_data/HighsInterface.cpp | 295 ++++++++++++++++++++------------- 3 files changed, 263 insertions(+), 120 deletions(-) diff --git a/check/TestIis.cpp b/check/TestIis.cpp index ffd8a96b49..d68dd8388e 100644 --- a/check/TestIis.cpp +++ b/check/TestIis.cpp @@ -6,7 +6,7 @@ #include "catch.hpp" //#include "io/FilereaderLp.h" -const bool dev_run = false; +const bool dev_run = true; const double inf = kHighsInf; void testIis(const std::string& model, const HighsIis& iis); @@ -185,23 +185,103 @@ TEST_CASE("lp-get-iis-avgas", "[iis]") { } TEST_CASE("lp-feasibility-relaxation", "[iis]") { + // Using infeasible LP from AMPL documentation HighsLp lp; lp.num_col_ = 2; lp.num_row_ = 3; lp.col_cost_ = {1, -2}; lp.col_lower_ = {5, -inf}; lp.col_upper_ = {inf, inf}; + lp.col_names_ = {"X", "Y"}; lp.row_lower_ = {2, -inf, -inf}; lp.row_upper_ = {inf, 1, 20}; + lp.row_names_ = {"R0", "R1", "R2"}; lp.a_matrix_.start_ = {0, 3, 6}; lp.a_matrix_.index_ = {0, 1, 2, 0, 1, 2}; lp.a_matrix_.value_ = {-1, -3, 20, 21, 2, 1}; lp.integrality_ = {HighsVarType::kInteger, HighsVarType::kInteger}; Highs h; + h.setOptionValue("output_flag", dev_run); + const HighsSolution& solution = h.getSolution(); h.passModel(lp); // h.run(); - h.feasibilityRelaxation(1, 1, 1); - h.writeSolution("", 1); + + const bool all_tests = false; + const bool test0 = false || all_tests; + const bool test1 = false || all_tests; + const bool test2 = true || all_tests; + const bool test3 = false || all_tests; + if (test0) { + // Vanilla feasibility relaxation + if (dev_run) + printf( + "==============================\n" + "Vanilla feasibility relaxation\n" + "==============================\n"); + REQUIRE(h.feasibilityRelaxation(1, 1, 1) == HighsStatus::kOk); + // Should get solution (1, 1) + h.writeSolution("", 1); + REQUIRE(solution.col_value[0] == 1); + REQUIRE(solution.col_value[1] == 1); + } + + if (test1) { + // Now we want to force all variable lower bounds to be respected + if (dev_run) + printf( + "========================\n" + "Respect all lower bounds\n" + "========================\n"); + h.feasibilityRelaxation(-1, 1, 1); + // Should get solution (5, 1) + h.writeSolution("", 1); + REQUIRE(solution.col_value[0] == 5); + REQUIRE(solution.col_value[1] == 1); + } + + if (test2) { + if (dev_run) + printf( + "===============================\n" + "Local penalties RHS {1, -1, 10}\n" + "===============================\n"); + // Now test local penalties + // + // constraint 0: normal weight + // + // constraint 1: cannot be violated + // + // constraint 2: rather not violate + // + std::vector local_rhs_penalty = {1, -1, 10}; + h.feasibilityRelaxation(1, 1, 0, nullptr, nullptr, + local_rhs_penalty.data()); + // Should get slacks (-3, 4, 0) + h.writeSolution("", 1); + REQUIRE(solution.row_value[0] == lp.row_lower_[0] - 3); + REQUIRE(solution.row_value[1] == lp.row_upper_[1] - 4); + REQUIRE(solution.row_value[2] == lp.row_upper_[2]); + } + + if (test3) { + if (dev_run) + printf( + "==============================\n" + "Local penalties RHS {10, 1, 1}\n" + "==============================\n"); + // + // constraint 0: rather not violate + // + // constraint 1: normal weight + // + // constraint 2: normal weight + // + std::vector local_rhs_penalty = {10, 1, 1}; + // Should get row activities (18, 2, -1) + REQUIRE(solution.row_value[0] == 18); + REQUIRE(solution.row_value[1] == 2); + REQUIRE(solution.row_value[2] == -1); + } } void testIis(const std::string& model, const HighsIis& iis) { diff --git a/src/lp_data/HighsIis.h b/src/lp_data/HighsIis.h index cb01ea3500..f49665de32 100644 --- a/src/lp_data/HighsIis.h +++ b/src/lp_data/HighsIis.h @@ -16,7 +16,7 @@ #include "lp_data/HighsLp.h" -const bool kIisDevReport = false; +const bool kIisDevReport = true; enum IisBoundStatus { kIisBoundStatusDropped = -1, diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 1d28dc2037..4b3726b217 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -1709,6 +1709,15 @@ HighsStatus Highs::elasticityFilterReturn( assert(lp.num_col_ == original_num_col); assert(lp.num_row_ == original_num_row); + if (return_status == HighsStatus::kOk) { + // Solution is invalidated by deleting rows and columns, but + // primal values are correct. Have to recompute row acivities, + // though + this->model_.lp_.a_matrix_.productQuad(this->solution_.row_value, + this->solution_.col_value); + this->solution_.value_valid = true; + } + // If the model is feasible, then the status of model is not known if (feasible_model) this->model_status_ = HighsModelStatus::kNotset; @@ -1721,6 +1730,7 @@ HighsStatus Highs::elasticityFilter( const double* local_upper_penalty, const double* local_rhs_penalty, const bool get_infeasible_row, std::vector& infeasible_row_subset) { + this->writeModel("infeasible.mps"); // Solve the feasibility relaxation problem for the given penalties, // continuing to act as the elasticity filter get_infeasible_row is // true, resulting in an infeasibility subset for further refinement @@ -1766,8 +1776,9 @@ HighsStatus Highs::elasticityFilter( const HighsLp& lp = this->model_.lp_; HighsInt evar_ix = lp.num_col_; HighsStatus run_status; - const bool write_model = false; + const bool write_model = true; HighsInt col_ecol_offset; + HighsInt row_ecol_offset; // Take copies of the original model dimensions and column data // vectors, as they will be modified in forming the e-LP const HighsInt original_num_col = lp.num_col_; @@ -1782,8 +1793,8 @@ HighsStatus Highs::elasticityFilter( run_status = this->changeColsCost(0, lp.num_col_ - 1, zero_costs.data()); assert(run_status == HighsStatus::kOk); - // Set any integrality to continuous - if (lp.integrality_.size()) { + if (get_infeasible_row && lp.integrality_.size()) { + // Set any integrality to continuous std::vector all_continuous; all_continuous.assign(original_num_col, HighsVarType::kContinuous); run_status = @@ -1808,16 +1819,37 @@ HighsStatus Highs::elasticityFilter( assert(has_elastic_columns || has_elastic_rows); if (has_elastic_columns) { + // Accumulate bounds to be used for columns + std::vector col_lower; + std::vector col_upper; // When defining names, need to know the column number HighsInt previous_num_col = lp.num_col_; - HighsInt previous_num_row = lp.num_row_; const bool has_col_names = lp.col_names_.size() > 0; erow_start.push_back(0); for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { const double lower = lp.col_lower_[iCol]; const double upper = lp.col_upper_[iCol]; + // Original bounds used unless e-variable introduced + col_lower.push_back(lower); + col_upper.push_back(upper); // Free columns have no erow if (lower <= -kHighsInf && upper >= kHighsInf) continue; + + // Get the penalty for violating the lower bounds on this column + const double lower_penalty = has_local_lower_penalty + ? local_lower_penalty[iCol] + : global_lower_penalty; + // Negative lower penalty and infininte upper bound implies that the + // bounds cannot be violated + if (lower_penalty < 0 && upper >= kHighsInf) continue; + + // Get the penalty for violating the upper bounds on this column + const double upper_penalty = has_local_upper_penalty + ? local_upper_penalty[iCol] + : global_upper_penalty; + // Infininte upper bound and negative lower penalty implies that the + // bounds cannot be violated + if (lower <= -kHighsInf && upper_penalty < 0) continue; erow_lower.push_back(lower); erow_upper.push_back(upper); if (has_col_names) @@ -1826,26 +1858,34 @@ HighsStatus Highs::elasticityFilter( // Define the entry for x[iCol] erow_index.push_back(iCol); erow_value.push_back(1); - if (lower > -kHighsInf) { + if (lower > -kHighsInf && lower_penalty >= 0) { // New e_l variable col_of_ecol.push_back(iCol); if (has_col_names) ecol_name.push_back("col_" + std::to_string(iCol) + "_" + lp.col_names_[iCol] + "_lower"); + // Save the original lower bound on this column and free its + // lower bound bound_of_col_of_ecol.push_back(lower); + col_lower[iCol] = -kHighsInf; erow_index.push_back(evar_ix); erow_value.push_back(1); + ecol_cost.push_back(lower_penalty); evar_ix++; } - if (upper < kHighsInf) { + if (upper < kHighsInf && upper_penalty >= 0) { // New e_u variable col_of_ecol.push_back(iCol); if (has_col_names) ecol_name.push_back("col_" + std::to_string(iCol) + "_" + lp.col_names_[iCol] + "_upper"); + // Save the original upper bound on this column and free its + // upper bound bound_of_col_of_ecol.push_back(upper); + col_upper[iCol] = kHighsInf; erow_index.push_back(evar_ix); erow_value.push_back(-1); + ecol_cost.push_back(upper_penalty); evar_ix++; } erow_start.push_back(erow_index.size()); @@ -1859,25 +1899,25 @@ HighsStatus Highs::elasticityFilter( HighsInt num_new_nz = erow_start[num_new_row]; if (kIisDevReport) printf( - "Elasticity filter: For columns there are %d variables and %d" + "Elasticity filter: For columns there are %d variables and %d " "constraints\n", int(num_new_col), int(num_new_row)); - // Free the original columns - std::vector col_lower; - std::vector col_upper; - col_lower.assign(lp.num_col_, -kHighsInf); - col_upper.assign(lp.num_col_, kHighsInf); + // Apply the original column bound changes + assert(col_lower.size() == static_cast(lp.num_col_)); + assert(col_upper.size() == static_cast(lp.num_col_)); run_status = this->changeColsBounds(0, lp.num_col_ - 1, col_lower.data(), col_upper.data()); assert(run_status == HighsStatus::kOk); // Add the new columns - ecol_cost.assign(num_new_col, 1); ecol_lower.assign(num_new_col, 0); ecol_upper.assign(num_new_col, kHighsInf); run_status = this->addCols(num_new_col, ecol_cost.data(), ecol_lower.data(), ecol_upper.data(), 0, nullptr, nullptr, nullptr); assert(run_status == HighsStatus::kOk); // Add the new rows + assert(erow_start.size() == static_cast(num_new_row + 1)); + assert(erow_index.size() == static_cast(num_new_nz)); + assert(erow_value.size() == static_cast(num_new_nz)); run_status = this->addRows(num_new_row, erow_lower.data(), erow_upper.data(), num_new_nz, erow_start.data(), erow_index.data(), erow_value.data()); @@ -1886,10 +1926,13 @@ HighsStatus Highs::elasticityFilter( for (HighsInt iCol = 0; iCol < num_new_col; iCol++) this->passColName(previous_num_col + iCol, ecol_name[iCol]); for (HighsInt iRow = 0; iRow < num_new_row; iRow++) - this->passRowName(previous_num_row + iRow, erow_name[iRow]); + this->passRowName(original_num_row + iRow, erow_name[iRow]); } + assert(ecol_cost.size() == static_cast(num_new_col)); + assert(ecol_lower.size() == static_cast(num_new_col)); + assert(ecol_upper.size() == static_cast(num_new_col)); if (write_model) { - printf("\nAfter adding e-rows\n=============\n"); + printf("\nAfter adding %d e-rows\n=============\n", int(num_new_col)); bool output_flag; run_status = this->getOptionValue("output_flag", output_flag); this->setOptionValue("output_flag", true); @@ -1897,74 +1940,86 @@ HighsStatus Highs::elasticityFilter( this->setOptionValue("output_flag", output_flag); } } - // Add the columns corresponding to the e_L and e_U variables for - // the constraints - ecol_name.clear(); - std::vector ecol_start; - std::vector ecol_index; - std::vector ecol_value; - ecol_start.push_back(0); - const bool has_row_names = lp.row_names_.size() > 0; - for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { - // Get the penalty for violating the bounds on this row - const double penalty = - has_local_rhs_penalty ? local_rhs_penalty[iRow] : global_rhs_penalty; - // Negative penalty implies that the bounds cannot be violated - if (penalty < 0) continue; - const double lower = lp.row_lower_[iRow]; - const double upper = lp.row_upper_[iRow]; - if (lower > -kHighsInf) { - // Create an e-var for the row lower bound - row_of_ecol.push_back(iRow); - if (has_row_names) - ecol_name.push_back("row_" + std::to_string(iRow) + "_" + - lp.row_names_[iRow] + "_lower"); - bound_of_row_of_ecol.push_back(lower); - // Define the sub-matrix column - ecol_index.push_back(iRow); - ecol_value.push_back(1); - ecol_start.push_back(ecol_index.size()); - ecol_cost.push_back(penalty); - evar_ix++; - } - if (upper < kHighsInf) { - // Create an e-var for the row upper bound - row_of_ecol.push_back(iRow); - if (has_row_names) - ecol_name.push_back("row_" + std::to_string(iRow) + "_" + - lp.row_names_[iRow] + "_upper"); - bound_of_row_of_ecol.push_back(upper); - // Define the sub-matrix column - ecol_index.push_back(iRow); - ecol_value.push_back(-1); - ecol_start.push_back(ecol_index.size()); - ecol_cost.push_back(penalty); - evar_ix++; - } - } - HighsInt num_new_col = ecol_start.size() - 1; - HighsInt num_new_nz = ecol_start[num_new_col]; - ecol_lower.assign(num_new_col, 0); - ecol_upper.assign(num_new_col, kHighsInf); - HighsInt previous_num_col = lp.num_col_; - HighsInt row_ecol_offset = previous_num_col; - run_status = this->addCols(num_new_col, ecol_cost.data(), ecol_lower.data(), - ecol_upper.data(), num_new_nz, ecol_start.data(), - ecol_index.data(), ecol_value.data()); - assert(run_status == HighsStatus::kOk); - if (has_row_names) { - for (HighsInt iCol = 0; iCol < num_new_col; iCol++) - this->passColName(previous_num_col + iCol, ecol_name[iCol]); - } + if (has_elastic_rows) { + // Add the columns corresponding to the e_L and e_U variables for + // the constraints + HighsInt previous_num_col = lp.num_col_; + row_ecol_offset = previous_num_col; + ecol_name.clear(); + ecol_cost.clear(); + std::vector ecol_start; + std::vector ecol_index; + std::vector ecol_value; + ecol_start.push_back(0); + const bool has_row_names = lp.row_names_.size() > 0; + for (HighsInt iRow = 0; iRow < original_num_row; iRow++) { + // Get the penalty for violating the bounds on this row + const double penalty = + has_local_rhs_penalty ? local_rhs_penalty[iRow] : global_rhs_penalty; + // Negative penalty implies that the bounds cannot be violated + if (penalty < 0) continue; + const double lower = lp.row_lower_[iRow]; + const double upper = lp.row_upper_[iRow]; + if (lower > -kHighsInf) { + // Create an e-var for the row lower bound + row_of_ecol.push_back(iRow); + if (has_row_names) + ecol_name.push_back("row_" + std::to_string(iRow) + "_" + + lp.row_names_[iRow] + "_lower"); + bound_of_row_of_ecol.push_back(lower); + // Define the sub-matrix column + ecol_index.push_back(iRow); + ecol_value.push_back(1); + ecol_start.push_back(ecol_index.size()); + ecol_cost.push_back(penalty); + evar_ix++; + } + if (upper < kHighsInf) { + // Create an e-var for the row upper bound + row_of_ecol.push_back(iRow); + if (has_row_names) + ecol_name.push_back("row_" + std::to_string(iRow) + "_" + + lp.row_names_[iRow] + "_upper"); + bound_of_row_of_ecol.push_back(upper); + // Define the sub-matrix column + ecol_index.push_back(iRow); + ecol_value.push_back(-1); + ecol_start.push_back(ecol_index.size()); + ecol_cost.push_back(penalty); + evar_ix++; + } + } + HighsInt num_new_col = ecol_start.size() - 1; + HighsInt num_new_nz = ecol_start[num_new_col]; + ecol_lower.assign(num_new_col, 0); + ecol_upper.assign(num_new_col, kHighsInf); + assert(ecol_cost.size() == static_cast(num_new_col)); + assert(ecol_lower.size() == static_cast(num_new_col)); + assert(ecol_upper.size() == static_cast(num_new_col)); + assert(ecol_start.size() == static_cast(num_new_col + 1)); + assert(ecol_index.size() == static_cast(num_new_nz)); + assert(ecol_value.size() == static_cast(num_new_nz)); + run_status = this->addCols(num_new_col, ecol_cost.data(), ecol_lower.data(), + ecol_upper.data(), num_new_nz, ecol_start.data(), + ecol_index.data(), ecol_value.data()); + assert(run_status == HighsStatus::kOk); + if (has_row_names) { + for (HighsInt iCol = 0; iCol < num_new_col; iCol++) + this->passColName(previous_num_col + iCol, ecol_name[iCol]); + } - if (write_model) { - bool output_flag; - printf("\nAfter adding e-cols\n=============\n"); - run_status = this->getOptionValue("output_flag", output_flag); - this->setOptionValue("output_flag", true); - this->writeModel(""); - this->setOptionValue("output_flag", output_flag); + if (write_model) { + bool output_flag; + printf("\nAfter adding %d e-cols\n=============\n", int(num_new_col)); + run_status = this->getOptionValue("output_flag", output_flag); + this->setOptionValue("output_flag", true); + this->writeModel(""); + this->setOptionValue("output_flag", output_flag); + } } + + if (write_model) this->writeModel("elastic.mps"); + // Lambda for gathering data when solving an LP auto solveLp = [&]() -> HighsStatus { HighsIisInfo iis_info; @@ -1980,6 +2035,7 @@ HighsStatus Highs::elasticityFilter( }; run_status = solveLp(); + if (run_status != HighsStatus::kOk) return elasticityFilterReturn(run_status, false, original_num_col, original_num_row, original_col_cost, @@ -2021,19 +2077,22 @@ HighsStatus Highs::elasticityFilter( } } } - for (HighsInt eCol = 0; eCol < row_of_ecol.size(); eCol++) { - HighsInt iRow = row_of_ecol[eCol]; - if (solution.col_value[row_ecol_offset + eCol] > - this->options_.primal_feasibility_tolerance) { - if (kIisDevReport) - printf( - "E-row %2d (column %2d) corresponds to row %2d with bound %g " - "and has solution value %g\n", - int(eCol), int(row_ecol_offset + eCol), int(iRow), - bound_of_row_of_ecol[eCol], - solution.col_value[row_ecol_offset + eCol]); - this->changeColBounds(row_ecol_offset + eCol, 0, 0); - num_fixed++; + if (has_elastic_rows) { + for (HighsInt eCol = 0; eCol < row_of_ecol.size(); eCol++) { + HighsInt iRow = row_of_ecol[eCol]; + if (solution.col_value[row_ecol_offset + eCol] > + this->options_.primal_feasibility_tolerance) { + if (kIisDevReport) + printf( + "E-row %2d (column %2d) corresponds to row %2d with bound " + "%g " + "and has solution value %g\n", + int(eCol), int(row_ecol_offset + eCol), int(iRow), + bound_of_row_of_ecol[eCol], + solution.col_value[row_ecol_offset + eCol]); + this->changeColBounds(row_ecol_offset + eCol, 0, 0); + num_fixed++; + } } } if (num_fixed == 0) { @@ -2056,31 +2115,35 @@ HighsStatus Highs::elasticityFilter( infeasible_row_subset.clear(); HighsInt num_enforced_col_ecol = 0; HighsInt num_enforced_row_ecol = 0; - for (HighsInt eCol = 0; eCol < col_of_ecol.size(); eCol++) { - HighsInt iCol = col_of_ecol[eCol]; - if (lp.col_upper_[col_ecol_offset + eCol] == 0) { - num_enforced_col_ecol++; - printf( - "Col e-col %2d (column %2d) corresponds to column %2d with bound %g " - "and is enforced\n", - int(eCol), int(col_ecol_offset + eCol), int(iCol), - bound_of_col_of_ecol[eCol]); - } - } - for (HighsInt eCol = 0; eCol < row_of_ecol.size(); eCol++) { - HighsInt iRow = row_of_ecol[eCol]; - if (lp.col_upper_[row_ecol_offset + eCol] == 0) { - num_enforced_row_ecol++; - infeasible_row_subset.push_back(iRow); - if (kIisDevReport) + if (has_elastic_columns) { + for (HighsInt eCol = 0; eCol < col_of_ecol.size(); eCol++) { + HighsInt iCol = col_of_ecol[eCol]; + if (lp.col_upper_[col_ecol_offset + eCol] == 0) { + num_enforced_col_ecol++; printf( - "Row e-col %2d (column %2d) corresponds to row %2d with bound " - "%g and is enforced\n", - int(eCol), int(row_ecol_offset + eCol), int(iRow), - bound_of_row_of_ecol[eCol]); + "Col e-col %2d (column %2d) corresponds to column %2d with bound " + "%g " + "and is enforced\n", + int(eCol), int(col_ecol_offset + eCol), int(iCol), + bound_of_col_of_ecol[eCol]); + } + } + } + if (has_elastic_rows) { + for (HighsInt eCol = 0; eCol < row_of_ecol.size(); eCol++) { + HighsInt iRow = row_of_ecol[eCol]; + if (lp.col_upper_[row_ecol_offset + eCol] == 0) { + num_enforced_row_ecol++; + infeasible_row_subset.push_back(iRow); + if (kIisDevReport) + printf( + "Row e-col %2d (column %2d) corresponds to row %2d with bound " + "%g and is enforced\n", + int(eCol), int(row_ecol_offset + eCol), int(iRow), + bound_of_row_of_ecol[eCol]); + } } } - if (feasible_model) assert(num_enforced_col_ecol == 0 && num_enforced_row_ecol == 0); From 010082c4b372e5c57099ed137aefd1fb0e581c59 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 15 Aug 2024 15:40:01 +0100 Subject: [PATCH 065/194] Passes AMPL model unit tests, and objective tests for galenet and woodinfe --- check/TestIis.cpp | 40 ++++++++++++++++++++++++++++------ src/lp_data/HighsInterface.cpp | 7 +++++- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/check/TestIis.cpp b/check/TestIis.cpp index d68dd8388e..fe3efbb3de 100644 --- a/check/TestIis.cpp +++ b/check/TestIis.cpp @@ -4,9 +4,8 @@ #include "HCheckConfig.h" #include "Highs.h" #include "catch.hpp" -//#include "io/FilereaderLp.h" -const bool dev_run = true; +const bool dev_run = false; const double inf = kHighsInf; void testIis(const std::string& model, const HighsIis& iis); @@ -15,6 +14,11 @@ void testMps(std::string& model, const HighsInt iis_strategy, const HighsModelStatus require_model_status = HighsModelStatus::kInfeasible); +void testFeasibilityRelaxation( + std::string& model, const double lower_penalty, const double upper_penalty, + const double rhs_penalty, + const double require_feasibility_objective_function_value); + TEST_CASE("lp-incompatible-bounds", "[iis]") { // LP has row0 and col2 with inconsistent bounds. // @@ -209,7 +213,7 @@ TEST_CASE("lp-feasibility-relaxation", "[iis]") { const bool all_tests = false; const bool test0 = false || all_tests; const bool test1 = false || all_tests; - const bool test2 = true || all_tests; + const bool test2 = false || all_tests; const bool test3 = false || all_tests; if (test0) { // Vanilla feasibility relaxation @@ -277,11 +281,19 @@ TEST_CASE("lp-feasibility-relaxation", "[iis]") { // constraint 2: normal weight // std::vector local_rhs_penalty = {10, 1, 1}; - // Should get row activities (18, 2, -1) - REQUIRE(solution.row_value[0] == 18); - REQUIRE(solution.row_value[1] == 2); - REQUIRE(solution.row_value[2] == -1); + h.feasibilityRelaxation(1, 1, 0, nullptr, nullptr, + local_rhs_penalty.data()); + // Should get slacks (18, 2, -1) + REQUIRE(solution.row_value[0] == lp.row_lower_[0] + 18); + REQUIRE(solution.row_value[1] == lp.row_upper_[1] - 2); + REQUIRE(solution.row_value[2] == lp.row_upper_[2] + 1); } + std::string model = "galenet"; + testFeasibilityRelaxation(model, 1, 1, 1, 28.0); + model = "woodinfe"; + testFeasibilityRelaxation(model, 1, 1, 1, 15.0); + model = "avgas"; + testFeasibilityRelaxation(model, 1, 1, 1, 0); } void testIis(const std::string& model, const HighsIis& iis) { @@ -436,3 +448,17 @@ void testMps(std::string& model, const HighsInt iis_strategy, REQUIRE(num_iis_row == 0); } } + +void testFeasibilityRelaxation( + std::string& model, const double lower_penalty, const double upper_penalty, + const double rhs_penalty, + const double require_feasibility_objective_function_value) { + std::string model_file = + std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; + Highs h; + h.readModel(model_file); + REQUIRE(h.feasibilityRelaxation(lower_penalty, upper_penalty, rhs_penalty) == + HighsStatus::kOk); + REQUIRE(h.getInfo().objective_function_value == + require_feasibility_objective_function_value); +} diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 4b3726b217..ae94f570fc 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -1682,6 +1682,7 @@ HighsStatus Highs::elasticityFilterReturn( const std::vector original_col_upper, const std::vector original_integrality) { const HighsLp& lp = this->model_.lp_; + double objective_function_value = info_.objective_function_value; // Delete any additional rows and columns, and restore the original // column costs and bounds HighsStatus run_status; @@ -1716,6 +1717,10 @@ HighsStatus Highs::elasticityFilterReturn( this->model_.lp_.a_matrix_.productQuad(this->solution_.row_value, this->solution_.col_value); this->solution_.value_valid = true; + // Set the feasibility objective and any KKT failures + info_.objective_function_value = objective_function_value; + getKktFailures(options_, model_, solution_, basis_, info_); + info_.valid = true; } // If the model is feasible, then the status of model is not known @@ -1776,7 +1781,7 @@ HighsStatus Highs::elasticityFilter( const HighsLp& lp = this->model_.lp_; HighsInt evar_ix = lp.num_col_; HighsStatus run_status; - const bool write_model = true; + const bool write_model = false; HighsInt col_ecol_offset; HighsInt row_ecol_offset; // Take copies of the original model dimensions and column data From f3ecbb92a63d0d9e04b633c33523a78fa39dc21b Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 15 Aug 2024 16:22:27 +0100 Subject: [PATCH 066/194] Silenced unit tests and adde C API test for FeasibilityRelaxation --- FEATURES.md | 4 ++ check/TestCAPI.c | 85 +++++++++++++++++++++++++++------- check/TestIis.cpp | 1 + src/interfaces/highs_c_api.cpp | 13 ++++++ src/interfaces/highs_c_api.h | 32 +++++++++++++ src/lp_data/HighsIis.h | 2 +- src/lp_data/HighsInterface.cpp | 5 +- 7 files changed, 122 insertions(+), 20 deletions(-) diff --git a/FEATURES.md b/FEATURES.md index c6db2f0380..6c66f9d67b 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -10,5 +10,9 @@ Introduced `const double kHighsUndefined` as value of undefined values in a user Added `Highs::setSolution(const HighsInt num_entries, const HighsInt* index, const double* value);` to allow a sparse primal solution to be defined. When a MIP is solved to do this, the value of (new) option `mip_max_start_nodes` is used for `mip_max_nodes` to avoid excessive cost +Added options `write_presolved_model_to_file` and `write_presolved_model_file` so that presolved model can be written via a command line option + +Added `Highs::feasibilityRelaxation` to solve the problem of minimizing a (possibly weighted) sum of (allowable) infeasibilities in an LP/MIP. + diff --git a/check/TestCAPI.c b/check/TestCAPI.c index dff94ec653..d4fd3aaa9b 100644 --- a/check/TestCAPI.c +++ b/check/TestCAPI.c @@ -1586,6 +1586,55 @@ void test_ranging() { Highs_destroy(highs); } +void test_feasibilityRelaxation() { + void* highs; + highs = Highs_create(); + const double kHighsInf = Highs_getInfinity(highs); + Highs_setBoolOptionValue(highs, "output_flag", dev_run); + HighsInt return_status; + + HighsInt num_col = 2; + HighsInt num_row = 3; + HighsInt num_nz = 6; + HighsInt a_format = kHighsMatrixFormatColwise; + HighsInt sense = kHighsObjSenseMinimize; + double offset = 0; + double col_cost[2] = {1, -2}; + double col_lower[2] = {5, -kHighsInf}; + double col_upper[2] = {kHighsInf, kHighsInf}; + double row_lower[3] = {2, -kHighsInf, -kHighsInf}; + double row_upper[3] = {kHighsInf, 1, 20}; + HighsInt a_start[2] = {0, 3}; + HighsInt a_index[6] = {0, 1, 2, 0, 1, 2}; + double a_value[6] = {-1, -3, 20, 21, 2, 1}; + HighsInt integrality[2] = {kHighsVarTypeInteger, kHighsVarTypeInteger}; + + Highs_passMip(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, + integrality); + Highs_feasibilityRelaxation(highs, 1, 1, 1, NULL, NULL, NULL); + double objective_function_value; + Highs_getDoubleInfoValue(highs, "objective_function_value", &objective_function_value); + double* col_value = (double*)malloc(sizeof(double) * num_col); + double* col_dual = (double*)malloc(sizeof(double) * num_col); + double* row_value = (double*)malloc(sizeof(double) * num_row); + double* row_dual = (double*)malloc(sizeof(double) * num_row); + return_status = Highs_getSolution(highs, col_value, col_dual, row_value, row_dual); + assert( return_status == kHighsStatusOk ); + assertDoubleValuesEqual("objective_function_value", objective_function_value, 5); + assertDoubleValuesEqual("solution_value[0]", col_value[0], 1); + assertDoubleValuesEqual("solution_value[1]", col_value[1], 1); + + free(col_value); + free(col_dual); + free(row_value); + free(row_dual); + + Highs_destroy(highs); +} + void test_callback() { HighsInt num_col = 7; HighsInt num_row = 1; @@ -1710,6 +1759,7 @@ void test_getModel() { assert( highsIntArraysEqual(num_nz, ck_a_index, a_index) ); assert( doubleArraysEqual(num_nz, ck_a_value, a_value) ); + Highs_destroy(highs); } /* @@ -1758,23 +1808,24 @@ void test_setSolution() { } */ 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_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(); return 0; } // test_setSolution(); diff --git a/check/TestIis.cpp b/check/TestIis.cpp index fe3efbb3de..bc5c991b9b 100644 --- a/check/TestIis.cpp +++ b/check/TestIis.cpp @@ -456,6 +456,7 @@ void testFeasibilityRelaxation( std::string model_file = std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; Highs h; + h.setOptionValue("output_flag", dev_run); h.readModel(model_file); REQUIRE(h.feasibilityRelaxation(lower_penalty, upper_penalty, rhs_penalty) == HighsStatus::kOk); diff --git a/src/interfaces/highs_c_api.cpp b/src/interfaces/highs_c_api.cpp index 59b73668b6..bbdd3f5e59 100644 --- a/src/interfaces/highs_c_api.cpp +++ b/src/interfaces/highs_c_api.cpp @@ -1344,6 +1344,19 @@ HighsInt Highs_getRanging( return status; } +HighsInt Highs_feasibilityRelaxation(void* highs, + const double global_lower_penalty, + const double global_upper_penalty, + const double global_rhs_penalty, + const double* local_lower_penalty, + const double* local_upper_penalty, + const double* local_rhs_penalty) { + return (HighsInt)((Highs*)highs) + ->feasibilityRelaxation(global_lower_penalty, global_upper_penalty, + global_rhs_penalty, local_lower_penalty, + local_upper_penalty, local_rhs_penalty); +} + void Highs_resetGlobalScheduler(HighsInt blocking) { Highs::resetGlobalScheduler(blocking != 0); } diff --git a/src/interfaces/highs_c_api.h b/src/interfaces/highs_c_api.h index 801ddf332e..46f9b683c1 100644 --- a/src/interfaces/highs_c_api.h +++ b/src/interfaces/highs_c_api.h @@ -2164,6 +2164,38 @@ HighsInt Highs_getRanging( double* row_bound_dn_value, double* row_bound_dn_objective, HighsInt* row_bound_dn_in_var, HighsInt* row_bound_dn_ou_var); +/** + * Compute the solution corresponding to a (possibly weighted) sum of + * (allowable) infeasibilities in an LP/MIP. + * + * If local penalties are not defined, pass NULL, and the global + * penalty will be used. Negative penalty values imply that the bound + * or RHS value cannot be violated + * + * @param highs A pointer to the Highs instance. + * @param const double global_lower_penalty The penalty for violating lower + * bounds on variables + * @param const double global_upper_penalty The penalty for violating upper + * bounds on variables + * @param const double global_rhs_penalty The penalty for violating constraint + * RHS values + * @param const double* local_lower_penalty The penalties for violating specific + * lower bounds on variables + * @param const double* local_upper_penalty The penalties for violating specific + * upper bounds on variables + * @param const double* local_rhs_penalty The penalties for violating specific + * constraint RHS values + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ + +HighsInt Highs_feasibilityRelaxation(void* highs, + const double global_lower_penalty, + const double global_upper_penalty, + const double global_rhs_penalty, + const double* local_lower_penalty, + const double* local_upper_penalty, + const double* local_rhs_penalty); + /** * Releases all resources held by the global scheduler instance. * diff --git a/src/lp_data/HighsIis.h b/src/lp_data/HighsIis.h index f49665de32..cb01ea3500 100644 --- a/src/lp_data/HighsIis.h +++ b/src/lp_data/HighsIis.h @@ -16,7 +16,7 @@ #include "lp_data/HighsLp.h" -const bool kIisDevReport = true; +const bool kIisDevReport = false; enum IisBoundStatus { kIisBoundStatusDropped = -1, diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index ae94f570fc..91881b3aee 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -1735,7 +1735,7 @@ HighsStatus Highs::elasticityFilter( const double* local_upper_penalty, const double* local_rhs_penalty, const bool get_infeasible_row, std::vector& infeasible_row_subset) { - this->writeModel("infeasible.mps"); + // this->writeModel("infeasible.mps"); // Solve the feasibility relaxation problem for the given penalties, // continuing to act as the elasticity filter get_infeasible_row is // true, resulting in an infeasibility subset for further refinement @@ -1896,7 +1896,8 @@ HighsStatus Highs::elasticityFilter( erow_start.push_back(erow_index.size()); HighsInt row_nz = erow_start[erow_start.size() - 1] - erow_start[erow_start.size() - 2]; - printf("eRow for column %d has %d nonzeros\n", int(iCol), int(row_nz)); + // printf("eRow for column %d has %d nonzeros\n", int(iCol), + // int(row_nz)); assert(row_nz == 2 || row_nz == 3); } HighsInt num_new_col = col_of_ecol.size(); From 4073ede75032555cfc579585304fc82719bcbde2 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 16 Aug 2024 17:25:33 +0100 Subject: [PATCH 067/194] Restored full C API unit test and corrected references to format of Hessian in highs_c_api.h --- check/TestCAPI.c | 34 +++++++++++++++++----------------- src/interfaces/highs_c_api.h | 32 +++++++++++++++++++++----------- 2 files changed, 38 insertions(+), 28 deletions(-) diff --git a/check/TestCAPI.c b/check/TestCAPI.c index d4fd3aaa9b..53702a320a 100644 --- a/check/TestCAPI.c +++ b/check/TestCAPI.c @@ -1808,24 +1808,24 @@ void test_setSolution() { } */ 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(); + 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_getModel(); return 0; } // test_setSolution(); diff --git a/src/interfaces/highs_c_api.h b/src/interfaces/highs_c_api.h index 46f9b683c1..a53518e12e 100644 --- a/src/interfaces/highs_c_api.h +++ b/src/interfaces/highs_c_api.h @@ -229,11 +229,14 @@ HighsInt Highs_mipCall(const HighsInt num_col, const HighsInt num_row, * @param q_format The format of the Hessian matrix in the form of a * `kHighsHessianStatus` constant. If q_num_nz > 0, this must * be `kHighsHessianFormatTriangular`. - * @param q_start The Hessian matrix is provided in the same format as the - * constraint matrix, using `q_start`, `q_index`, and `q_value` - * in the place of `a_start`, `a_index`, and `a_value`. + * @param q_start The Hessian matrix is provided to HiGHS as the lower + * triangular component in compressed sparse column form + * (or, equivalently, as the upper triangular component + * in compressed sparse row form). The sparse matrix consists + * of three arrays, `q_start`, `q_index`, and `q_value`. + * `q_start` is an array of length [num_col]. * @param q_index An array of length [q_num_nz] with indices of matrix - * sentries. + * entries. * @param q_value An array of length [q_num_nz] with values of matrix entries. * * @returns A `kHighsStatus` constant indicating whether the call succeeded. @@ -498,10 +501,13 @@ HighsInt Highs_passMip(void* highs, const HighsInt num_col, * @param a_index An array of length [num_nz] with indices of matrix * entries. * @param a_value An array of length [num_nz] with values of matrix entries. - * @param q_start The Hessian matrix is provided in the same format as the - * constraint matrix, using `q_start`, `q_index`, and - * `q_value` in the place of `a_start`, `a_index`, and - * `a_value`. If the model is linear, pass NULL. + * @param q_start The Hessian matrix is provided to HiGHS as the lower + * triangular component in compressed sparse column form + * (or, equivalently, as the upper triangular component + * in compressed sparse row form). The sparse matrix consists + * of three arrays, `q_start`, `q_index`, and `q_value`. + * `q_start` is an array of length [num_col]. If the model + * is linear, pass NULL. * @param q_index An array of length [q_num_nz] with indices of matrix * entries. If the model is linear, pass NULL. * @param q_value An array of length [q_num_nz] with values of matrix @@ -531,9 +537,13 @@ HighsInt Highs_passModel(void* highs, const HighsInt num_col, * @param num_nz The number of non-zero elements in the Hessian matrix. * @param format The format of the Hessian matrix as a `kHighsHessianFormat` * constant. This must be `kHighsHessianFormatTriangular`. - * @param start The Hessian matrix is provided to HiGHS as the upper - * triangular component in compressed sparse column form. The - * sparse matrix consists of three arrays, `start`, `index`, + * @param start The Hessian matrix is provided to HiGHS as the lower + * triangular component in compressed sparse column form + * (or, equivalently, as the upper triangular component + * in compressed sparse row form), using `q_start`, `q_index`, + * and `q_value`.The Hessian matrix is provided to HiGHS as the + * lower triangular component in compressed sparse column form. + * The sparse matrix consists of three arrays, `start`, `index`, * and `value`. `start` is an array of length [num_col] * containing the starting index of each column in `index`. * @param index An array of length [num_nz] with indices of matrix entries. From 20b87aadbe0f30d42001961e67245638e96fcb70 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 16 Aug 2024 17:52:47 +0100 Subject: [PATCH 068/194] Added HighsHessian.md and HighsModel.md plus documentation of HessianFormat in enums.md --- docs/make.jl | 2 ++ docs/src/structures/classes/HighsHessian.md | 9 +++++++++ docs/src/structures/classes/HighsModel.md | 7 +++++++ docs/src/structures/classes/HighsSparseMatrix.md | 4 ++-- docs/src/structures/classes/index.md | 2 ++ docs/src/structures/enums.md | 7 +++++++ 6 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 docs/src/structures/classes/HighsHessian.md create mode 100644 docs/src/structures/classes/HighsModel.md diff --git a/docs/make.jl b/docs/make.jl index 132d36c4de..0c65c9d2b4 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -61,6 +61,8 @@ Documenter.makedocs( "structures/classes/index.md", "structures/classes/HighsSparseMatrix.md", "structures/classes/HighsLp.md", + "structures/classes/HighsHessian.md", + "structures/classes/HighsModel.md", "structures/classes/HighsSolution.md", "structures/classes/HighsBasis.md", "structures/classes/HighsInfo.md", diff --git a/docs/src/structures/classes/HighsHessian.md b/docs/src/structures/classes/HighsHessian.md new file mode 100644 index 0000000000..5e165ff8ee --- /dev/null +++ b/docs/src/structures/classes/HighsHessian.md @@ -0,0 +1,9 @@ +# HighsHessian + +A Hessian matrix is communicated via an instance of the HighsHessian class. + +- dim_: Scalar of type integer - Dimension of the Hessian +- format\_: Scalar of [HessianFormat](@ref) type - Format of the Hessian +- start\_: Vector of integer type - Start of each compressed column in the Hessian +- index\_: Vector of integer type - Indices of the nonzeros in the Hessian +- value\_: Vector of double type - Values of the nonzeros in the Hessian diff --git a/docs/src/structures/classes/HighsModel.md b/docs/src/structures/classes/HighsModel.md new file mode 100644 index 0000000000..21aa4c1e80 --- /dev/null +++ b/docs/src/structures/classes/HighsModel.md @@ -0,0 +1,7 @@ +# HighsModel + +A QP model is communicated via an instance of the HighsModel class + +- lp\_: Instance of [HighsLp](@ref) class - LP components of the model + +- hessian\_: Instance of [HighsHessian](@ref) class - Hessian matrix diff --git a/docs/src/structures/classes/HighsSparseMatrix.md b/docs/src/structures/classes/HighsSparseMatrix.md index fdda98936d..411c9bcc1e 100644 --- a/docs/src/structures/classes/HighsSparseMatrix.md +++ b/docs/src/structures/classes/HighsSparseMatrix.md @@ -2,9 +2,9 @@ The constraint matrix of an LP model is communicated via an instance of the HighsSparseMatrix class -- format\_: Scalar of MatrixFormat type - Format of the matrix +- format\_: Scalar of [MatrixFormat](@ref) type - Format of the matrix - num\_col\_ : Scalar of integer type - Number of columns in the matrix - num\_row\_: Scalar of integer type - Number of rows in the matrix -- start\_: Vector of integer type - Start of each compressed vector in the matrixs +- start\_: Vector of integer type - Start of each compressed vector in the matrix - index\_: Vector of integer type - Indices of the nonzeros in the matrix - value\_: Vector of double type - Values of the nonzeros in the matrix diff --git a/docs/src/structures/classes/index.md b/docs/src/structures/classes/index.md index b907914083..f2f5ed4326 100644 --- a/docs/src/structures/classes/index.md +++ b/docs/src/structures/classes/index.md @@ -4,6 +4,8 @@ The data members of fundamental classes in HiGHS are defined in this section. * [HighsSparseMatrix](@ref) * [HighsLp](@ref) + * [HighsHessian](@ref) + * [HighsModel](@ref) * [HighsSolution](@ref) * [HighsBasis](@ref) * [HighsInfo](@ref) diff --git a/docs/src/structures/enums.md b/docs/src/structures/enums.md index 7975e41fb0..d88306fd15 100644 --- a/docs/src/structures/enums.md +++ b/docs/src/structures/enums.md @@ -41,6 +41,13 @@ This defines the feasible values of a variable within a model: * `kSemiContinuous`: The variable must be zero or take continuous values between its bounds * `kSemiInteger`: The variable must be zero or take integer values between its bounds +## HessianFormat + +This defines the format of a [HighsHessian](@ref): + + * `kTriangular`: The lower triangular component of the Hessian is stored column-wise or, equivalently, the upper triangular component is stored row-wise + * `kSquare`: The whole Hessian ``Q`` is stored column-wise. This is for input only: internally the lower triangular component of ``(Q+Q^T)/2`` will be stored + ## SolutionStatus This defines the nature of any primal or dual solution information: From ad2986350b126455799fa8eac71e84c70194beda Mon Sep 17 00:00:00 2001 From: Sebastian Luther Date: Sat, 17 Aug 2024 21:34:25 +0200 Subject: [PATCH 069/194] writePrimalSolution: flush file after each call (#1880) --- src/lp_data/HighsModelUtils.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lp_data/HighsModelUtils.cpp b/src/lp_data/HighsModelUtils.cpp index 0e4db30085..5d887167b7 100644 --- a/src/lp_data/HighsModelUtils.cpp +++ b/src/lp_data/HighsModelUtils.cpp @@ -243,6 +243,7 @@ void writePrimalSolution(FILE* file, const HighsLogOptions& log_options, ss << "\n"; highsFprintfString(file, log_options, ss.str()); } + fflush(file); } void writeModelSolution(FILE* file, const HighsLogOptions& log_options, From f4b50beccbb7afca974c89bf03d07db268fa8424 Mon Sep 17 00:00:00 2001 From: Sebastian Luther Date: Sun, 18 Aug 2024 11:15:22 +0200 Subject: [PATCH 070/194] highspy: Expose new feasibility relaxation feature --- examples/call_highs_from_python.py | 155 +++++++++++++++++++++ src/highs_bindings.cpp | 60 +++++++++ src/highspy/__init__.py | 6 + src/highspy/_core.pyi | 209 ++++++++++++++++++++++------- 4 files changed, 378 insertions(+), 52 deletions(-) diff --git a/examples/call_highs_from_python.py b/examples/call_highs_from_python.py index 5de97e9b15..2ff88f281b 100644 --- a/examples/call_highs_from_python.py +++ b/examples/call_highs_from_python.py @@ -339,3 +339,158 @@ def user_interrupt_callback( for icol in range(num_var-2, num_var): print(icol, solution.col_value[icol], h.basisStatusToString(basis.col_status[icol])) + +print("computing IIS for lp-incompatible-bounds") +""" +LP has row0 and col2 with inconsistent bounds. + +When prioritising rows, row0 and its constituent columns (1, 2) should be found +When prioritising columns, col2 and its constituent rows (0, 1) should be found +""" +# Define the LP +lp = highspy.HighsLp() +lp.num_col_ = 3 +lp.num_row_ = 2 +lp.col_cost_ = np.array([0, 0, 0], dtype=np.double) +lp.col_lower_ = np.array([0, 0, 0], dtype=np.double) +lp.col_upper_ = np.array([1, 1, -1], dtype=np.double) +lp.row_lower_ = np.array([1, 0], dtype=np.double) +lp.row_upper_ = np.array([0, 1], dtype=np.double) +lp.a_matrix_.format_ = highspy.MatrixFormat.kRowwise +lp.a_matrix_.start_ = np.array([0, 2, 4]) +lp.a_matrix_.index_ = np.array([1, 2, 0, 2]) +lp.a_matrix_.value_ = np.array([1, 1, 1, 1], dtype=np.double) + +h.clear() +h.passModel(lp) +h.run() +assert h.getModelStatus() == highspy.HighsModelStatus.kInfeasible + +# Set IIS strategy to row priority and get IIS +h.setOptionValue("iis_strategy", highspy.IisStrategy.kIisStrategyFromLpRowPriority) + +iis = highspy.HighsIis() +assert h.getIis(iis) == highspy.HighsStatus.kOk +assert len(iis.col_index) == 0 +assert len(iis.row_index) == 1 +assert iis.row_index[0] == 0 +assert iis.row_bound[0] == highspy.IisBoundStatus.kIisBoundStatusBoxed + +# Set IIS strategy to column priority and get IIS +h.setOptionValue("iis_strategy", highspy.IisStrategy.kIisStrategyFromLpColPriority) +iis.invalidate() +assert h.getIis(iis) == highspy.HighsStatus.kOk +assert len(iis.col_index) == 1 +assert len(iis.row_index) == 0 +assert iis.col_index[0] == 2 +assert iis.col_bound[0] == highspy.IisBoundStatus.kIisBoundStatusBoxed + +print("IIS computation completed successfully") + +print("computing feasibility relaxation") +h.clear() +inf = h.getInfinity() + +num_col = 2 +num_row = 3 +num_nz = 6 +a_format = highspy.MatrixFormat.kColwise +sense = highspy.ObjSense.kMinimize +offset = 0 +col_cost = np.array([1, -2], dtype=np.double) +col_lower = np.array([5, -inf], dtype=np.double) +col_upper = np.array([inf, inf], dtype=np.double) +row_lower = np.array([2, -inf, -inf], dtype=np.double) +row_upper = np.array([inf, 1, 20], dtype=np.double) +a_start = np.array([0, 3]) +a_index = np.array([0, 1, 2, 0, 1, 2]) +a_value = np.array([-1, -3, 20, 21, 2, 1], dtype=np.double) +integrality = np.array([highspy.HighsVarType.kInteger, highspy.HighsVarType.kInteger]) + +h.passModel( + 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, + integrality +) + +assert h.feasibilityRelaxation(1, 1, 1) == highspy.HighsStatus.kOk + +info = h.getInfo() +objective_function_value = info.objective_function_value + +solution = h.getSolution() + +assert abs(objective_function_value - 5) < 1e-6, f"Expected objective value 5, got {objective_function_value}" +assert abs(solution.col_value[0] - 1) < 1e-6, f"Expected solution[0] = 1, got {solution.col_value[0]}" +assert abs(solution.col_value[1] - 1) < 1e-6, f"Expected solution[1] = 1, got {solution.col_value[1]}" + +print("Feasibility Relaxation Test Passed") + +# Using infeasible LP from AMPL documentation +h = highspy.Highs() +lp = highspy.HighsLp() +lp.num_col_ = 2 +lp.num_row_ = 3 +lp.col_cost_ = np.array([1, -2], dtype=np.double) +lp.col_lower_ = np.array([5, -h.getInfinity()], dtype=np.double) +lp.col_upper_ = np.array([h.getInfinity(), h.getInfinity()], dtype=np.double) +lp.col_names_ = ["X", "Y"] +lp.row_lower_ = np.array([2, -h.getInfinity(), -h.getInfinity()], dtype=np.double) +lp.row_upper_ = np.array([h.getInfinity(), 1, 20], dtype=np.double) +lp.row_names_ = ["R0", "R1", "R2"] +lp.a_matrix_.start_ = np.array([0, 3, 6]) +lp.a_matrix_.index_ = np.array([0, 1, 2, 0, 1, 2]) +lp.a_matrix_.value_ = np.array([-1, -3, 20, 21, 2, 1], dtype=np.double) +lp.integrality_ = np.array([highspy.HighsVarType.kInteger, highspy.HighsVarType.kInteger]) +h.setOptionValue("output_flag", False) +h.passModel(lp) + +# Vanilla feasibility relaxation +print("Vanilla feasibility relaxation") +h.feasibilityRelaxation(1, 1, 1) +solution = h.getSolution() +print(f"Solution: ({solution.col_value[0]}, {solution.col_value[1]})") +print(f"Slacks: ({solution.row_value[0] - lp.row_lower_[0]}, " + f"{lp.row_upper_[1] - solution.row_value[1]}, " + f"{lp.row_upper_[2] - solution.row_value[2]})") + +# Respect all lower bounds +print("\nRespect all lower bounds") +h.feasibilityRelaxation(-1, 1, 1) +solution = h.getSolution() +print(f"Solution: ({solution.col_value[0]}, {solution.col_value[1]})") +print(f"Slacks: ({solution.row_value[0] - lp.row_lower_[0]}, " + f"{lp.row_upper_[1] - solution.row_value[1]}, " + f"{lp.row_upper_[2] - solution.row_value[2]})") + +# Local penalties RHS {1, -1, 10} +print("\nLocal penalties RHS {1, -1, 10}") +local_rhs_penalty = np.array([1, -1, 10], dtype=np.double) +h.feasibilityRelaxation(1, 1, 0, None, None, local_rhs_penalty) +solution = h.getSolution() +print(f"Solution: ({solution.col_value[0]}, {solution.col_value[1]})") +print(f"Slacks: ({solution.row_value[0] - lp.row_lower_[0]}, " + f"{lp.row_upper_[1] - solution.row_value[1]}, " + f"{lp.row_upper_[2] - solution.row_value[2]})") + +# Local penalties RHS {10, 1, 1} +print("\nLocal penalties RHS {10, 1, 1}") +local_rhs_penalty = np.array([10, 1, 1], dtype=np.double) +h.feasibilityRelaxation(1, 1, 0, None, None, local_rhs_penalty) +solution = h.getSolution() +print(f"Solution: ({solution.col_value[0]}, {solution.col_value[1]})") +print(f"Slacks: ({solution.row_value[0] - lp.row_lower_[0]}, " + f"{lp.row_upper_[1] - solution.row_value[1]}, " + f"{lp.row_upper_[2] - solution.row_value[2]})") + +iis = highspy.HighsIis() +assert h.getIis(iis) == highspy.HighsStatus.kOk + +print("\nIIS") +print("row_index:", iis.row_index) +print("row_bound:", iis.row_bound) + +print("col_index:", iis.col_index) +print("col_bound:", iis.col_bound) \ No newline at end of file diff --git a/src/highs_bindings.cpp b/src/highs_bindings.cpp index e7f9e49cac..972b8a6719 100644 --- a/src/highs_bindings.cpp +++ b/src/highs_bindings.cpp @@ -671,6 +671,20 @@ PYBIND11_MODULE(_core, m) { .value("kWarning", HighsLogType::kWarning) .value("kError", HighsLogType::kError); // .export_values(); + py::enum_(m, "IisStrategy") + .value("kIisStrategyMin", IisStrategy::kIisStrategyMin) + .value("kIisStrategyFromLpRowPriority", IisStrategy::kIisStrategyFromLpRowPriority) + .value("kIisStrategyFromLpColPriority", IisStrategy::kIisStrategyFromLpColPriority) + .value("kIisStrategyMax", IisStrategy::kIisStrategyMax); + // .export_values(); + py::enum_(m, "IisBoundStatus") + .value("kIisBoundStatusDropped", IisBoundStatus::kIisBoundStatusDropped) + .value("kIisBoundStatusNull", IisBoundStatus::kIisBoundStatusNull) + .value("kIisBoundStatusFree", IisBoundStatus::kIisBoundStatusFree) + .value("kIisBoundStatusLower", IisBoundStatus::kIisBoundStatusLower) + .value("kIisBoundStatusUpper", IisBoundStatus::kIisBoundStatusUpper) + .value("kIisBoundStatusBoxed", IisBoundStatus::kIisBoundStatusBoxed); + // .export_values(); // Classes py::class_(m, "HighsSparseMatrix") .def(py::init<>()) @@ -861,6 +875,37 @@ PYBIND11_MODULE(_core, m) { .def("postsolve", &highs_postsolve) .def("postsolve", &highs_mipPostsolve) .def("run", &Highs::run) + .def("feasibilityRelaxation", + [](Highs& self, double global_lower_penalty, double global_upper_penalty, double global_rhs_penalty, + py::object local_lower_penalty, py::object local_upper_penalty, py::object local_rhs_penalty) { + std::vector llp, lup, lrp; + const double* llp_ptr = nullptr; + const double* lup_ptr = nullptr; + const double* lrp_ptr = nullptr; + + if (!local_lower_penalty.is_none()) { + llp = local_lower_penalty.cast>(); + llp_ptr = llp.data(); + } + if (!local_upper_penalty.is_none()) { + lup = local_upper_penalty.cast>(); + lup_ptr = lup.data(); + } + if (!local_rhs_penalty.is_none()) { + lrp = local_rhs_penalty.cast>(); + lrp_ptr = lrp.data(); + } + + return self.feasibilityRelaxation(global_lower_penalty, global_upper_penalty, global_rhs_penalty, + llp_ptr, lup_ptr, lrp_ptr); + }, + py::arg("global_lower_penalty"), + py::arg("global_upper_penalty"), + py::arg("global_rhs_penalty"), + py::arg("local_lower_penalty") = py::none(), + py::arg("local_upper_penalty") = py::none(), + py::arg("local_rhs_penalty") = py::none()) + .def("getIis", &Highs::getIis) .def("presolve", &Highs::presolve) .def("writeSolution", &highs_writeSolution) .def("readSolution", &Highs::readSolution) @@ -975,6 +1020,17 @@ PYBIND11_MODULE(_core, m) { &Highs::startCallback)) .def("stopCallbackInt", static_cast( &Highs::stopCallback)); + + py::class_(m, "HighsIis") + .def(py::init<>()) + .def("invalidate", &HighsIis::invalidate) + .def_readwrite("valid", &HighsIis::valid_) + .def_readwrite("strategy", &HighsIis::strategy_) + .def_readwrite("col_index", &HighsIis::col_index_) + .def_readwrite("row_index", &HighsIis::row_index_) + .def_readwrite("col_bound", &HighsIis::col_bound_) + .def_readwrite("row_bound", &HighsIis::row_bound_) + .def_readwrite("info", &HighsIis::info_); // structs py::class_(m, "HighsSolution") .def(py::init<>()) @@ -1013,6 +1069,10 @@ PYBIND11_MODULE(_core, m) { .def_readwrite("col_bound_dn", &HighsRanging::col_bound_dn) .def_readwrite("row_bound_up", &HighsRanging::row_bound_up) .def_readwrite("row_bound_dn", &HighsRanging::row_bound_dn); + py::class_(m, "HighsIisInfo") + .def(py::init<>()) + .def_readwrite("simplex_time", &HighsIisInfo::simplex_time) + .def_readwrite("simplex_iterations", &HighsIisInfo::simplex_iterations); // constants m.attr("kHighsInf") = kHighsInf; m.attr("kHighsIInf") = kHighsIInf; diff --git a/src/highspy/__init__.py b/src/highspy/__init__.py index 3638f094c2..5142728fe3 100644 --- a/src/highspy/__init__.py +++ b/src/highspy/__init__.py @@ -14,6 +14,8 @@ HighsInfoType, \ HighsStatus, \ HighsLogType, \ + IisStrategy, \ + IisBoundStatus, \ HighsSparseMatrix, \ HighsLp, \ HighsHessian, \ @@ -21,6 +23,7 @@ HighsInfo, \ HighsOptions, \ _Highs, \ + HighsIis, \ HighsSolution, \ HighsObjectiveSolution, \ HighsBasis, \ @@ -111,6 +114,8 @@ "HighsInfoType", "HighsStatus", "HighsLogType", + "IisStrategy", + "IisBoundStatus", "HighsSparseMatrix", "HighsLp", "HighsHessian", @@ -119,6 +124,7 @@ "HighsOptions", "_Highs", "Highs", + "HighsIis", "HighsSolution", "HighsObjectiveSolution", "HighsBasis", diff --git a/src/highspy/_core.pyi b/src/highspy/_core.pyi index a3a6fcea9f..85fa2e4030 100644 --- a/src/highspy/_core.pyi +++ b/src/highspy/_core.pyi @@ -1,6 +1,7 @@ -import numpy -from typing import Callable, ClassVar, overload +from typing import Callable, ClassVar, List, Tuple +from typing import overload +import numpy HIGHS_VERSION_MAJOR: int HIGHS_VERSION_MINOR: int HIGHS_VERSION_PATCH: int @@ -13,32 +14,38 @@ kSolutionStatusInfeasible: SolutionStatus kSolutionStatusNone: SolutionStatus class BasisValidity: + __doc__: ClassVar[str] = ... # read-only __members__: ClassVar[dict] = ... # read-only __entries: ClassVar[dict] = ... kBasisValidityInvalid: ClassVar[BasisValidity] = ... kBasisValidityValid: ClassVar[BasisValidity] = ... def __init__(self, value: int) -> None: ... def __eq__(self, other: object) -> bool: ... + def __getstate__(self) -> int: ... def __hash__(self) -> int: ... def __index__(self) -> int: ... def __int__(self) -> int: ... def __ne__(self, other: object) -> bool: ... + def __setstate__(self, state: int) -> None: ... @property def name(self) -> str: ... @property def value(self) -> int: ... class HessianFormat: + __doc__: ClassVar[str] = ... # read-only __members__: ClassVar[dict] = ... # read-only __entries: ClassVar[dict] = ... kSquare: ClassVar[HessianFormat] = ... kTriangular: ClassVar[HessianFormat] = ... def __init__(self, value: int) -> None: ... def __eq__(self, other: object) -> bool: ... + def __getstate__(self) -> int: ... def __hash__(self) -> int: ... def __index__(self) -> int: ... def __int__(self) -> int: ... def __ne__(self, other: object) -> bool: ... + def __setstate__(self, state: int) -> None: ... @property def name(self) -> str: ... @property @@ -46,16 +53,17 @@ class HessianFormat: class HighsBasis: alien: bool - col_status: list[HighsBasisStatus] + col_status: List[HighsBasisStatus] debug_id: int debug_origin_name: str debug_update_count: int - row_status: list[HighsBasisStatus] + row_status: List[HighsBasisStatus] valid: bool was_alien: bool def __init__(self) -> None: ... class HighsBasisStatus: + __doc__: ClassVar[str] = ... # read-only __members__: ClassVar[dict] = ... # read-only __entries: ClassVar[dict] = ... kBasic: ClassVar[HighsBasisStatus] = ... @@ -65,10 +73,12 @@ class HighsBasisStatus: kZero: ClassVar[HighsBasisStatus] = ... def __init__(self, value: int) -> None: ... def __eq__(self, other: object) -> bool: ... + def __getstate__(self) -> int: ... def __hash__(self) -> int: ... def __index__(self) -> int: ... def __int__(self) -> int: ... def __ne__(self, other: object) -> bool: ... + def __setstate__(self, state: int) -> None: ... @property def name(self) -> str: ... @property @@ -77,9 +87,25 @@ class HighsBasisStatus: class HighsHessian: dim_: int format_: HessianFormat - index_: list[int] - start_: list[int] - value_: list[float] + index_: List[int] + start_: List[int] + value_: List[float] + def __init__(self) -> None: ... + +class HighsIis: + col_bound: List[int] + col_index: List[int] + info: List[HighsIisInfo] + row_bound: List[int] + row_index: List[int] + strategy: int + valid: bool + def __init__(self) -> None: ... + def invalidate(self) -> None: ... + +class HighsIisInfo: + simplex_iterations: int + simplex_time: float def __init__(self) -> None: ... class HighsInfo: @@ -108,6 +134,7 @@ class HighsInfo: def __init__(self) -> None: ... class HighsInfoType: + __doc__: ClassVar[str] = ... # read-only __members__: ClassVar[dict] = ... # read-only __entries: ClassVar[dict] = ... kDouble: ClassVar[HighsInfoType] = ... @@ -115,16 +142,19 @@ class HighsInfoType: kInt64: ClassVar[HighsInfoType] = ... def __init__(self, value: int) -> None: ... def __eq__(self, other: object) -> bool: ... + def __getstate__(self) -> int: ... def __hash__(self) -> int: ... def __index__(self) -> int: ... def __int__(self) -> int: ... def __ne__(self, other: object) -> bool: ... + def __setstate__(self, state: int) -> None: ... @property def name(self) -> str: ... @property def value(self) -> int: ... class HighsLogType: + __doc__: ClassVar[str] = ... # read-only __members__: ClassVar[dict] = ... # read-only __entries: ClassVar[dict] = ... kDetailed: ClassVar[HighsLogType] = ... @@ -134,10 +164,12 @@ class HighsLogType: kWarning: ClassVar[HighsLogType] = ... def __init__(self, value: int) -> None: ... def __eq__(self, other: object) -> bool: ... + def __getstate__(self) -> int: ... def __hash__(self) -> int: ... def __index__(self) -> int: ... def __int__(self) -> int: ... def __ne__(self, other: object) -> bool: ... + def __setstate__(self, state: int) -> None: ... @property def name(self) -> str: ... @property @@ -145,11 +177,11 @@ class HighsLogType: class HighsLp: a_matrix_: HighsSparseMatrix - col_cost_: list[float] - col_lower_: list[float] - col_names_: list[str] - col_upper_: list[float] - integrality_: list[HighsVarType] + col_cost_: List[float] + col_lower_: List[float] + col_names_: List[str] + col_upper_: List[float] + integrality_: List[HighsVarType] is_moved_: bool is_scaled_: bool model_name_: str @@ -157,9 +189,9 @@ class HighsLp: num_col_: int num_row_: int offset_: float - row_lower_: list[float] - row_names_: list[str] - row_upper_: list[float] + row_lower_: List[float] + row_names_: List[str] + row_upper_: List[float] scale_: HighsScale sense_: ObjSense def __init__(self) -> None: ... @@ -170,6 +202,7 @@ class HighsModel: def __init__(self) -> None: ... class HighsModelStatus: + __doc__: ClassVar[str] = ... # read-only __members__: ClassVar[dict] = ... # read-only __entries: ClassVar[dict] = ... kInfeasible: ClassVar[HighsModelStatus] = ... @@ -193,21 +226,24 @@ class HighsModelStatus: kUnknown: ClassVar[HighsModelStatus] = ... def __init__(self, value: int) -> None: ... def __eq__(self, other: object) -> bool: ... + def __getstate__(self) -> int: ... def __hash__(self) -> int: ... def __index__(self) -> int: ... def __int__(self) -> int: ... def __ne__(self, other: object) -> bool: ... + def __setstate__(self, state: int) -> None: ... @property def name(self) -> str: ... @property def value(self) -> int: ... class HighsObjectiveSolution: - col_value: list[float] + col_value: List[float] objective: float def __init__(self) -> None: ... class HighsOptionType: + __doc__: ClassVar[str] = ... # read-only __members__: ClassVar[dict] = ... # read-only __entries: ClassVar[dict] = ... kBool: ClassVar[HighsOptionType] = ... @@ -216,10 +252,12 @@ class HighsOptionType: kString: ClassVar[HighsOptionType] = ... def __init__(self, value: int) -> None: ... def __eq__(self, other: object) -> bool: ... + def __getstate__(self) -> int: ... def __hash__(self) -> int: ... def __index__(self) -> int: ... def __int__(self) -> int: ... def __ne__(self, other: object) -> bool: ... + def __setstate__(self, state: int) -> None: ... @property def name(self) -> str: ... @property @@ -289,6 +327,7 @@ class HighsOptions: def __init__(self) -> None: ... class HighsPresolveStatus: + __doc__: ClassVar[str] = ... # read-only __members__: ClassVar[dict] = ... # read-only __entries: ClassVar[dict] = ... kInfeasible: ClassVar[HighsPresolveStatus] = ... @@ -302,10 +341,12 @@ class HighsPresolveStatus: kUnboundedOrInfeasible: ClassVar[HighsPresolveStatus] = ... def __init__(self, value: int) -> None: ... def __eq__(self, other: object) -> bool: ... + def __getstate__(self) -> int: ... def __hash__(self) -> int: ... def __index__(self) -> int: ... def __int__(self) -> int: ... def __ne__(self, other: object) -> bool: ... + def __setstate__(self, state: int) -> None: ... @property def name(self) -> str: ... @property @@ -322,32 +363,33 @@ class HighsRanging: def __init__(self) -> None: ... class HighsRangingRecord: - in_var_: list[int] - objective_: list[float] - ou_var_: list[int] - value_: list[float] + in_var_: List[int] + objective_: List[float] + ou_var_: List[int] + value_: List[float] def __init__(self) -> None: ... class HighsSolution: - col_dual: list[float] - col_value: list[float] + col_dual: List[float] + col_value: List[float] dual_valid: bool - row_dual: list[float] - row_value: list[float] + row_dual: List[float] + row_value: List[float] value_valid: bool def __init__(self) -> None: ... class HighsSparseMatrix: format_: MatrixFormat - index_: list[int] + index_: List[int] num_col_: int num_row_: int - p_end_: list[int] - start_: list[int] - value_: list[float] + p_end_: List[int] + start_: List[int] + value_: List[float] def __init__(self) -> None: ... class HighsStatus: + __doc__: ClassVar[str] = ... # read-only __members__: ClassVar[dict] = ... # read-only __entries: ClassVar[dict] = ... kError: ClassVar[HighsStatus] = ... @@ -355,16 +397,19 @@ class HighsStatus: kWarning: ClassVar[HighsStatus] = ... def __init__(self, value: int) -> None: ... def __eq__(self, other: object) -> bool: ... + def __getstate__(self) -> int: ... def __hash__(self) -> int: ... def __index__(self) -> int: ... def __int__(self) -> int: ... def __ne__(self, other: object) -> bool: ... + def __setstate__(self, state: int) -> None: ... @property def name(self) -> str: ... @property def value(self) -> int: ... class HighsVarType: + __doc__: ClassVar[str] = ... # read-only __members__: ClassVar[dict] = ... # read-only __entries: ClassVar[dict] = ... kContinuous: ClassVar[HighsVarType] = ... @@ -373,16 +418,63 @@ class HighsVarType: kSemiInteger: ClassVar[HighsVarType] = ... def __init__(self, value: int) -> None: ... def __eq__(self, other: object) -> bool: ... + def __getstate__(self) -> int: ... def __hash__(self) -> int: ... def __index__(self) -> int: ... def __int__(self) -> int: ... def __ne__(self, other: object) -> bool: ... + def __setstate__(self, state: int) -> None: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + +class IisBoundStatus: + __doc__: ClassVar[str] = ... # read-only + __members__: ClassVar[dict] = ... # read-only + __entries: ClassVar[dict] = ... + kIisBoundStatusBoxed: ClassVar[IisBoundStatus] = ... + kIisBoundStatusDropped: ClassVar[IisBoundStatus] = ... + kIisBoundStatusFree: ClassVar[IisBoundStatus] = ... + kIisBoundStatusLower: ClassVar[IisBoundStatus] = ... + kIisBoundStatusNull: ClassVar[IisBoundStatus] = ... + kIisBoundStatusUpper: ClassVar[IisBoundStatus] = ... + def __init__(self, value: int) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __getstate__(self) -> int: ... + def __hash__(self) -> int: ... + def __index__(self) -> int: ... + def __int__(self) -> int: ... + def __ne__(self, other: object) -> bool: ... + def __setstate__(self, state: int) -> None: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + +class IisStrategy: + __doc__: ClassVar[str] = ... # read-only + __members__: ClassVar[dict] = ... # read-only + __entries: ClassVar[dict] = ... + kIisStrategyFromLpColPriority: ClassVar[IisStrategy] = ... + kIisStrategyFromLpRowPriority: ClassVar[IisStrategy] = ... + kIisStrategyMax: ClassVar[IisStrategy] = ... + kIisStrategyMin: ClassVar[IisStrategy] = ... + def __init__(self, value: int) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __getstate__(self) -> int: ... + def __hash__(self) -> int: ... + def __index__(self) -> int: ... + def __int__(self) -> int: ... + def __ne__(self, other: object) -> bool: ... + def __setstate__(self, state: int) -> None: ... @property def name(self) -> str: ... @property def value(self) -> int: ... class MatrixFormat: + __doc__: ClassVar[str] = ... # read-only __members__: ClassVar[dict] = ... # read-only __entries: ClassVar[dict] = ... kColwise: ClassVar[MatrixFormat] = ... @@ -390,32 +482,38 @@ class MatrixFormat: kRowwisePartitioned: ClassVar[MatrixFormat] = ... def __init__(self, value: int) -> None: ... def __eq__(self, other: object) -> bool: ... + def __getstate__(self) -> int: ... def __hash__(self) -> int: ... def __index__(self) -> int: ... def __int__(self) -> int: ... def __ne__(self, other: object) -> bool: ... + def __setstate__(self, state: int) -> None: ... @property def name(self) -> str: ... @property def value(self) -> int: ... class ObjSense: + __doc__: ClassVar[str] = ... # read-only __members__: ClassVar[dict] = ... # read-only __entries: ClassVar[dict] = ... kMaximize: ClassVar[ObjSense] = ... kMinimize: ClassVar[ObjSense] = ... def __init__(self, value: int) -> None: ... def __eq__(self, other: object) -> bool: ... + def __getstate__(self) -> int: ... def __hash__(self) -> int: ... def __index__(self) -> int: ... def __int__(self) -> int: ... def __ne__(self, other: object) -> bool: ... + def __setstate__(self, state: int) -> None: ... @property def name(self) -> str: ... @property def value(self) -> int: ... class SolutionStatus: + __doc__: ClassVar[str] = ... # read-only __members__: ClassVar[dict] = ... # read-only __entries: ClassVar[dict] = ... kSolutionStatusFeasible: ClassVar[SolutionStatus] = ... @@ -423,10 +521,12 @@ class SolutionStatus: kSolutionStatusNone: ClassVar[SolutionStatus] = ... def __init__(self, value: int) -> None: ... def __eq__(self, other: object) -> bool: ... + def __getstate__(self) -> int: ... def __hash__(self) -> int: ... def __index__(self) -> int: ... def __int__(self) -> int: ... def __ne__(self, other: object) -> bool: ... + def __setstate__(self, state: int) -> None: ... @property def name(self) -> str: ... @property @@ -456,21 +556,23 @@ class _Highs: def clearModel(self) -> HighsStatus: ... def clearSolver(self) -> HighsStatus: ... def crossover(self, arg0: HighsSolution) -> HighsStatus: ... - def deleteCols(self, arg0: int, arg1: list[int]) -> HighsStatus: ... - def deleteRows(self, arg0: int, arg1: list[int]) -> HighsStatus: ... - def deleteVars(self, arg0: int, arg1: list[int]) -> HighsStatus: ... + def deleteCols(self, arg0: int, arg1: List[int]) -> HighsStatus: ... + def deleteRows(self, arg0: int, arg1: List[int]) -> HighsStatus: ... + def deleteVars(self, arg0: int, arg1: List[int]) -> HighsStatus: ... + def feasibilityRelaxation(self, global_lower_penalty: float, global_upper_penalty: float, global_rhs_penalty: float, local_lower_penalty: object = ..., local_upper_penalty: object = ..., local_rhs_penalty: object = ...) -> HighsStatus: ... def getBasis(self) -> HighsBasis: ... - def getCol(self, arg0: int) -> tuple[HighsStatus, float, float, float, int]: ... - def getColByName(self, arg0: str) -> tuple[HighsStatus, int]: ... - def getColEntries(self, arg0: int) -> tuple[HighsStatus, numpy.ndarray[numpy.int32], numpy.ndarray[numpy.float64]]: ... - def getColName(self, arg0: int) -> tuple[HighsStatus, str]: ... - def getCols(self, arg0: int, arg1: numpy.ndarray[numpy.int32]) -> tuple[HighsStatus, int, numpy.ndarray[numpy.float64], numpy.ndarray[numpy.float64], numpy.ndarray[numpy.float64], int]: ... - def getColsEntries(self, arg0: int, arg1: numpy.ndarray[numpy.int32]) -> tuple[HighsStatus, numpy.ndarray[numpy.int32], numpy.ndarray[numpy.int32], numpy.ndarray[numpy.float64]]: ... + def getCol(self, arg0: int) -> Tuple[HighsStatus,float,float,float,int]: ... + def getColByName(self, arg0: str) -> Tuple[HighsStatus,int]: ... + def getColEntries(self, arg0: int) -> Tuple[HighsStatus,numpy.ndarray[numpy.int32],numpy.ndarray[numpy.float64]]: ... + def getColName(self, arg0: int) -> Tuple[HighsStatus,str]: ... + def getCols(self, arg0: int, arg1: numpy.ndarray[numpy.int32]) -> Tuple[HighsStatus,int,numpy.ndarray[numpy.float64],numpy.ndarray[numpy.float64],numpy.ndarray[numpy.float64],int]: ... + def getColsEntries(self, arg0: int, arg1: numpy.ndarray[numpy.int32]) -> Tuple[HighsStatus,numpy.ndarray[numpy.int32],numpy.ndarray[numpy.int32],numpy.ndarray[numpy.float64]]: ... def getHessianNumNz(self) -> int: ... + def getIis(self, arg0: HighsIis) -> HighsStatus: ... def getInfinity(self) -> float: ... def getInfo(self) -> HighsInfo: ... - def getInfoType(self, arg0: str) -> tuple[HighsStatus, HighsInfoType]: ... - def getInfoValue(self, arg0: str) -> tuple[HighsStatus, object]: ... + def getInfoType(self, arg0: str) -> Tuple[HighsStatus,HighsInfoType]: ... + def getInfoValue(self, arg0: str) -> Tuple[HighsStatus,object]: ... def getLp(self) -> HighsLp: ... def getModel(self) -> HighsModel: ... def getModelPresolveStatus(self) -> HighsPresolveStatus: ... @@ -478,22 +580,22 @@ class _Highs: def getNumCol(self) -> int: ... def getNumNz(self) -> int: ... def getNumRow(self) -> int: ... - def getObjectiveOffset(self) -> tuple[HighsStatus, float]: ... - def getObjectiveSense(self) -> tuple[HighsStatus, ObjSense]: ... + def getObjectiveOffset(self) -> Tuple[HighsStatus,float]: ... + def getObjectiveSense(self) -> Tuple[HighsStatus,ObjSense]: ... def getObjectiveValue(self) -> float: ... - def getOptionType(self, arg0: str) -> tuple[HighsStatus, HighsOptionType]: ... - def getOptionValue(self, arg0: str) -> tuple[HighsStatus, object]: ... + def getOptionType(self, arg0: str) -> Tuple[HighsStatus,HighsOptionType]: ... + def getOptionValue(self, arg0: str) -> Tuple[HighsStatus,object]: ... def getOptions(self) -> HighsOptions: ... def getPresolvedLp(self) -> HighsLp: ... - def getRanging(self) -> tuple[HighsStatus, HighsRanging]: ... - def getRow(self, arg0: int) -> tuple[HighsStatus, float, float, int]: ... - def getRowByName(self, arg0: str) -> tuple[HighsStatus, int]: ... - def getRowEntries(self, arg0: int) -> tuple[HighsStatus, numpy.ndarray[numpy.int32], numpy.ndarray[numpy.float64]]: ... - def getRowName(self, arg0: int) -> tuple[HighsStatus, str]: ... - def getRows(self, arg0: int, arg1: numpy.ndarray[numpy.int32]) -> tuple[HighsStatus, int, numpy.ndarray[numpy.float64], numpy.ndarray[numpy.float64], int]: ... - def getRowsEntries(self, arg0: int, arg1: numpy.ndarray[numpy.int32]) -> tuple[HighsStatus, numpy.ndarray[numpy.int32], numpy.ndarray[numpy.int32], numpy.ndarray[numpy.float64]]: ... + def getRanging(self) -> Tuple[HighsStatus,HighsRanging]: ... + def getRow(self, arg0: int) -> Tuple[HighsStatus,float,float,int]: ... + def getRowByName(self, arg0: str) -> Tuple[HighsStatus,int]: ... + def getRowEntries(self, arg0: int) -> Tuple[HighsStatus,numpy.ndarray[numpy.int32],numpy.ndarray[numpy.float64]]: ... + def getRowName(self, arg0: int) -> Tuple[HighsStatus,str]: ... + def getRows(self, arg0: int, arg1: numpy.ndarray[numpy.int32]) -> Tuple[HighsStatus,int,numpy.ndarray[numpy.float64],numpy.ndarray[numpy.float64],int]: ... + def getRowsEntries(self, arg0: int, arg1: numpy.ndarray[numpy.int32]) -> Tuple[HighsStatus,numpy.ndarray[numpy.int32],numpy.ndarray[numpy.int32],numpy.ndarray[numpy.float64]]: ... def getRunTime(self) -> float: ... - def getSavedMipSolutions(self) -> list[HighsObjectiveSolution]: ... + def getSavedMipSolutions(self) -> List[HighsObjectiveSolution]: ... def getSolution(self) -> HighsSolution: ... def githash(self) -> str: ... def modelStatusToString(self, arg0: HighsModelStatus) -> str: ... @@ -527,7 +629,7 @@ class _Highs: def setBasis(self, arg0: HighsBasis) -> HighsStatus: ... @overload def setBasis(self) -> HighsStatus: ... - def setCallback(self, arg0: Callable[[int, str, HighsCallbackDataOut, HighsCallbackDataIn, capsule], None], arg1: capsule) -> HighsStatus: ... + def setCallback(self, arg0: Callable[[int,str,HighsCallbackDataOut,HighsCallbackDataIn,capsule],None], arg1: capsule) -> HighsStatus: ... @overload def setOptionValue(self, arg0: str, arg1: bool) -> HighsStatus: ... @overload @@ -536,7 +638,10 @@ class _Highs: def setOptionValue(self, arg0: str, arg1: float) -> HighsStatus: ... @overload def setOptionValue(self, arg0: str, arg1: str) -> HighsStatus: ... + @overload def setSolution(self, arg0: HighsSolution) -> HighsStatus: ... + @overload + def setSolution(self, arg0: int, arg1: numpy.ndarray[numpy.int32], arg2: numpy.ndarray[numpy.float64]) -> HighsStatus: ... def solutionStatusToString(self, arg0: int) -> str: ... def startCallback(self, arg0: HighsCallbackType) -> HighsStatus: ... def startCallbackInt(self, arg0: int) -> HighsStatus: ... From 0b4f3f8a5a69faf9d1c4e5e3dc232c9624e091b7 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 19 Aug 2024 13:54:29 +0100 Subject: [PATCH 071/194] HPresolve::fixColToLower/Upper now reanmed fixColToLower/UpperOrUnbounded and returns true if fixing to infinite value --- src/presolve/HPresolve.cpp | 73 +++++++++++++++++++++----------------- src/presolve/HPresolve.h | 4 +-- 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/src/presolve/HPresolve.cpp b/src/presolve/HPresolve.cpp index f2d549e865..56d6c54eb6 100644 --- a/src/presolve/HPresolve.cpp +++ b/src/presolve/HPresolve.cpp @@ -1183,7 +1183,7 @@ HPresolve::Result HPresolve::dominatedColumns( checkDomination(-1, j, -1, k)) { // case (iii) lb(x_j) = -inf, -x_j > -x_k: set x_k = ub(x_k) ++numFixedCols; - fixColToLower(postsolve_stack, j); + fixColToLowerOrUnbounded(postsolve_stack, j); HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); break; } else if (-ajBestRowMinus <= ak + options->small_matrix_value && @@ -1192,7 +1192,7 @@ HPresolve::Result HPresolve::dominatedColumns( checkDomination(-1, j, 1, k)) { // case (iv) lb(x_j) = -inf, -x_j > x_k: set x_k = lb(x_k) ++numFixedCols; - fixColToLower(postsolve_stack, j); + fixColToLowerOrUnbounded(postsolve_stack, j); HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); break; } @@ -1223,7 +1223,7 @@ HPresolve::Result HPresolve::dominatedColumns( checkDomination(1, j, 1, k)) { // case (i) ub(x_j) = inf, x_j > x_k: set x_k = lb(x_k) ++numFixedCols; - fixColToUpper(postsolve_stack, j); + fixColToUpperOrUnbounded(postsolve_stack, j); HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); break; } else if (ajBestRowPlus <= -ak + options->small_matrix_value && @@ -1232,7 +1232,7 @@ HPresolve::Result HPresolve::dominatedColumns( checkDomination(1, j, -1, k)) { // case (ii) ub(x_j) = inf, x_j > -x_k: set x_k = ub(x_k) ++numFixedCols; - fixColToUpper(postsolve_stack, j); + fixColToUpperOrUnbounded(postsolve_stack, j); HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); break; } @@ -1271,7 +1271,7 @@ HPresolve::Result HPresolve::dominatedColumns( checkDomination(1, j, 1, k)) { // case (i) ub(x_j) = inf, x_j > x_k: set x_k = lb(x_k) ++numFixedCols; - fixColToLower(postsolve_stack, k); + fixColToLowerOrUnbounded(postsolve_stack, k); HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); } else if (model->col_upper_[k] != kHighsInf && (upperImplied || @@ -1284,7 +1284,7 @@ HPresolve::Result HPresolve::dominatedColumns( checkDomination(1, j, -1, k)) { // case (ii) ub(x_j) = inf, x_j > -x_k: set x_k = ub(x_k) ++numFixedCols; - fixColToUpper(postsolve_stack, k); + fixColToUpperOrUnbounded(postsolve_stack, k); HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); } } @@ -1313,7 +1313,7 @@ HPresolve::Result HPresolve::dominatedColumns( checkDomination(-1, j, -1, k)) { // case (iii) lb(x_j) = -inf, -x_j > -x_k: set x_k = ub(x_k) ++numFixedCols; - fixColToUpper(postsolve_stack, k); + fixColToUpperOrUnbounded(postsolve_stack, k); HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); } else if (model->col_lower_[k] != -kHighsInf && (lowerImplied || @@ -1326,7 +1326,7 @@ HPresolve::Result HPresolve::dominatedColumns( checkDomination(-1, j, 1, k)) { // case (iv) lb(x_j) = -inf, -x_j > x_k: set x_k = lb(x_k) ++numFixedCols; - fixColToLower(postsolve_stack, k); + fixColToLowerOrUnbounded(postsolve_stack, k); HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); } } @@ -2837,7 +2837,7 @@ HPresolve::Result HPresolve::singletonCol(HighsPostsolveStack& postsolve_stack, if (colDualLower > options->dual_feasibility_tolerance) { if (model->col_lower_[col] == -kHighsInf) return Result::kDualInfeasible; if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleDominatedCol); - fixColToLower(postsolve_stack, col); + fixColToLowerOrUnbounded(postsolve_stack, col); analysis_.logging_on_ = logging_on; if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleDominatedCol); return checkLimits(postsolve_stack); @@ -2846,7 +2846,7 @@ HPresolve::Result HPresolve::singletonCol(HighsPostsolveStack& postsolve_stack, if (colDualUpper < -options->dual_feasibility_tolerance) { if (model->col_upper_[col] == kHighsInf) return Result::kDualInfeasible; if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleDominatedCol); - fixColToUpper(postsolve_stack, col); + fixColToUpperOrUnbounded(postsolve_stack, col); analysis_.logging_on_ = logging_on; if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleDominatedCol); return checkLimits(postsolve_stack); @@ -2856,7 +2856,7 @@ HPresolve::Result HPresolve::singletonCol(HighsPostsolveStack& postsolve_stack, if (colDualUpper <= options->dual_feasibility_tolerance) { if (model->col_upper_[col] != kHighsInf) { if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleDominatedCol); - fixColToUpper(postsolve_stack, col); + fixColToUpperOrUnbounded(postsolve_stack, col); analysis_.logging_on_ = logging_on; if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleDominatedCol); } else if (impliedDualRowBounds.getSumLowerOrig(col) == 0.0 && @@ -2895,7 +2895,7 @@ HPresolve::Result HPresolve::singletonCol(HighsPostsolveStack& postsolve_stack, if (colDualLower >= -options->dual_feasibility_tolerance) { if (model->col_lower_[col] != -kHighsInf) { if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleDominatedCol); - fixColToLower(postsolve_stack, col); + fixColToLowerOrUnbounded(postsolve_stack, col); analysis_.logging_on_ = logging_on; if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleDominatedCol); } else if (impliedDualRowBounds.getSumUpperOrig(col) == 0.0 && @@ -3344,11 +3344,11 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, // Fix variable if (std::abs(model->col_lower_[x1] - fixVal) <= primal_feastol) { - fixColToLower(postsolve_stack, x1); + fixColToLowerOrUnbounded(postsolve_stack, x1); } else { assert(std::abs(model->col_upper_[x1] - fixVal) <= primal_feastol); - fixColToUpper(postsolve_stack, x1); + fixColToUpperOrUnbounded(postsolve_stack, x1); } rowpositions.erase(rowpositions.begin() + x1Cand); } else { @@ -3863,12 +3863,12 @@ HPresolve::Result HPresolve::emptyCol(HighsPostsolveStack& postsolve_stack, } if (model->col_cost_[col] > 0) - fixColToLower(postsolve_stack, col); + fixColToLowerOrUnbounded(postsolve_stack, col); else if (model->col_cost_[col] < 0 || std::abs(model->col_upper_[col]) < std::abs(model->col_lower_[col])) - fixColToUpper(postsolve_stack, col); + fixColToUpperOrUnbounded(postsolve_stack, col); else if (model->col_lower_[col] != -kHighsInf) - fixColToLower(postsolve_stack, col); + fixColToLowerOrUnbounded(postsolve_stack, col); else fixColToZero(postsolve_stack, col); @@ -3913,7 +3913,7 @@ HPresolve::Result HPresolve::colPresolve(HighsPostsolveStack& postsolve_stack, if (model->col_lower_[col] == -kHighsInf) return Result::kDualInfeasible; else { - fixColToLower(postsolve_stack, col); + fixColToLowerOrUnbounded(postsolve_stack, col); HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); } return checkLimits(postsolve_stack); @@ -3923,7 +3923,7 @@ HPresolve::Result HPresolve::colPresolve(HighsPostsolveStack& postsolve_stack, if (model->col_upper_[col] == kHighsInf) return Result::kDualInfeasible; else { - fixColToUpper(postsolve_stack, col); + fixColToUpperOrUnbounded(postsolve_stack, col); HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); } return checkLimits(postsolve_stack); @@ -3932,7 +3932,7 @@ HPresolve::Result HPresolve::colPresolve(HighsPostsolveStack& postsolve_stack, // check for weakly dominated column if (colDualUpper <= options->dual_feasibility_tolerance) { if (model->col_upper_[col] != kHighsInf) { - fixColToUpper(postsolve_stack, col); + fixColToUpperOrUnbounded(postsolve_stack, col); HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); return checkLimits(postsolve_stack); } else if (impliedDualRowBounds.getSumLowerOrig(col) == 0.0) { @@ -3958,7 +3958,7 @@ HPresolve::Result HPresolve::colPresolve(HighsPostsolveStack& postsolve_stack, } else if (colDualLower >= -options->dual_feasibility_tolerance) { // symmetric case for fixing to the lower bound if (model->col_lower_[col] != -kHighsInf) { - fixColToLower(postsolve_stack, col); + fixColToLowerOrUnbounded(postsolve_stack, col); HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); return checkLimits(postsolve_stack); } else if (impliedDualRowBounds.getSumUpperOrig(col) == 0.0) { @@ -4941,12 +4941,14 @@ void HPresolve::substitute(HighsInt substcol, HighsInt staycol, double offset, } } -void HPresolve::fixColToLower(HighsPostsolveStack& postsolve_stack, - HighsInt col) { +bool HPresolve::fixColToLowerOrUnbounded(HighsPostsolveStack& postsolve_stack, + HighsInt col) { + double fixval = model->col_lower_[col]; + bool unbounded = fixval == -kHighsInf; + if (unbounded) return true; + const bool logging_on = analysis_.logging_on_; if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleFixedCol); - double fixval = model->col_lower_[col]; - assert(fixval != -kHighsInf); // printf("fixing column %" HIGHSINT_FORMAT " to %.15g\n", col, fixval); @@ -4980,14 +4982,18 @@ void HPresolve::fixColToLower(HighsPostsolveStack& postsolve_stack, model->col_cost_[col] = 0; analysis_.logging_on_ = logging_on; if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleFixedCol); + return false; } -void HPresolve::fixColToUpper(HighsPostsolveStack& postsolve_stack, - HighsInt col) { +bool HPresolve::fixColToUpperOrUnbounded(HighsPostsolveStack& postsolve_stack, + HighsInt col) { + double fixval = model->col_upper_[col]; + bool unbounded = fixval == kHighsInf; + if (unbounded) return true; + const bool logging_on = analysis_.logging_on_; if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleFixedCol); - double fixval = model->col_upper_[col]; - assert(fixval != kHighsInf); + // printf("fixing column %" HIGHSINT_FORMAT " to %.15g\n", col, fixval); // mark the column as deleted first so that it is not registered as singleton @@ -5020,6 +5026,7 @@ void HPresolve::fixColToUpper(HighsPostsolveStack& postsolve_stack, model->col_cost_[col] = 0; analysis_.logging_on_ = logging_on; if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleFixedCol); + return false; } void HPresolve::fixColToZero(HighsPostsolveStack& postsolve_stack, @@ -5780,7 +5787,7 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( HighsInt row = Arow[colhead[duplicateCol]]; numRowSingletons[row] -= 1; } - fixColToLower(postsolve_stack, duplicateCol); + fixColToLowerOrUnbounded(postsolve_stack, duplicateCol); break; case kDominanceDuplicateColToUpper: delCol = duplicateCol; @@ -5788,7 +5795,7 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( HighsInt row = Arow[colhead[duplicateCol]]; numRowSingletons[row] -= 1; } - fixColToUpper(postsolve_stack, duplicateCol); + fixColToUpperOrUnbounded(postsolve_stack, duplicateCol); break; case kDominanceColToLower: delCol = col; @@ -5796,7 +5803,7 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( HighsInt row = Arow[colhead[col]]; numRowSingletons[row] -= 1; } - fixColToLower(postsolve_stack, col); + fixColToLowerOrUnbounded(postsolve_stack, col); break; case kDominanceColToUpper: delCol = col; @@ -5804,7 +5811,7 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( HighsInt row = Arow[colhead[col]]; numRowSingletons[row] -= 1; } - fixColToUpper(postsolve_stack, col); + fixColToUpperOrUnbounded(postsolve_stack, col); break; case kMergeParallelCols: const bool ok_merge = postsolve_stack.duplicateColumn( diff --git a/src/presolve/HPresolve.h b/src/presolve/HPresolve.h index fe21860ff0..977606547e 100644 --- a/src/presolve/HPresolve.h +++ b/src/presolve/HPresolve.h @@ -223,9 +223,9 @@ class HPresolve { void markColDeleted(HighsInt col); - void fixColToLower(HighsPostsolveStack& postsolve_stack, HighsInt col); + bool fixColToLowerOrUnbounded(HighsPostsolveStack& postsolve_stack, HighsInt col); - void fixColToUpper(HighsPostsolveStack& postsolve_stack, HighsInt col); + bool fixColToUpperOrUnbounded(HighsPostsolveStack& postsolve_stack, HighsInt col); void fixColToZero(HighsPostsolveStack& postsolve_stack, HighsInt col); From 5c7177a92929502499a52c0e767cc0fd24fb9561 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 19 Aug 2024 14:04:52 +0100 Subject: [PATCH 072/194] Added dumm conditional for unboundedness to be handled --- src/interfaces/highs_c_api.h | 6 +- src/presolve/HPresolve.cpp | 118 +++++++++++++++++++++++++---------- src/presolve/HPresolve.h | 6 +- 3 files changed, 92 insertions(+), 38 deletions(-) diff --git a/src/interfaces/highs_c_api.h b/src/interfaces/highs_c_api.h index a53518e12e..82771d4ac2 100644 --- a/src/interfaces/highs_c_api.h +++ b/src/interfaces/highs_c_api.h @@ -543,9 +543,9 @@ HighsInt Highs_passModel(void* highs, const HighsInt num_col, * in compressed sparse row form), using `q_start`, `q_index`, * and `q_value`.The Hessian matrix is provided to HiGHS as the * lower triangular component in compressed sparse column form. - * The sparse matrix consists of three arrays, `start`, `index`, - * and `value`. `start` is an array of length [num_col] - * containing the starting index of each column in `index`. + * The sparse matrix consists of three arrays, `start`, + * `index`, and `value`. `start` is an array of length [num_col] containing the + * starting index of each column in `index`. * @param index An array of length [num_nz] with indices of matrix entries. * @param value An array of length [num_nz] with values of matrix entries. * diff --git a/src/presolve/HPresolve.cpp b/src/presolve/HPresolve.cpp index 56d6c54eb6..ac28354de9 100644 --- a/src/presolve/HPresolve.cpp +++ b/src/presolve/HPresolve.cpp @@ -1183,7 +1183,9 @@ HPresolve::Result HPresolve::dominatedColumns( checkDomination(-1, j, -1, k)) { // case (iii) lb(x_j) = -inf, -x_j > -x_k: set x_k = ub(x_k) ++numFixedCols; - fixColToLowerOrUnbounded(postsolve_stack, j); + if (fixColToLowerOrUnbounded(postsolve_stack, j)) { + // Handle unboundedness + } HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); break; } else if (-ajBestRowMinus <= ak + options->small_matrix_value && @@ -1192,7 +1194,9 @@ HPresolve::Result HPresolve::dominatedColumns( checkDomination(-1, j, 1, k)) { // case (iv) lb(x_j) = -inf, -x_j > x_k: set x_k = lb(x_k) ++numFixedCols; - fixColToLowerOrUnbounded(postsolve_stack, j); + if (fixColToLowerOrUnbounded(postsolve_stack, j)) { + // Handle unboundedness + } HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); break; } @@ -1223,7 +1227,9 @@ HPresolve::Result HPresolve::dominatedColumns( checkDomination(1, j, 1, k)) { // case (i) ub(x_j) = inf, x_j > x_k: set x_k = lb(x_k) ++numFixedCols; - fixColToUpperOrUnbounded(postsolve_stack, j); + if (fixColToUpperOrUnbounded(postsolve_stack, j)) { + // Handle unboundedness + } HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); break; } else if (ajBestRowPlus <= -ak + options->small_matrix_value && @@ -1232,7 +1238,9 @@ HPresolve::Result HPresolve::dominatedColumns( checkDomination(1, j, -1, k)) { // case (ii) ub(x_j) = inf, x_j > -x_k: set x_k = ub(x_k) ++numFixedCols; - fixColToUpperOrUnbounded(postsolve_stack, j); + if (fixColToUpperOrUnbounded(postsolve_stack, j)) { + // Handle unboundedness + } HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); break; } @@ -1271,7 +1279,9 @@ HPresolve::Result HPresolve::dominatedColumns( checkDomination(1, j, 1, k)) { // case (i) ub(x_j) = inf, x_j > x_k: set x_k = lb(x_k) ++numFixedCols; - fixColToLowerOrUnbounded(postsolve_stack, k); + if (fixColToLowerOrUnbounded(postsolve_stack, k)) { + // Handle unboundedness + } HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); } else if (model->col_upper_[k] != kHighsInf && (upperImplied || @@ -1284,7 +1294,9 @@ HPresolve::Result HPresolve::dominatedColumns( checkDomination(1, j, -1, k)) { // case (ii) ub(x_j) = inf, x_j > -x_k: set x_k = ub(x_k) ++numFixedCols; - fixColToUpperOrUnbounded(postsolve_stack, k); + if (fixColToUpperOrUnbounded(postsolve_stack, k)) { + // Handle unboundedness + } HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); } } @@ -1313,7 +1325,9 @@ HPresolve::Result HPresolve::dominatedColumns( checkDomination(-1, j, -1, k)) { // case (iii) lb(x_j) = -inf, -x_j > -x_k: set x_k = ub(x_k) ++numFixedCols; - fixColToUpperOrUnbounded(postsolve_stack, k); + if (fixColToUpperOrUnbounded(postsolve_stack, k)) { + // Handle unboundedness + } HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); } else if (model->col_lower_[k] != -kHighsInf && (lowerImplied || @@ -1326,7 +1340,9 @@ HPresolve::Result HPresolve::dominatedColumns( checkDomination(-1, j, 1, k)) { // case (iv) lb(x_j) = -inf, -x_j > x_k: set x_k = lb(x_k) ++numFixedCols; - fixColToLowerOrUnbounded(postsolve_stack, k); + if (fixColToLowerOrUnbounded(postsolve_stack, k)) { + // Handle unboundedness + } HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); } } @@ -2837,7 +2853,9 @@ HPresolve::Result HPresolve::singletonCol(HighsPostsolveStack& postsolve_stack, if (colDualLower > options->dual_feasibility_tolerance) { if (model->col_lower_[col] == -kHighsInf) return Result::kDualInfeasible; if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleDominatedCol); - fixColToLowerOrUnbounded(postsolve_stack, col); + if (fixColToLowerOrUnbounded(postsolve_stack, col)) { + // Handle unboundedness + } analysis_.logging_on_ = logging_on; if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleDominatedCol); return checkLimits(postsolve_stack); @@ -2846,7 +2864,9 @@ HPresolve::Result HPresolve::singletonCol(HighsPostsolveStack& postsolve_stack, if (colDualUpper < -options->dual_feasibility_tolerance) { if (model->col_upper_[col] == kHighsInf) return Result::kDualInfeasible; if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleDominatedCol); - fixColToUpperOrUnbounded(postsolve_stack, col); + if (fixColToUpperOrUnbounded(postsolve_stack, col)) { + // Handle unboundedness + } analysis_.logging_on_ = logging_on; if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleDominatedCol); return checkLimits(postsolve_stack); @@ -2856,7 +2876,9 @@ HPresolve::Result HPresolve::singletonCol(HighsPostsolveStack& postsolve_stack, if (colDualUpper <= options->dual_feasibility_tolerance) { if (model->col_upper_[col] != kHighsInf) { if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleDominatedCol); - fixColToUpperOrUnbounded(postsolve_stack, col); + if (fixColToUpperOrUnbounded(postsolve_stack, col)) { + // Handle unboundedness + } analysis_.logging_on_ = logging_on; if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleDominatedCol); } else if (impliedDualRowBounds.getSumLowerOrig(col) == 0.0 && @@ -2895,7 +2917,9 @@ HPresolve::Result HPresolve::singletonCol(HighsPostsolveStack& postsolve_stack, if (colDualLower >= -options->dual_feasibility_tolerance) { if (model->col_lower_[col] != -kHighsInf) { if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleDominatedCol); - fixColToLowerOrUnbounded(postsolve_stack, col); + if (fixColToLowerOrUnbounded(postsolve_stack, col)) { + // Handle unboundedness + } analysis_.logging_on_ = logging_on; if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleDominatedCol); } else if (impliedDualRowBounds.getSumUpperOrig(col) == 0.0 && @@ -3344,11 +3368,15 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, // Fix variable if (std::abs(model->col_lower_[x1] - fixVal) <= primal_feastol) { - fixColToLowerOrUnbounded(postsolve_stack, x1); + if (fixColToLowerOrUnbounded(postsolve_stack, x1)) { + // Handle unboundedness + } } else { assert(std::abs(model->col_upper_[x1] - fixVal) <= primal_feastol); - fixColToUpperOrUnbounded(postsolve_stack, x1); + if (fixColToUpperOrUnbounded(postsolve_stack, x1)) { + // Handle unboundedness + } } rowpositions.erase(rowpositions.begin() + x1Cand); } else { @@ -3862,15 +3890,23 @@ HPresolve::Result HPresolve::emptyCol(HighsPostsolveStack& postsolve_stack, return Result::kDualInfeasible; } - if (model->col_cost_[col] > 0) - fixColToLowerOrUnbounded(postsolve_stack, col); - else if (model->col_cost_[col] < 0 || - std::abs(model->col_upper_[col]) < std::abs(model->col_lower_[col])) - fixColToUpperOrUnbounded(postsolve_stack, col); - else if (model->col_lower_[col] != -kHighsInf) - fixColToLowerOrUnbounded(postsolve_stack, col); - else + if (model->col_cost_[col] > 0) { + if (fixColToLowerOrUnbounded(postsolve_stack, col)) { + // Handle unboundedness + } + } else if (model->col_cost_[col] < 0 || + std::abs(model->col_upper_[col]) < + std::abs(model->col_lower_[col])) { + if (fixColToUpperOrUnbounded(postsolve_stack, col)) { + // Handle unboundedness + } + } else if (model->col_lower_[col] != -kHighsInf) { + if (fixColToLowerOrUnbounded(postsolve_stack, col)) { + // Handle unboundedness + } + } else { fixColToZero(postsolve_stack, col); + } analysis_.logging_on_ = logging_on; if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleEmptyCol); @@ -3913,7 +3949,9 @@ HPresolve::Result HPresolve::colPresolve(HighsPostsolveStack& postsolve_stack, if (model->col_lower_[col] == -kHighsInf) return Result::kDualInfeasible; else { - fixColToLowerOrUnbounded(postsolve_stack, col); + if (fixColToLowerOrUnbounded(postsolve_stack, col)) { + // Handle unboundedness + } HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); } return checkLimits(postsolve_stack); @@ -3923,7 +3961,9 @@ HPresolve::Result HPresolve::colPresolve(HighsPostsolveStack& postsolve_stack, if (model->col_upper_[col] == kHighsInf) return Result::kDualInfeasible; else { - fixColToUpperOrUnbounded(postsolve_stack, col); + if (fixColToUpperOrUnbounded(postsolve_stack, col)) { + // Handle unboundedness + } HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); } return checkLimits(postsolve_stack); @@ -3932,7 +3972,9 @@ HPresolve::Result HPresolve::colPresolve(HighsPostsolveStack& postsolve_stack, // check for weakly dominated column if (colDualUpper <= options->dual_feasibility_tolerance) { if (model->col_upper_[col] != kHighsInf) { - fixColToUpperOrUnbounded(postsolve_stack, col); + if (fixColToUpperOrUnbounded(postsolve_stack, col)) { + // Handle unboundedness + } HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); return checkLimits(postsolve_stack); } else if (impliedDualRowBounds.getSumLowerOrig(col) == 0.0) { @@ -3958,7 +4000,9 @@ HPresolve::Result HPresolve::colPresolve(HighsPostsolveStack& postsolve_stack, } else if (colDualLower >= -options->dual_feasibility_tolerance) { // symmetric case for fixing to the lower bound if (model->col_lower_[col] != -kHighsInf) { - fixColToLowerOrUnbounded(postsolve_stack, col); + if (fixColToLowerOrUnbounded(postsolve_stack, col)) { + // Handle unboundedness + } HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); return checkLimits(postsolve_stack); } else if (impliedDualRowBounds.getSumUpperOrig(col) == 0.0) { @@ -4942,7 +4986,7 @@ void HPresolve::substitute(HighsInt substcol, HighsInt staycol, double offset, } bool HPresolve::fixColToLowerOrUnbounded(HighsPostsolveStack& postsolve_stack, - HighsInt col) { + HighsInt col) { double fixval = model->col_lower_[col]; bool unbounded = fixval == -kHighsInf; if (unbounded) return true; @@ -4986,7 +5030,7 @@ bool HPresolve::fixColToLowerOrUnbounded(HighsPostsolveStack& postsolve_stack, } bool HPresolve::fixColToUpperOrUnbounded(HighsPostsolveStack& postsolve_stack, - HighsInt col) { + HighsInt col) { double fixval = model->col_upper_[col]; bool unbounded = fixval == kHighsInf; if (unbounded) return true; @@ -5026,7 +5070,7 @@ bool HPresolve::fixColToUpperOrUnbounded(HighsPostsolveStack& postsolve_stack, model->col_cost_[col] = 0; analysis_.logging_on_ = logging_on; if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleFixedCol); - return false; + return false; } void HPresolve::fixColToZero(HighsPostsolveStack& postsolve_stack, @@ -5787,7 +5831,9 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( HighsInt row = Arow[colhead[duplicateCol]]; numRowSingletons[row] -= 1; } - fixColToLowerOrUnbounded(postsolve_stack, duplicateCol); + if (fixColToLowerOrUnbounded(postsolve_stack, duplicateCol)) { + // Handle unboundedness + } break; case kDominanceDuplicateColToUpper: delCol = duplicateCol; @@ -5795,7 +5841,9 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( HighsInt row = Arow[colhead[duplicateCol]]; numRowSingletons[row] -= 1; } - fixColToUpperOrUnbounded(postsolve_stack, duplicateCol); + if (fixColToUpperOrUnbounded(postsolve_stack, duplicateCol)) { + // Handle unboundedness + } break; case kDominanceColToLower: delCol = col; @@ -5803,7 +5851,9 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( HighsInt row = Arow[colhead[col]]; numRowSingletons[row] -= 1; } - fixColToLowerOrUnbounded(postsolve_stack, col); + if (fixColToLowerOrUnbounded(postsolve_stack, col)) { + // Handle unboundedness + } break; case kDominanceColToUpper: delCol = col; @@ -5811,7 +5861,9 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( HighsInt row = Arow[colhead[col]]; numRowSingletons[row] -= 1; } - fixColToUpperOrUnbounded(postsolve_stack, col); + if (fixColToUpperOrUnbounded(postsolve_stack, col)) { + // Handle unboundedness + } break; case kMergeParallelCols: const bool ok_merge = postsolve_stack.duplicateColumn( diff --git a/src/presolve/HPresolve.h b/src/presolve/HPresolve.h index 977606547e..9c0511b3e6 100644 --- a/src/presolve/HPresolve.h +++ b/src/presolve/HPresolve.h @@ -223,9 +223,11 @@ class HPresolve { void markColDeleted(HighsInt col); - bool fixColToLowerOrUnbounded(HighsPostsolveStack& postsolve_stack, HighsInt col); + bool fixColToLowerOrUnbounded(HighsPostsolveStack& postsolve_stack, + HighsInt col); - bool fixColToUpperOrUnbounded(HighsPostsolveStack& postsolve_stack, HighsInt col); + bool fixColToUpperOrUnbounded(HighsPostsolveStack& postsolve_stack, + HighsInt col); void fixColToZero(HighsPostsolveStack& postsolve_stack, HighsInt col); From 2f37aa35d7d0c425357d5ba6a2597926d5110960 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 19 Aug 2024 18:11:04 +0100 Subject: [PATCH 073/194] Now handles all returns of true for fixColToLower/UpperOrUnbounded --- src/presolve/HPresolve.cpp | 52 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/presolve/HPresolve.cpp b/src/presolve/HPresolve.cpp index ac28354de9..9229c8c264 100644 --- a/src/presolve/HPresolve.cpp +++ b/src/presolve/HPresolve.cpp @@ -1185,6 +1185,8 @@ HPresolve::Result HPresolve::dominatedColumns( ++numFixedCols; if (fixColToLowerOrUnbounded(postsolve_stack, j)) { // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; } HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); break; @@ -1196,6 +1198,8 @@ HPresolve::Result HPresolve::dominatedColumns( ++numFixedCols; if (fixColToLowerOrUnbounded(postsolve_stack, j)) { // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; } HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); break; @@ -1229,6 +1233,8 @@ HPresolve::Result HPresolve::dominatedColumns( ++numFixedCols; if (fixColToUpperOrUnbounded(postsolve_stack, j)) { // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; } HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); break; @@ -1240,6 +1246,8 @@ HPresolve::Result HPresolve::dominatedColumns( ++numFixedCols; if (fixColToUpperOrUnbounded(postsolve_stack, j)) { // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; } HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); break; @@ -1281,6 +1289,8 @@ HPresolve::Result HPresolve::dominatedColumns( ++numFixedCols; if (fixColToLowerOrUnbounded(postsolve_stack, k)) { // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; } HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); } else if (model->col_upper_[k] != kHighsInf && @@ -1296,6 +1306,8 @@ HPresolve::Result HPresolve::dominatedColumns( ++numFixedCols; if (fixColToUpperOrUnbounded(postsolve_stack, k)) { // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; } HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); } @@ -1327,6 +1339,8 @@ HPresolve::Result HPresolve::dominatedColumns( ++numFixedCols; if (fixColToUpperOrUnbounded(postsolve_stack, k)) { // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; } HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); } else if (model->col_lower_[k] != -kHighsInf && @@ -1342,6 +1356,8 @@ HPresolve::Result HPresolve::dominatedColumns( ++numFixedCols; if (fixColToLowerOrUnbounded(postsolve_stack, k)) { // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; } HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); } @@ -2855,6 +2871,8 @@ HPresolve::Result HPresolve::singletonCol(HighsPostsolveStack& postsolve_stack, if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleDominatedCol); if (fixColToLowerOrUnbounded(postsolve_stack, col)) { // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; } analysis_.logging_on_ = logging_on; if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleDominatedCol); @@ -2866,6 +2884,8 @@ HPresolve::Result HPresolve::singletonCol(HighsPostsolveStack& postsolve_stack, if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleDominatedCol); if (fixColToUpperOrUnbounded(postsolve_stack, col)) { // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; } analysis_.logging_on_ = logging_on; if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleDominatedCol); @@ -2878,6 +2898,8 @@ HPresolve::Result HPresolve::singletonCol(HighsPostsolveStack& postsolve_stack, if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleDominatedCol); if (fixColToUpperOrUnbounded(postsolve_stack, col)) { // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; } analysis_.logging_on_ = logging_on; if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleDominatedCol); @@ -2919,6 +2941,8 @@ HPresolve::Result HPresolve::singletonCol(HighsPostsolveStack& postsolve_stack, if (logging_on) analysis_.startPresolveRuleLog(kPresolveRuleDominatedCol); if (fixColToLowerOrUnbounded(postsolve_stack, col)) { // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; } analysis_.logging_on_ = logging_on; if (logging_on) analysis_.stopPresolveRuleLog(kPresolveRuleDominatedCol); @@ -3370,12 +3394,18 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, primal_feastol) { if (fixColToLowerOrUnbounded(postsolve_stack, x1)) { // Handle unboundedness + presolve_status_ = + HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; } } else { assert(std::abs(model->col_upper_[x1] - fixVal) <= primal_feastol); if (fixColToUpperOrUnbounded(postsolve_stack, x1)) { // Handle unboundedness + presolve_status_ = + HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; } } rowpositions.erase(rowpositions.begin() + x1Cand); @@ -3893,16 +3923,22 @@ HPresolve::Result HPresolve::emptyCol(HighsPostsolveStack& postsolve_stack, if (model->col_cost_[col] > 0) { if (fixColToLowerOrUnbounded(postsolve_stack, col)) { // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; } } else if (model->col_cost_[col] < 0 || std::abs(model->col_upper_[col]) < std::abs(model->col_lower_[col])) { if (fixColToUpperOrUnbounded(postsolve_stack, col)) { // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; } } else if (model->col_lower_[col] != -kHighsInf) { if (fixColToLowerOrUnbounded(postsolve_stack, col)) { // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; } } else { fixColToZero(postsolve_stack, col); @@ -3951,6 +3987,8 @@ HPresolve::Result HPresolve::colPresolve(HighsPostsolveStack& postsolve_stack, else { if (fixColToLowerOrUnbounded(postsolve_stack, col)) { // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; } HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); } @@ -3963,6 +4001,8 @@ HPresolve::Result HPresolve::colPresolve(HighsPostsolveStack& postsolve_stack, else { if (fixColToUpperOrUnbounded(postsolve_stack, col)) { // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; } HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); } @@ -3974,6 +4014,8 @@ HPresolve::Result HPresolve::colPresolve(HighsPostsolveStack& postsolve_stack, if (model->col_upper_[col] != kHighsInf) { if (fixColToUpperOrUnbounded(postsolve_stack, col)) { // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; } HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); return checkLimits(postsolve_stack); @@ -4002,6 +4044,8 @@ HPresolve::Result HPresolve::colPresolve(HighsPostsolveStack& postsolve_stack, if (model->col_lower_[col] != -kHighsInf) { if (fixColToLowerOrUnbounded(postsolve_stack, col)) { // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; } HPRESOLVE_CHECKED_CALL(removeRowSingletons(postsolve_stack)); return checkLimits(postsolve_stack); @@ -5833,6 +5877,8 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( } if (fixColToLowerOrUnbounded(postsolve_stack, duplicateCol)) { // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; } break; case kDominanceDuplicateColToUpper: @@ -5843,6 +5889,8 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( } if (fixColToUpperOrUnbounded(postsolve_stack, duplicateCol)) { // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; } break; case kDominanceColToLower: @@ -5853,6 +5901,8 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( } if (fixColToLowerOrUnbounded(postsolve_stack, col)) { // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; } break; case kDominanceColToUpper: @@ -5863,6 +5913,8 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( } if (fixColToUpperOrUnbounded(postsolve_stack, col)) { // Handle unboundedness + presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible; + return Result::kDualInfeasible; } break; case kMergeParallelCols: From cf82e09c7dc342ad688b77eb59e4f63d32f11abd Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 19 Aug 2024 18:19:44 +0100 Subject: [PATCH 074/194] Corrected formatting in highs_c_api.h --- src/interfaces/highs_c_api.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/interfaces/highs_c_api.h b/src/interfaces/highs_c_api.h index 82771d4ac2..3a0c9c05c9 100644 --- a/src/interfaces/highs_c_api.h +++ b/src/interfaces/highs_c_api.h @@ -544,8 +544,9 @@ HighsInt Highs_passModel(void* highs, const HighsInt num_col, * and `q_value`.The Hessian matrix is provided to HiGHS as the * lower triangular component in compressed sparse column form. * The sparse matrix consists of three arrays, `start`, - * `index`, and `value`. `start` is an array of length [num_col] containing the - * starting index of each column in `index`. + * `index`, and `value`. `start` is an array of length + * [num_col] containing the starting index of each column in + * `index`. * @param index An array of length [num_nz] with indices of matrix entries. * @param value An array of length [num_nz] with values of matrix entries. * From e9f423506dc14a7a6c2b2eecf6c889533e3f95d2 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 19 Aug 2024 13:25:18 -0600 Subject: [PATCH 075/194] run solve in background thread so that keyboard interrupt works --- src/highspy/highs.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/highspy/highs.py b/src/highspy/highs.py index 9abf20ea26..456708e3c2 100644 --- a/src/highspy/highs.py +++ b/src/highspy/highs.py @@ -36,6 +36,13 @@ from itertools import groupby from operator import itemgetter from decimal import Decimal +from threading import Thread + + +class _ThreadingResult: + def __init__(self): + self.out = None + class Highs(_Highs): """HiGHS solver interface""" @@ -51,10 +58,20 @@ def __init__(self): # Silence logging def silent(self): super().setOptionValue("output_flag", False) + + def _run(self, res): + res.out = super().run() + + def run(self): + return self.solve() # solve def solve(self): - return super().run() + res = _ThreadingResult() + t = Thread(target=self._run, args=(res,)) + t.start() + t.join() + return res.out # reset the objective and sense, then solve def minimize(self, obj=None): From 50bd1806ddbdcb42bd4ff76f875d8dcf3e5e0b7f Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 21 Aug 2024 11:20:26 +0200 Subject: [PATCH 076/194] Fix warning regarding -Wreorder --- src/lp_data/HighsOptions.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lp_data/HighsOptions.h b/src/lp_data/HighsOptions.h index 8f934b30ce..df45d4de87 100644 --- a/src/lp_data/HighsOptions.h +++ b/src/lp_data/HighsOptions.h @@ -440,7 +440,6 @@ struct HighsOptionsStruct { time_limit(0.0), solution_file(""), write_model_file(""), - write_presolved_model_file(""), random_seed(0), ranging(""), infinite_cost(0.0), @@ -472,6 +471,7 @@ struct HighsOptionsStruct { write_solution_to_file(false), write_solution_style(0), glpsol_cost_row_location(0), + write_presolved_model_file(""), output_flag(false), log_to_console(false), ipm_iteration_limit(0), From b1a7d2e79ea7b49e06ce12e278d53c5fccd90ee1 Mon Sep 17 00:00:00 2001 From: jajhall Date: Thu, 22 Aug 2024 10:28:07 +0100 Subject: [PATCH 077/194] col_ecol_offset now set - as const - for greater clarity --- src/lp_data/HighsInterface.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 91881b3aee..6176c39bf1 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -1782,8 +1782,6 @@ HighsStatus Highs::elasticityFilter( HighsInt evar_ix = lp.num_col_; HighsStatus run_status; const bool write_model = false; - HighsInt col_ecol_offset; - HighsInt row_ecol_offset; // Take copies of the original model dimensions and column data // vectors, as they will be modified in forming the e-LP const HighsInt original_num_col = lp.num_col_; @@ -1823,12 +1821,12 @@ HighsStatus Highs::elasticityFilter( const bool has_elastic_rows = has_local_rhs_penalty || has_global_elastic_rhs; assert(has_elastic_columns || has_elastic_rows); + const HighsInt col_ecol_offset = lp.num_col_; if (has_elastic_columns) { // Accumulate bounds to be used for columns std::vector col_lower; std::vector col_upper; // When defining names, need to know the column number - HighsInt previous_num_col = lp.num_col_; const bool has_col_names = lp.col_names_.size() > 0; erow_start.push_back(0); for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { @@ -1930,7 +1928,7 @@ HighsStatus Highs::elasticityFilter( assert(run_status == HighsStatus::kOk); if (has_col_names) { for (HighsInt iCol = 0; iCol < num_new_col; iCol++) - this->passColName(previous_num_col + iCol, ecol_name[iCol]); + this->passColName(col_ecol_offset + iCol, ecol_name[iCol]); for (HighsInt iRow = 0; iRow < num_new_row; iRow++) this->passRowName(original_num_row + iRow, erow_name[iRow]); } @@ -1946,11 +1944,10 @@ HighsStatus Highs::elasticityFilter( this->setOptionValue("output_flag", output_flag); } } + const HighsInt row_ecol_offset = lp.num_col_; if (has_elastic_rows) { // Add the columns corresponding to the e_L and e_U variables for // the constraints - HighsInt previous_num_col = lp.num_col_; - row_ecol_offset = previous_num_col; ecol_name.clear(); ecol_cost.clear(); std::vector ecol_start; @@ -2011,7 +2008,7 @@ HighsStatus Highs::elasticityFilter( assert(run_status == HighsStatus::kOk); if (has_row_names) { for (HighsInt iCol = 0; iCol < num_new_col; iCol++) - this->passColName(previous_num_col + iCol, ecol_name[iCol]); + this->passColName(row_ecol_offset + iCol, ecol_name[iCol]); } if (write_model) { From 3271ee8a23ee63adc40e2b27ae38d126b3c0ee79 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sun, 25 Aug 2024 18:05:04 +0100 Subject: [PATCH 078/194] Introduced examples/plot_highs_log.py and final logging line in MIP solver to allow complete plotting --- src/mip/HighsMipSolver.cpp | 17 ++++++++++++----- src/mip/HighsMipSolverData.cpp | 9 +++++---- src/mip/HighsMipSolverData.h | 2 +- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/mip/HighsMipSolver.cpp b/src/mip/HighsMipSolver.cpp index 4724f1a33b..b1478a5c24 100644 --- a/src/mip/HighsMipSolver.cpp +++ b/src/mip/HighsMipSolver.cpp @@ -29,6 +29,8 @@ using std::fabs; +const bool report_mip_timing = false; + HighsMipSolver::HighsMipSolver(HighsCallback& callback, const HighsOptions& options, const HighsLp& lp, const HighsSolution& solution, bool submip) @@ -115,7 +117,7 @@ void HighsMipSolver::run() { mipdata_ = decltype(mipdata_)(new HighsMipSolverData(*this)); mipdata_->init(); mipdata_->runPresolve(options_mip_->presolve_reduction_limit); - if (!submip) + if (report_mip_timing & !submip) highsLogUser(options_mip_->log_options, HighsLogType::kInfo, "MIP-Timing: After %6.4fs - completed mipdata_->runPresolve\n", timer_.read(timer_.solve_clock)); @@ -138,12 +140,12 @@ void HighsMipSolver::run() { return; } - if (!submip) + if (report_mip_timing & !submip) highsLogUser(options_mip_->log_options, HighsLogType::kInfo, "MIP-Timing: After %6.4fs - reached mipdata_->runSetup()\n", timer_.read(timer_.solve_clock)); mipdata_->runSetup(); - if (!submip) + if (report_mip_timing & !submip) highsLogUser(options_mip_->log_options, HighsLogType::kInfo, "MIP-Timing: After %6.4fs - completed mipdata_->runSetup()\n", timer_.read(timer_.solve_clock)); @@ -154,13 +156,13 @@ void HighsMipSolver::run() { cleanupSolve(); return; } - if (!submip) + if (report_mip_timing & !submip) highsLogUser( options_mip_->log_options, HighsLogType::kInfo, "MIP-Timing: After %6.4fs - reached mipdata_->evaluateRootNode()\n", timer_.read(timer_.solve_clock)); mipdata_->evaluateRootNode(); - if (!submip) + if (report_mip_timing & !submip) highsLogUser( options_mip_->log_options, HighsLogType::kInfo, "MIP-Timing: After %6.4fs - completed mipdata_->evaluateRootNode()\n", @@ -174,6 +176,10 @@ void HighsMipSolver::run() { mipdata_->cutpool.performAging(); } if (mipdata_->nodequeue.empty() || mipdata_->checkLimits()) { + if (!submip) + printf( + "HighsMipSolver::run() mipdata_->nodequeue.empty() || " + "mipdata_->checkLimits()\n"); cleanupSolve(); return; } @@ -524,6 +530,7 @@ void HighsMipSolver::run() { } void HighsMipSolver::cleanupSolve() { + mipdata_->printDisplayLine('Z'); timer_.start(timer_.postsolve_clock); bool havesolution = solution_objective_ != kHighsInf; bool feasible; diff --git a/src/mip/HighsMipSolverData.cpp b/src/mip/HighsMipSolverData.cpp index 9dcd8129b6..d0b1dfbee6 100644 --- a/src/mip/HighsMipSolverData.cpp +++ b/src/mip/HighsMipSolverData.cpp @@ -1199,7 +1199,7 @@ static std::array convertToPrintString(double val, return printString; } -void HighsMipSolverData::printDisplayLine(char first) { +void HighsMipSolverData::printDisplayLine(char source) { // MIP logging method // // Note that if the original problem is a maximization, the cost @@ -1214,10 +1214,11 @@ void HighsMipSolverData::printDisplayLine(char first) { if (!output_flag) return; double time = mipsolver.timer_.read(mipsolver.timer_.solve_clock); - if (first == ' ' && + if (source == ' ' && time - last_disptime < mipsolver.options_mip_->mip_min_logging_interval) return; last_disptime = time; + char use_source = source != 'Z' ? source : ' '; if (num_disp_lines % 20 == 0) { highsLogUser( @@ -1284,7 +1285,7 @@ void HighsMipSolverData::printDisplayLine(char first) { // clang-format off " %c %7s %7s %7s %6.2f%% %-15s %-15s %8s %6" HIGHSINT_FORMAT " %6" HIGHSINT_FORMAT " %6" HIGHSINT_FORMAT " %7s %7.1fs\n", // clang-format on - first, print_nodes.data(), queue_nodes.data(), print_leaves.data(), + use_source, print_nodes.data(), queue_nodes.data(), print_leaves.data(), explored, lb_string.data(), ub_string.data(), gap_string.data(), cutpool.getNumCuts(), lp.numRows() - lp.getNumModelRows(), conflictPool.getNumConflicts(), print_lp_iters.data(), time); @@ -1305,7 +1306,7 @@ void HighsMipSolverData::printDisplayLine(char first) { // clang-format off " %c %7s %7s %7s %6.2f%% %-15s %-15s %8.2f %6" HIGHSINT_FORMAT " %6" HIGHSINT_FORMAT " %6" HIGHSINT_FORMAT " %7s %7.1fs\n", // clang-format on - first, print_nodes.data(), queue_nodes.data(), print_leaves.data(), + use_source, print_nodes.data(), queue_nodes.data(), print_leaves.data(), explored, lb_string.data(), ub_string.data(), gap, cutpool.getNumCuts(), lp.numRows() - lp.getNumModelRows(), conflictPool.getNumConflicts(), print_lp_iters.data(), time); diff --git a/src/mip/HighsMipSolverData.h b/src/mip/HighsMipSolverData.h index cbf697fcfc..1d7eb87af3 100644 --- a/src/mip/HighsMipSolverData.h +++ b/src/mip/HighsMipSolverData.h @@ -215,7 +215,7 @@ struct HighsMipSolverData { const std::vector& getSolution() const; - void printDisplayLine(char first = ' '); + void printDisplayLine(char source = ' '); void getRow(HighsInt row, HighsInt& rowlen, const HighsInt*& rowinds, const double*& rowvals) const { From a154ba8bebf8f8227d986c2c80238abd764be391 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sun, 25 Aug 2024 18:47:55 +0100 Subject: [PATCH 079/194] Now plotting gap --- examples/plot_highs_log.py | 148 +++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 examples/plot_highs_log.py diff --git a/examples/plot_highs_log.py b/examples/plot_highs_log.py new file mode 100644 index 0000000000..ac3c6e9fc6 --- /dev/null +++ b/examples/plot_highs_log.py @@ -0,0 +1,148 @@ +import re +import matplotlib.pyplot as plt +import numpy as np + + +def parse_highs_log(log_file_path): + last_full_entry = [] + current_entry = [] + found_solution = False + + with open(log_file_path, "r") as f: + for line in f: + if "Running HiGHS" in line: + if found_solution: + last_full_entry = current_entry + current_entry = [line] + found_solution = False + else: + current_entry.append(line) + if "Writing the solution to" in line: + found_solution = True + + if not last_full_entry: + last_full_entry = current_entry + + if not last_full_entry: + return None, None, None, None, None, None + + time_values, best_bound_values, best_sol_values, in_queue_values, expl_values, gap_values = ( + [], + [], + [], + [], + [], + [], + ) + for line in last_full_entry: + match = re.search(r"\dk?\s+\d+\.\ds$", line) + + if not match: + continue + + tokens = line.split() + if len(tokens) == 13: + tokens = tokens[1:] + assert len(tokens) == 12, f"{line}" + + in_queue_values.append(float(tokens[1])) # InQueue + expl_values.append(float(tokens[3].replace("%", ""))) # Expl.% + best_bound_values.append(float(tokens[4].replace("inf", "nan"))) # Best Bound + best_sol_values.append(float(tokens[5].replace("inf", "nan"))) # Best Sol + gap_values.append( + float(tokens[6].replace("%", "").replace("inf", "nan").replace("Large", "nan")) + ) # Gap% + time_values.append(float(tokens[11].replace("s", ""))) # Time + + return time_values, best_bound_values, best_sol_values, in_queue_values, expl_values, gap_values + + +def plot_highs_log( + time_values, best_bound_values, best_sol_values, in_queue_values, expl_values, gap_values +): + fig, ax1 = plt.subplots(figsize=(10, 6)) + + # Plot Objective Bounds + ax1.plot(time_values, best_bound_values, label="Best Bound", color="blue") + ax1.plot(time_values, best_sol_values, label="Best Solution", color="green") + ax1.set_xlabel("Time (seconds)") + ax1.set_ylabel("Objective Bounds", color="blue") + ax1.tick_params(axis="y", labelcolor="blue") + + # Limit y-axis to the range between min and max of the non-NaN values + valid_gap_index = next(i for i, gap in enumerate(gap_values) if not np.isnan(gap)) + min_y = min(best_bound_values[valid_gap_index], best_sol_values[valid_gap_index]) + max_y = max(best_bound_values[valid_gap_index], best_sol_values[valid_gap_index]) + padding = (max_y - min_y) * 0.1 + ax1.set_ylim(min_y - padding, max_y + padding) + + # Add second y-axis for InQueue values + ax2 = ax1.twinx() + ax2.plot(time_values, in_queue_values, label="InQueue", color="red") + #ax2.set_ylabel("InQueue", color="red") + ax2.tick_params(axis="y", labelcolor="red") + + # Add third y-axis for Explored % values (scaled) + ax3 = ax1.twinx() + ax3.spines["right"].set_position(("outward", 30)) + ax3.plot(time_values, expl_values, label="Explored %", color="purple") + #ax3.set_ylabel("Expl.%", color="purple") + ax3.tick_params(axis="y", labelcolor="purple") + + # Add fourth y-axis for Gap % values (scaled) + ax4 = ax1.twinx() + ax4.spines["right"].set_position(("outward", 60)) + ax4.plot(time_values, gap_values, label="Gap %", color="orange")#, linestyle="--")#, linewidth=0.5) + #ax4.set_ylabel("Relative gap.%", color="orange") + ax4.tick_params(axis="y", labelcolor="orange") + + # Determine the interval for labeling Gap% + #interval = max(1, len(time_values) // 10) # Adjust interval for more labels + + # Add Gap% as text labels above the time axis, matching the color with Best Solution line + #for i, (t, gap) in enumerate(zip(time_values, gap_values)): + #if i % interval == 0: # Label at defined intervals + #ax1.text( + #t, + #ax1.get_ylim()[0], + #f"{gap:.2f}%", + #color="green", + #ha="center", + #va="bottom", + #fontsize=10, + #) + + # Add a Gap% label to indicate what the text values represent + #ax1.text( + #0.48, + #0.05, + #"Gap%", + #color="green", + #ha="left", + #va="center", + #transform=ax1.transAxes, + #fontsize=8, + #) + + # Plot vertical hash lines where Best Solution changes + for i in range(1, len(best_sol_values)): + if best_sol_values[i] != best_sol_values[i - 1]: # Change detected + ax1.axvline(x=time_values[i], color="grey", linestyle="--", linewidth=0.5) + + # Set up legend + fig.legend(loc="lower left") + + # Show plot + plt.title("HiGHS MIP Log Analysis") + plt.show() + + +#log_file_path = "/path/to/your/logfile.log" +log_file_path = "HiGHS.log" +time_values, best_bound_values, best_sol_values, in_queue_values, expl_values, gap_values = ( + parse_highs_log(log_file_path) +) + +plot_highs_log( + time_values, best_bound_values, best_sol_values, in_queue_values, expl_values, gap_values +) From fedcf783a3e55701d8e16c87d199e34a086b6e7f Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sun, 25 Aug 2024 22:50:48 +0100 Subject: [PATCH 080/194] Updated examples/plot_highs_log.py --- FEATURES.md | 3 +++ examples/plot_highs_log.py | 52 +++++++++++--------------------------- 2 files changed, 18 insertions(+), 37 deletions(-) diff --git a/FEATURES.md b/FEATURES.md index 6c66f9d67b..f0a52fcf7f 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -14,5 +14,8 @@ Added options `write_presolved_model_to_file` and `write_presolved_model_file` s Added `Highs::feasibilityRelaxation` to solve the problem of minimizing a (possibly weighted) sum of (allowable) infeasibilities in an LP/MIP. +Added Python utility `examples/plot_highs_log.py` (due to @Thell) to visualise progress of the MIP solver. + + diff --git a/examples/plot_highs_log.py b/examples/plot_highs_log.py index ac3c6e9fc6..492f9be895 100644 --- a/examples/plot_highs_log.py +++ b/examples/plot_highs_log.py @@ -66,7 +66,7 @@ def plot_highs_log( ax1.plot(time_values, best_bound_values, label="Best Bound", color="blue") ax1.plot(time_values, best_sol_values, label="Best Solution", color="green") ax1.set_xlabel("Time (seconds)") - ax1.set_ylabel("Objective Bounds", color="blue") + ax1.set_ylabel("Objective Bounds", color="blue", labelpad=15) ax1.tick_params(axis="y", labelcolor="blue") # Limit y-axis to the range between min and max of the non-NaN values @@ -79,61 +79,39 @@ def plot_highs_log( # Add second y-axis for InQueue values ax2 = ax1.twinx() ax2.plot(time_values, in_queue_values, label="InQueue", color="red") - #ax2.set_ylabel("InQueue", color="red") +# ax2.set_ylabel("InQueue", color="red", loc="top", labelpad=12) + ax2.yaxis.label.set_rotation(0) ax2.tick_params(axis="y", labelcolor="red") # Add third y-axis for Explored % values (scaled) ax3 = ax1.twinx() - ax3.spines["right"].set_position(("outward", 30)) + ax3.spines["right"].set_position(("outward", 50)) ax3.plot(time_values, expl_values, label="Explored %", color="purple") - #ax3.set_ylabel("Expl.%", color="purple") +# ax3.set_ylabel("Expl.%", color="purple", loc="top", labelpad=10) + ax3.yaxis.label.set_rotation(0) ax3.tick_params(axis="y", labelcolor="purple") # Add fourth y-axis for Gap % values (scaled) ax4 = ax1.twinx() - ax4.spines["right"].set_position(("outward", 60)) - ax4.plot(time_values, gap_values, label="Gap %", color="orange")#, linestyle="--")#, linewidth=0.5) - #ax4.set_ylabel("Relative gap.%", color="orange") + ax4.spines["right"].set_position(("outward", 90)) + ax4.plot(time_values, gap_values, label="Gap %", color="orange") +# ax4.set_ylabel("Gap.%", color="orange", loc="top", labelpad=22) + ax4.yaxis.label.set_rotation(0) ax4.tick_params(axis="y", labelcolor="orange") - # Determine the interval for labeling Gap% - #interval = max(1, len(time_values) // 10) # Adjust interval for more labels - - # Add Gap% as text labels above the time axis, matching the color with Best Solution line - #for i, (t, gap) in enumerate(zip(time_values, gap_values)): - #if i % interval == 0: # Label at defined intervals - #ax1.text( - #t, - #ax1.get_ylim()[0], - #f"{gap:.2f}%", - #color="green", - #ha="center", - #va="bottom", - #fontsize=10, - #) - - # Add a Gap% label to indicate what the text values represent - #ax1.text( - #0.48, - #0.05, - #"Gap%", - #color="green", - #ha="left", - #va="center", - #transform=ax1.transAxes, - #fontsize=8, - #) - # Plot vertical hash lines where Best Solution changes for i in range(1, len(best_sol_values)): if best_sol_values[i] != best_sol_values[i - 1]: # Change detected ax1.axvline(x=time_values[i], color="grey", linestyle="--", linewidth=0.5) + # Shift plot area left to make room on the right for the three y-axis labels. + fig.subplots_adjust(left=0.08, right=0.85) + # Set up legend - fig.legend(loc="lower left") + fig.legend(loc="lower center", ncols=5) # Show plot - plt.title("HiGHS MIP Log Analysis") + plt.title("HiGHS MIP Log Analysis", pad=20) plt.show() From 4907467fbbcce893f97ccaa2d127c96699470c7e Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sun, 25 Aug 2024 23:02:50 +0100 Subject: [PATCH 081/194] Moved key and using identifiers for colours to ease changes; updated FEATURES.md --- examples/plot_highs_log.py | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/examples/plot_highs_log.py b/examples/plot_highs_log.py index 492f9be895..e3fe0150b0 100644 --- a/examples/plot_highs_log.py +++ b/examples/plot_highs_log.py @@ -62,12 +62,18 @@ def plot_highs_log( ): fig, ax1 = plt.subplots(figsize=(10, 6)) + best_bound_colour = "blue" + best_solution_colour = "green" + in_queue_colour = "red" + explored_colour = "purple" + gap_colour = "orange" + # Plot Objective Bounds - ax1.plot(time_values, best_bound_values, label="Best Bound", color="blue") - ax1.plot(time_values, best_sol_values, label="Best Solution", color="green") + ax1.plot(time_values, best_bound_values, label="Best Bound", color=best_bound_colour) + ax1.plot(time_values, best_sol_values, label="Best Solution", color=best_solution_colour) ax1.set_xlabel("Time (seconds)") - ax1.set_ylabel("Objective Bounds", color="blue", labelpad=15) - ax1.tick_params(axis="y", labelcolor="blue") + ax1.set_ylabel("Objective Bounds", color=best_bound_colour, labelpad=15) + ax1.tick_params(axis="y", labelcolor=best_bound_colour) # Limit y-axis to the range between min and max of the non-NaN values valid_gap_index = next(i for i, gap in enumerate(gap_values) if not np.isnan(gap)) @@ -78,26 +84,26 @@ def plot_highs_log( # Add second y-axis for InQueue values ax2 = ax1.twinx() - ax2.plot(time_values, in_queue_values, label="InQueue", color="red") -# ax2.set_ylabel("InQueue", color="red", loc="top", labelpad=12) + ax2.plot(time_values, in_queue_values, label="InQueue", color=in_queue_colour) +# ax2.set_ylabel("InQueue", color=in_queue_colour, loc="top", labelpad=12) ax2.yaxis.label.set_rotation(0) - ax2.tick_params(axis="y", labelcolor="red") + ax2.tick_params(axis="y", labelcolor=in_queue_colour) # Add third y-axis for Explored % values (scaled) ax3 = ax1.twinx() ax3.spines["right"].set_position(("outward", 50)) - ax3.plot(time_values, expl_values, label="Explored %", color="purple") -# ax3.set_ylabel("Expl.%", color="purple", loc="top", labelpad=10) + ax3.plot(time_values, expl_values, label="Explored %", color=explored_colour) +# ax3.set_ylabel("Expl.%", color=explored_colour, loc="top", labelpad=10) ax3.yaxis.label.set_rotation(0) - ax3.tick_params(axis="y", labelcolor="purple") + ax3.tick_params(axis="y", labelcolor=explored_colour) # Add fourth y-axis for Gap % values (scaled) ax4 = ax1.twinx() ax4.spines["right"].set_position(("outward", 90)) - ax4.plot(time_values, gap_values, label="Gap %", color="orange") -# ax4.set_ylabel("Gap.%", color="orange", loc="top", labelpad=22) + ax4.plot(time_values, gap_values, label="Gap %", color=gap_colour, linestyle="--", linewidth=0.5) +# ax4.set_ylabel("Gap.%", color=gap_colour, loc="top", labelpad=22) ax4.yaxis.label.set_rotation(0) - ax4.tick_params(axis="y", labelcolor="orange") + ax4.tick_params(axis="y", labelcolor=gap_colour) # Plot vertical hash lines where Best Solution changes for i in range(1, len(best_sol_values)): From 00e812dab355ef47114f8cb006335dd044870332 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 26 Aug 2024 18:17:09 +0100 Subject: [PATCH 082/194] Deleted stray prinf in HighsMipSolver.cpp --- src/mip/HighsMipSolver.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/mip/HighsMipSolver.cpp b/src/mip/HighsMipSolver.cpp index b1478a5c24..36ce3d4649 100644 --- a/src/mip/HighsMipSolver.cpp +++ b/src/mip/HighsMipSolver.cpp @@ -176,10 +176,6 @@ void HighsMipSolver::run() { mipdata_->cutpool.performAging(); } if (mipdata_->nodequeue.empty() || mipdata_->checkLimits()) { - if (!submip) - printf( - "HighsMipSolver::run() mipdata_->nodequeue.empty() || " - "mipdata_->checkLimits()\n"); cleanupSolve(); return; } From 032eb5680085cf7e8720ad1db7fb3e1df2ace83e Mon Sep 17 00:00:00 2001 From: fwesselm Date: Tue, 27 Aug 2024 14:06:57 +0200 Subject: [PATCH 083/194] Fix uninitialized arrays --- src/io/FilereaderLp.cpp | 2 +- src/io/HighsIO.cpp | 42 +++++++++++++++++----------------- src/mip/HighsMipSolver.cpp | 2 +- src/mip/HighsMipSolverData.cpp | 29 ++++++++++++++--------- 4 files changed, 41 insertions(+), 34 deletions(-) diff --git a/src/io/FilereaderLp.cpp b/src/io/FilereaderLp.cpp index 4c0382e31c..5de2163359 100644 --- a/src/io/FilereaderLp.cpp +++ b/src/io/FilereaderLp.cpp @@ -216,7 +216,7 @@ FilereaderRetcode FilereaderLp::readModelFromFile(const HighsOptions& options, void FilereaderLp::writeToFile(FILE* file, const char* format, ...) { va_list argptr; va_start(argptr, format); - char stringbuffer[LP_MAX_LINE_LENGTH + 1]; + char stringbuffer[LP_MAX_LINE_LENGTH + 1] = {}; HighsInt tokenlength = vsnprintf(stringbuffer, sizeof stringbuffer, format, argptr); va_end(argptr); diff --git a/src/io/HighsIO.cpp b/src/io/HighsIO.cpp index 8d7911e02f..bc0e0b6941 100644 --- a/src/io/HighsIO.cpp +++ b/src/io/HighsIO.cpp @@ -33,7 +33,7 @@ void highsLogHeader(const HighsLogOptions& log_options, std::array highsDoubleToString(const double val, const double tolerance) { - std::array printString; + std::array printString = {}; double l = std::abs(val) == kHighsInf ? 1.0 @@ -41,55 +41,55 @@ std::array highsDoubleToString(const double val, std::log10(std::max(tolerance, std::abs(val)) / (tolerance))); switch (int(l)) { case 0: - std::snprintf(printString.data(), 32, "%c", '0'); + std::snprintf(printString.data(), printString.size(), "%c", '0'); break; case 1: - std::snprintf(printString.data(), 32, "%.1g", val); + std::snprintf(printString.data(), printString.size(), "%.1g", val); break; case 2: - std::snprintf(printString.data(), 32, "%.2g", val); + std::snprintf(printString.data(), printString.size(), "%.2g", val); break; case 3: - std::snprintf(printString.data(), 32, "%.3g", val); + std::snprintf(printString.data(), printString.size(), "%.3g", val); break; case 4: - std::snprintf(printString.data(), 32, "%.4g", val); + std::snprintf(printString.data(), printString.size(), "%.4g", val); break; case 5: - std::snprintf(printString.data(), 32, "%.5g", val); + std::snprintf(printString.data(), printString.size(), "%.5g", val); break; case 6: - std::snprintf(printString.data(), 32, "%.6g", val); + std::snprintf(printString.data(), printString.size(), "%.6g", val); break; case 7: - std::snprintf(printString.data(), 32, "%.7g", val); + std::snprintf(printString.data(), printString.size(), "%.7g", val); break; case 8: - std::snprintf(printString.data(), 32, "%.8g", val); + std::snprintf(printString.data(), printString.size(), "%.8g", val); break; case 9: - std::snprintf(printString.data(), 32, "%.9g", val); + std::snprintf(printString.data(), printString.size(), "%.9g", val); break; case 10: - std::snprintf(printString.data(), 32, "%.10g", val); + std::snprintf(printString.data(), printString.size(), "%.10g", val); break; case 11: - std::snprintf(printString.data(), 32, "%.11g", val); + std::snprintf(printString.data(), printString.size(), "%.11g", val); break; case 12: - std::snprintf(printString.data(), 32, "%.12g", val); + std::snprintf(printString.data(), printString.size(), "%.12g", val); break; case 13: - std::snprintf(printString.data(), 32, "%.13g", val); + std::snprintf(printString.data(), printString.size(), "%.13g", val); break; case 14: - std::snprintf(printString.data(), 32, "%.14g", val); + std::snprintf(printString.data(), printString.size(), "%.14g", val); break; case 15: - std::snprintf(printString.data(), 32, "%.15g", val); + std::snprintf(printString.data(), printString.size(), "%.15g", val); break; default: - std::snprintf(printString.data(), 32, "%.16g", val); + std::snprintf(printString.data(), printString.size(), "%.16g", val); } return printString; @@ -131,7 +131,7 @@ void highsLogUser(const HighsLogOptions& log_options_, const HighsLogType type, } } else { int len = 0; - char msgbuffer[kIoBufferSize]; + char msgbuffer[kIoBufferSize] = {}; if (prefix) len = snprintf(msgbuffer, sizeof(msgbuffer), "%-9s", HighsLogTypeTag[(int)type]); @@ -199,7 +199,7 @@ void highsLogDev(const HighsLogOptions& log_options_, const HighsLogType type, } } else { int len; - char msgbuffer[kIoBufferSize]; + char msgbuffer[kIoBufferSize] = {}; len = vsnprintf(msgbuffer, sizeof(msgbuffer), format, argptr); if (len >= (int)sizeof(msgbuffer)) { // Output was truncated: for now just ensure string is null-terminated @@ -261,7 +261,7 @@ std::string highsFormatToString(const char* format, ...) { va_list argptr; va_start(argptr, format); int len; - char msgbuffer[kIoBufferSize]; + char msgbuffer[kIoBufferSize] = {}; len = vsnprintf(msgbuffer, sizeof(msgbuffer), format, argptr); if (len >= (int)sizeof(msgbuffer)) { diff --git a/src/mip/HighsMipSolver.cpp b/src/mip/HighsMipSolver.cpp index 36ce3d4649..5471613d17 100644 --- a/src/mip/HighsMipSolver.cpp +++ b/src/mip/HighsMipSolver.cpp @@ -587,7 +587,7 @@ void HighsMipSolver::cleanupSolve() { else gap_ = kHighsInf; - std::array gapString; + std::array gapString = {}; if (gap_ == kHighsInf) std::strcpy(gapString.data(), "inf"); diff --git a/src/mip/HighsMipSolverData.cpp b/src/mip/HighsMipSolverData.cpp index d0b1dfbee6..7da88cc400 100644 --- a/src/mip/HighsMipSolverData.cpp +++ b/src/mip/HighsMipSolverData.cpp @@ -1144,7 +1144,7 @@ bool HighsMipSolverData::addIncumbent(const std::vector& sol, static std::array convertToPrintString(int64_t val) { double l = std::log10(std::max(1.0, double(val))); - std::array printString; + std::array printString = {}; switch (int(l)) { case 0: case 1: @@ -1152,15 +1152,17 @@ static std::array convertToPrintString(int64_t val) { case 3: case 4: case 5: - std::snprintf(printString.data(), 22, "%" PRId64, val); + std::snprintf(printString.data(), printString.size(), "%" PRId64, val); break; case 6: case 7: case 8: - std::snprintf(printString.data(), 22, "%" PRId64 "k", val / 1000); + std::snprintf(printString.data(), printString.size(), "%" PRId64 "k", + val / 1000); break; default: - std::snprintf(printString.data(), 22, "%" PRId64 "m", val / 1000000); + std::snprintf(printString.data(), printString.size(), "%" PRId64 "m", + val / 1000000); } return printString; @@ -1168,7 +1170,7 @@ static std::array convertToPrintString(int64_t val) { static std::array convertToPrintString(double val, const char* trailingStr = "") { - std::array printString; + std::array printString = {}; double l = std::abs(val) == kHighsInf ? 0.0 : std::log10(std::max(1e-6, std::abs(val))); @@ -1177,23 +1179,28 @@ static std::array convertToPrintString(double val, case 1: case 2: case 3: - std::snprintf(printString.data(), 22, "%.10g%s", val, trailingStr); + std::snprintf(printString.data(), printString.size(), "%.10g%s", val, + trailingStr); break; case 4: - std::snprintf(printString.data(), 22, "%.11g%s", val, trailingStr); + std::snprintf(printString.data(), printString.size(), "%.11g%s", val, + trailingStr); break; case 5: - std::snprintf(printString.data(), 22, "%.12g%s", val, trailingStr); + std::snprintf(printString.data(), printString.size(), "%.12g%s", val, + trailingStr); break; case 6: case 7: case 8: case 9: case 10: - std::snprintf(printString.data(), 22, "%.13g%s", val, trailingStr); + std::snprintf(printString.data(), printString.size(), "%.13g%s", val, + trailingStr); break; default: - std::snprintf(printString.data(), 22, "%.9g%s", val, trailingStr); + std::snprintf(printString.data(), printString.size(), "%.9g%s", val, + trailingStr); } return printString; @@ -1263,7 +1270,7 @@ void HighsMipSolverData::printDisplayLine(char source) { else gap = 100. * (ub - lb) / fabs(ub); - std::array gap_string; + std::array gap_string = {}; if (gap >= 9999.) std::strcpy(gap_string.data(), "Large"); else From c096294a27dff29b2518db4aaf9107b2127f23b9 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Tue, 27 Aug 2024 17:09:33 +0200 Subject: [PATCH 084/194] Use decltype and auto some more --- src/io/HighsIO.cpp | 3 ++- src/lp_data/HighsModelUtils.cpp | 41 ++++++++++++++++----------------- src/lp_data/HighsRanging.cpp | 4 ++-- src/mip/HighsMipSolver.cpp | 6 ++--- src/mip/HighsMipSolverData.cpp | 20 ++++++++-------- 5 files changed, 35 insertions(+), 39 deletions(-) diff --git a/src/io/HighsIO.cpp b/src/io/HighsIO.cpp index bc0e0b6941..b32b5758a5 100644 --- a/src/io/HighsIO.cpp +++ b/src/io/HighsIO.cpp @@ -33,7 +33,8 @@ void highsLogHeader(const HighsLogOptions& log_options, std::array highsDoubleToString(const double val, const double tolerance) { - std::array printString = {}; + decltype(highsDoubleToString(std::declval(), + std::declval())) printString = {}; double l = std::abs(val) == kHighsInf ? 1.0 diff --git a/src/lp_data/HighsModelUtils.cpp b/src/lp_data/HighsModelUtils.cpp index 0e4db30085..ea2944a21c 100644 --- a/src/lp_data/HighsModelUtils.cpp +++ b/src/lp_data/HighsModelUtils.cpp @@ -203,8 +203,8 @@ void writeLpObjective(FILE* file, const HighsLogOptions& log_options, void writeObjectiveValue(FILE* file, const HighsLogOptions& log_options, const double objective_value) { - std::array objStr = highsDoubleToString( - objective_value, kHighsSolutionValueToStringTolerance); + auto objStr = highsDoubleToString(objective_value, + kHighsSolutionValueToStringTolerance); std::string s = highsFormatToString("Objective %s\n", objStr.data()); highsFprintfString(file, log_options, s); } @@ -231,8 +231,8 @@ void writePrimalSolution(FILE* file, const HighsLogOptions& log_options, highsFprintfString(file, log_options, ss.str()); for (HighsInt ix = 0; ix < lp.num_col_; ix++) { if (sparse && !primal_solution[ix]) continue; - std::array valStr = highsDoubleToString( - primal_solution[ix], kHighsSolutionValueToStringTolerance); + auto valStr = highsDoubleToString(primal_solution[ix], + kHighsSolutionValueToStringTolerance); // Create a column name ss.str(std::string()); ss << "C" << ix; @@ -284,8 +284,8 @@ void writeModelSolution(FILE* file, const HighsLogOptions& log_options, ss << highsFormatToString("# Rows %" HIGHSINT_FORMAT "\n", lp.num_row_); highsFprintfString(file, log_options, ss.str()); for (HighsInt ix = 0; ix < lp.num_row_; ix++) { - std::array valStr = highsDoubleToString( - solution.row_value[ix], kHighsSolutionValueToStringTolerance); + auto valStr = highsDoubleToString(solution.row_value[ix], + kHighsSolutionValueToStringTolerance); // Create a row name ss.str(std::string()); ss << "R" << ix; @@ -309,8 +309,8 @@ void writeModelSolution(FILE* file, const HighsLogOptions& log_options, ss << highsFormatToString("# Columns %" HIGHSINT_FORMAT "\n", lp.num_col_); highsFprintfString(file, log_options, ss.str()); for (HighsInt ix = 0; ix < lp.num_col_; ix++) { - std::array valStr = highsDoubleToString( - solution.col_dual[ix], kHighsSolutionValueToStringTolerance); + auto valStr = highsDoubleToString(solution.col_dual[ix], + kHighsSolutionValueToStringTolerance); ss.str(std::string()); ss << "C" << ix; const std::string name = have_col_names ? lp.col_names_[ix] : ss.str(); @@ -322,8 +322,8 @@ void writeModelSolution(FILE* file, const HighsLogOptions& log_options, ss << highsFormatToString("# Rows %" HIGHSINT_FORMAT "\n", lp.num_row_); highsFprintfString(file, log_options, ss.str()); for (HighsInt ix = 0; ix < lp.num_row_; ix++) { - std::array valStr = highsDoubleToString( - solution.row_dual[ix], kHighsSolutionValueToStringTolerance); + auto valStr = highsDoubleToString(solution.row_dual[ix], + kHighsSolutionValueToStringTolerance); ss.str(std::string()); ss << "R" << ix; const std::string name = have_row_names ? lp.row_names_[ix] : ss.str(); @@ -435,9 +435,8 @@ void writeSolutionFile(FILE* file, const HighsOptions& options, ss << highsFormatToString("Model status: %s\n", utilModelStatusToString(model_status).c_str()); highsFprintfString(file, log_options, ss.str()); - std::array objStr = - highsDoubleToString((double)info.objective_function_value, - kHighsSolutionValueToStringTolerance); + auto objStr = highsDoubleToString((double)info.objective_function_value, + kHighsSolutionValueToStringTolerance); highsFprintfString(file, log_options, "\n"); ss.str(std::string()); ss << highsFormatToString("Objective value: %s\n", objStr.data()); @@ -469,7 +468,7 @@ void writeGlpsolCostRow(FILE* file, const HighsLogOptions& log_options, ss.str(std::string()); if (raw) { double double_value = objective_function_value; - std::array double_string = highsDoubleToString( + auto double_string = highsDoubleToString( double_value, kGlpsolSolutionValueToStringTolerance); // Last term of 0 for dual should (also) be blank when not MIP ss << highsFormatToString("i %d %s%s%s\n", (int)row_id, is_mip ? "" : "b ", @@ -754,7 +753,7 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, } } double double_value = has_objective ? info.objective_function_value : 0; - std::array double_string = + auto double_string = highsDoubleToString(double_value, kHighsSolutionValueToStringTolerance); ss << highsFormatToString(" %s\n", double_string.data()); highsFprintfString(file, log_options, ss.str()); @@ -795,7 +794,7 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, if (is_mip) { // Complete the line if for a MIP double double_value = have_value ? solution.row_value[iRow] : 0; - std::array double_string = highsDoubleToString( + auto double_string = highsDoubleToString( double_value, kHighsSolutionValueToStringTolerance); ss << highsFormatToString("%s\n", double_string.data()); highsFprintfString(file, log_options, ss.str()); @@ -847,7 +846,7 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, if (raw) { ss << highsFormatToString("%s ", status_char.c_str()); double double_value = have_value ? solution.row_value[iRow] : 0; - std::array double_string = highsDoubleToString( + auto double_string = highsDoubleToString( double_value, kHighsSolutionValueToStringTolerance); ss << highsFormatToString("%s ", double_string.data()); } else { @@ -866,7 +865,7 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, if (have_dual) { if (raw) { double double_value = solution.row_dual[iRow]; - std::array double_string = highsDoubleToString( + auto double_string = highsDoubleToString( double_value, kHighsSolutionValueToStringTolerance); ss << highsFormatToString("%s", double_string.data()); } else { @@ -920,7 +919,7 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, ss << highsFormatToString("%s%d ", line_prefix.c_str(), (int)(iCol + 1)); if (is_mip) { double double_value = have_value ? solution.col_value[iCol] : 0; - std::array double_string = highsDoubleToString( + auto double_string = highsDoubleToString( double_value, kHighsSolutionValueToStringTolerance); ss << highsFormatToString("%s\n", double_string.data()); highsFprintfString(file, log_options, ss.str()); @@ -976,7 +975,7 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, if (raw) { ss << highsFormatToString("%s ", status_char.c_str()); double double_value = have_value ? solution.col_value[iCol] : 0; - std::array double_string = highsDoubleToString( + auto double_string = highsDoubleToString( double_value, kHighsSolutionValueToStringTolerance); ss << highsFormatToString("%s ", double_string.data()); } else { @@ -995,7 +994,7 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, if (have_dual) { if (raw) { double double_value = solution.col_dual[iCol]; - std::array double_string = highsDoubleToString( + auto double_string = highsDoubleToString( double_value, kHighsSolutionValueToStringTolerance); ss << highsFormatToString("%s", double_string.data()); } else { diff --git a/src/lp_data/HighsRanging.cpp b/src/lp_data/HighsRanging.cpp index a84ec98cd5..901f3f39c5 100644 --- a/src/lp_data/HighsRanging.cpp +++ b/src/lp_data/HighsRanging.cpp @@ -631,8 +631,8 @@ void writeRangingFile(FILE* file, const HighsLp& lp, std::array dn_value; std::array up_value; - std::array objective = highsDoubleToString( - objective_function_value, kRangingValueToStringTolerance); + auto objective = highsDoubleToString(objective_function_value, + kRangingValueToStringTolerance); fprintf(file, "Objective %s\n", objective.data()); if (pretty) { fprintf(file, diff --git a/src/mip/HighsMipSolver.cpp b/src/mip/HighsMipSolver.cpp index 5471613d17..1cf6e34c51 100644 --- a/src/mip/HighsMipSolver.cpp +++ b/src/mip/HighsMipSolver.cpp @@ -593,8 +593,7 @@ void HighsMipSolver::cleanupSolve() { std::strcpy(gapString.data(), "inf"); else { double printTol = std::max(std::min(1e-2, 1e-1 * gap_), 1e-6); - std::array gapValString = - highsDoubleToString(100.0 * gap_, printTol); + auto gapValString = highsDoubleToString(100.0 * gap_, printTol); double gapTol = options_mip_->mip_rel_gap; if (options_mip_->mip_abs_gap > options_mip_->mip_feasibility_tolerance) { @@ -609,8 +608,7 @@ void HighsMipSolver::cleanupSolve() { gapValString.data()); else if (gapTol != kHighsInf) { printTol = std::max(std::min(1e-2, 1e-1 * gapTol), 1e-6); - std::array gapTolString = - highsDoubleToString(100.0 * gapTol, printTol); + auto gapTolString = highsDoubleToString(100.0 * gapTol, printTol); std::snprintf(gapString.data(), gapString.size(), "%s%% (tolerance: %s%%)", gapValString.data(), gapTolString.data()); diff --git a/src/mip/HighsMipSolverData.cpp b/src/mip/HighsMipSolverData.cpp index 7da88cc400..181b6adb1e 100644 --- a/src/mip/HighsMipSolverData.cpp +++ b/src/mip/HighsMipSolverData.cpp @@ -1143,8 +1143,8 @@ bool HighsMipSolverData::addIncumbent(const std::vector& sol, } static std::array convertToPrintString(int64_t val) { + decltype(convertToPrintString(std::declval())) printString = {}; double l = std::log10(std::max(1.0, double(val))); - std::array printString = {}; switch (int(l)) { case 0: case 1: @@ -1170,7 +1170,8 @@ static std::array convertToPrintString(int64_t val) { static std::array convertToPrintString(double val, const char* trailingStr = "") { - std::array printString = {}; + decltype(convertToPrintString(std::declval(), + std::declval())) printString = {}; double l = std::abs(val) == kHighsInf ? 0.0 : std::log10(std::max(1e-6, std::abs(val))); @@ -1244,11 +1245,9 @@ void HighsMipSolverData::printDisplayLine(char source) { ++num_disp_lines; - std::array print_nodes = convertToPrintString(num_nodes); - std::array queue_nodes = - convertToPrintString(nodequeue.numActiveNodes()); - std::array print_leaves = - convertToPrintString(num_leaves - num_leaves_before_run); + auto print_nodes = convertToPrintString(num_nodes); + auto queue_nodes = convertToPrintString(nodequeue.numActiveNodes()); + auto print_leaves = convertToPrintString(num_leaves - num_leaves_before_run); double explored = 100 * double(pruned_treeweight); @@ -1258,8 +1257,7 @@ void HighsMipSolverData::printDisplayLine(char source) { double ub = kHighsInf; double gap = kHighsInf; - std::array print_lp_iters = - convertToPrintString(total_lp_iterations); + auto print_lp_iters = convertToPrintString(total_lp_iterations); if (upper_bound != kHighsInf) { ub = upper_bound + offset; @@ -1284,7 +1282,7 @@ void HighsMipSolverData::printDisplayLine(char source) { } else ub_string = convertToPrintString((int)mipsolver.orig_model_->sense_ * ub); - std::array lb_string = + auto lb_string = convertToPrintString((int)mipsolver.orig_model_->sense_ * lb); highsLogUser( @@ -1305,7 +1303,7 @@ void HighsMipSolverData::printDisplayLine(char source) { } else ub_string = convertToPrintString((int)mipsolver.orig_model_->sense_ * ub); - std::array lb_string = + auto lb_string = convertToPrintString((int)mipsolver.orig_model_->sense_ * lb); highsLogUser( From 754c204b4fe24ceb7feda8b5103fcf1b182bf07f Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 28 Aug 2024 12:43:53 +0100 Subject: [PATCH 085/194] plot_highs_log.py now avoids printing spurious incumbent lines when best solution is NaN --- examples/plot_highs_log.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/plot_highs_log.py b/examples/plot_highs_log.py index e3fe0150b0..6163c96039 100644 --- a/examples/plot_highs_log.py +++ b/examples/plot_highs_log.py @@ -1,6 +1,7 @@ import re import matplotlib.pyplot as plt import numpy as np +import math as math def parse_highs_log(log_file_path): @@ -107,7 +108,8 @@ def plot_highs_log( # Plot vertical hash lines where Best Solution changes for i in range(1, len(best_sol_values)): - if best_sol_values[i] != best_sol_values[i - 1]: # Change detected + # Print if change detected and not NaN + if (best_sol_values[i] != best_sol_values[i - 1]) and not(math.isnan(best_sol_values[i])): ax1.axvline(x=time_values[i], color="grey", linestyle="--", linewidth=0.5) # Shift plot area left to make room on the right for the three y-axis labels. From fb8998673c4958bd05f3ef6cda4221098f9d56dd Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 28 Aug 2024 16:19:42 +0200 Subject: [PATCH 086/194] use std::array instead of C arrays if possible --- src/io/HMPSIO.cpp | 32 +++++++++++++++++++------------- src/io/HighsIO.cpp | 32 ++++++++++++++++++++------------ 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/src/io/HMPSIO.cpp b/src/io/HMPSIO.cpp index b6037d3624..014f5716b1 100644 --- a/src/io/HMPSIO.cpp +++ b/src/io/HMPSIO.cpp @@ -78,21 +78,21 @@ FilereaderRetcode readMps( highsLogDev(log_options, HighsLogType::kInfo, "readMPS: Opened file OK\n"); // Input buffer const HighsInt lmax = 128; - char line[lmax]; - char flag[2] = {0, 0}; - double data[3]; + std::array line; + std::array flag = {0, 0}; + std::array data; HighsInt num_alien_entries = 0; HighsVarType integerCol = HighsVarType::kContinuous; // Load NAME - load_mpsLine(file, integerCol, lmax, line, flag, data); + load_mpsLine(file, integerCol, lmax, line.data(), flag.data(), data.data()); highsLogDev(log_options, HighsLogType::kInfo, "readMPS: Read NAME OK\n"); // Load OBJSENSE or ROWS - load_mpsLine(file, integerCol, lmax, line, flag, data); + load_mpsLine(file, integerCol, lmax, line.data(), flag.data(), data.data()); if (flag[0] == 'O') { // Found OBJSENSE - load_mpsLine(file, integerCol, lmax, line, flag, data); + load_mpsLine(file, integerCol, lmax, line.data(), flag.data(), data.data()); std::string sense(&line[2], &line[2] + 3); // the sense must be "MAX" or "MIN" if (sense.compare("MAX") == 0) { @@ -105,7 +105,7 @@ FilereaderRetcode readMps( highsLogDev(log_options, HighsLogType::kInfo, "readMPS: Read OBJSENSE OK\n"); // Load ROWS - load_mpsLine(file, integerCol, lmax, line, flag, data); + load_mpsLine(file, integerCol, lmax, line.data(), flag.data(), data.data()); } row_names.clear(); @@ -113,7 +113,8 @@ FilereaderRetcode readMps( vector rowType; map rowIndex; double objName = 0; - while (load_mpsLine(file, integerCol, lmax, line, flag, data)) { + while (load_mpsLine(file, integerCol, lmax, line.data(), flag.data(), + data.data())) { if (flag[0] == 'N' && (objName == 0 || keep_n_rows == kKeepNRowsDeleteRows)) { // N-row: take the first as the objective and possibly ignore any others @@ -149,7 +150,8 @@ FilereaderRetcode readMps( // line - field 5 non-empty. save_flag1 is used to deduce whether // the row name and value are from fields 5 and 6, or 3 and 4 HighsInt save_flag1 = 0; - while (load_mpsLine(file, integerCol, lmax, line, flag, data)) { + while (load_mpsLine(file, integerCol, lmax, line.data(), flag.data(), + data.data())) { HighsInt iRow = rowIndex[data[2]] - 1; std::string name = ""; if (iRow >= 0) name = row_names[iRow]; @@ -218,7 +220,8 @@ FilereaderRetcode readMps( num_alien_entries = 0; vector RHS(numRow, 0); save_flag1 = 0; - while (load_mpsLine(file, integerCol, lmax, line, flag, data)) { + while (load_mpsLine(file, integerCol, lmax, line.data(), flag.data(), + data.data())) { if (data[2] != objName) { HighsInt iRow = rowIndex[data[2]] - 1; if (iRow >= 0) { @@ -268,7 +271,8 @@ FilereaderRetcode readMps( rowUpper.resize(numRow); if (flag[0] == 'R') { save_flag1 = 0; - while (load_mpsLine(file, integerCol, lmax, line, flag, data)) { + while (load_mpsLine(file, integerCol, lmax, line.data(), flag.data(), + data.data())) { HighsInt iRow = rowIndex[data[2]] - 1; if (iRow >= 0) { if (rowType[iRow] == 'L' || (rowType[iRow] == 'E' && data[0] < 0)) { @@ -338,7 +342,8 @@ FilereaderRetcode readMps( colLower.assign(numCol, 0); colUpper.assign(numCol, kHighsInf); if (flag[0] == 'B') { - while (load_mpsLine(file, integerCol, lmax, line, flag, data)) { + while (load_mpsLine(file, integerCol, lmax, line.data(), flag.data(), + data.data())) { // Find the column index associated with the name "data[2]". If // the name is in colIndex then the value stored is the true // column index plus one. Otherwise 0 will be returned. @@ -389,7 +394,8 @@ FilereaderRetcode readMps( HighsInt previous_col = -1; bool has_diagonal = false; Qstart.clear(); - while (load_mpsLine(file, integerCol, lmax, line, flag, data)) { + while (load_mpsLine(file, integerCol, lmax, line.data(), flag.data(), + data.data())) { HighsInt iCol0 = colIndex[data[1]] - 1; std::string name0 = ""; if (iCol0 >= 0) name0 = col_names[iCol0]; diff --git a/src/io/HighsIO.cpp b/src/io/HighsIO.cpp index b32b5758a5..22763a09aa 100644 --- a/src/io/HighsIO.cpp +++ b/src/io/HighsIO.cpp @@ -131,27 +131,35 @@ void highsLogUser(const HighsLogOptions& log_options_, const HighsLogType type, if (flush_streams) fflush(stdout); } } else { - int len = 0; - char msgbuffer[kIoBufferSize] = {}; - if (prefix) - len = snprintf(msgbuffer, sizeof(msgbuffer), "%-9s", - HighsLogTypeTag[(int)type]); - if (len < (int)sizeof(msgbuffer)) - len += - vsnprintf(msgbuffer + len, sizeof(msgbuffer) - len, format, argptr); - if (len >= (int)sizeof(msgbuffer)) { + size_t len = 0; + std::array msgbuffer = {}; + if (prefix) { + int l = snprintf(msgbuffer.data(), msgbuffer.size(), "%-9s", + HighsLogTypeTag[(int)type]); + // assert that there are no encoding errors + assert(l >= 0); + len += static_cast(l); + } + if (len < msgbuffer.size()) { + int l = vsnprintf(msgbuffer.data() + len, msgbuffer.size() - len, format, + argptr); + // assert that there are no encoding errors + assert(l >= 0); + len += static_cast(l); + } + if (len >= msgbuffer.size()) { // Output was truncated: for now just ensure string is null-terminated - msgbuffer[sizeof(msgbuffer) - 1] = '\0'; + msgbuffer[msgbuffer.size() - 1] = '\0'; } if (log_options_.user_log_callback) { - log_options_.user_log_callback(type, msgbuffer, + log_options_.user_log_callback(type, msgbuffer.data(), log_options_.user_log_callback_data); } if (log_options_.user_callback_active) { assert(log_options_.user_callback); HighsCallbackDataOut data_out; data_out.log_type = int(type); - log_options_.user_callback(kCallbackLogging, msgbuffer, &data_out, + log_options_.user_callback(kCallbackLogging, msgbuffer.data(), &data_out, nullptr, log_options_.user_callback_data); } } From b1862c8900c697d575a46b7e9d3b531737fd7130 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 28 Aug 2024 16:34:43 +0200 Subject: [PATCH 087/194] Chaser --- src/io/HighsIO.cpp | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/io/HighsIO.cpp b/src/io/HighsIO.cpp index 22763a09aa..936b2db5c0 100644 --- a/src/io/HighsIO.cpp +++ b/src/io/HighsIO.cpp @@ -207,21 +207,22 @@ void highsLogDev(const HighsLogOptions& log_options_, const HighsLogType type, if (flush_streams) fflush(stdout); } } else { - int len; - char msgbuffer[kIoBufferSize] = {}; - len = vsnprintf(msgbuffer, sizeof(msgbuffer), format, argptr); - if (len >= (int)sizeof(msgbuffer)) { + std::array msgbuffer = {}; + int len = vsnprintf(msgbuffer.data(), msgbuffer.size(), format, argptr); + // assert that there are no encoding errors + assert(len >= 0); + if (static_cast(len) >= msgbuffer.size()) { // Output was truncated: for now just ensure string is null-terminated - msgbuffer[sizeof(msgbuffer) - 1] = '\0'; + msgbuffer[msgbuffer.size() - 1] = '\0'; } if (log_options_.user_log_callback) { - log_options_.user_log_callback(type, msgbuffer, + log_options_.user_log_callback(type, msgbuffer.data(), log_options_.user_log_callback_data); } else if (log_options_.user_callback_active) { assert(log_options_.user_callback); HighsCallbackDataOut data_out; data_out.log_type = int(type); - log_options_.user_callback(kCallbackLogging, msgbuffer, &data_out, + log_options_.user_callback(kCallbackLogging, msgbuffer.data(), &data_out, nullptr, log_options_.user_callback_data); } } @@ -269,16 +270,17 @@ void highsReportLogOptions(const HighsLogOptions& log_options_) { std::string highsFormatToString(const char* format, ...) { va_list argptr; va_start(argptr, format); - int len; - char msgbuffer[kIoBufferSize] = {}; - len = vsnprintf(msgbuffer, sizeof(msgbuffer), format, argptr); + std::array msgbuffer = {}; + int len = vsnprintf(msgbuffer.data(), msgbuffer.size(), format, argptr); + // assert that there are no encoding errors + assert(len >= 0); - if (len >= (int)sizeof(msgbuffer)) { + if (static_cast(len) >= msgbuffer.size()) { // Output was truncated: for now just ensure string is null-terminated - msgbuffer[sizeof(msgbuffer) - 1] = '\0'; + msgbuffer[msgbuffer.size() - 1] = '\0'; } va_end(argptr); - return std::string(msgbuffer); + return std::string(msgbuffer.data()); } const std::string highsBoolToString(const bool b, const HighsInt field_width) { From 74682bc69161681ca0a8f2acd7c7d7f41d933474 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 28 Aug 2024 16:06:23 +0100 Subject: [PATCH 088/194] Added public enum HessianFormat, public class HighsHessian, private static extern int Highs_passHessian, private static extern int Highs_passModel and private static extern int Highs_getHessianNumNz to highs_csharp_api.cs --- src/interfaces/highs_csharp_api.cs | 64 ++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/interfaces/highs_csharp_api.cs b/src/interfaces/highs_csharp_api.cs index dadb15caac..8aec04aa55 100644 --- a/src/interfaces/highs_csharp_api.cs +++ b/src/interfaces/highs_csharp_api.cs @@ -19,6 +19,12 @@ public enum HighsMatrixFormat kRowwise } +public enum HessianFormat +{ + kTriangular = 1, + kSquare +} + public enum HighsBasisStatus { kLower = 0, @@ -104,6 +110,27 @@ public HighsModel(double[] colcost, double[] collower, double[] colupper, double } } +public class HighsHessian +{ + public HessianFormat q_format; + public int[] qstart; + public int[] qindex; + public double[] qvalue; + + public HighsHessian() + { + + } + + public HighsHessian(int[] qstart, int[] qindex, double[] qvalue, HessianFormat q_format = HessianFormat.kTriangular) + { + this.qstart = qstart; + this.qindex = qindex; + this.qvalue = qvalue; + this.q_format = q_format; + } +} + public class HighsSolution { public double[] colvalue; @@ -236,6 +263,40 @@ private static extern int Highs_passMip( double[] avalue, int[] highs_integrality); + [DllImport(highslibname)] + private static extern int Highs_passModel( + IntPtr highs, + int numcol, + int numrow, + int numnz, + int qnumnz, + int aformat, + int qformat, + int sense, + double offset, + double[] colcost, + double[] collower, + double[] colupper, + double[] rowlower, + double[] rowupper, + int[] astart, + int[] aindex, + double[] avalue, + int[] qstart, + int[] qindex, + double[] qvalue, + int[] highs_integrality); + + [DllImport(highslibname)] + private static extern int Highs_passHessian( + IntPtr highs, + int dim, + int numnz, + int q_format, + int[] qstart, + int[] qindex, + double[] qvalue); + [DllImport(highslibname)] private static extern int Highs_setOptionValue(IntPtr highs, string option, string value); @@ -275,6 +336,9 @@ private static extern int Highs_passMip( [DllImport(highslibname)] private static extern int Highs_getNumNz(IntPtr highs); + [DllImport(highslibname)] + private static extern int Highs_getHessianNumNz(IntPtr highs); + [DllImport(highslibname)] private static extern int Highs_getBasis(IntPtr highs, int[] colstatus, int[] rowstatus); From af09d2d5b9927445830bb61bdb06255e282a50f1 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 28 Aug 2024 16:23:03 +0100 Subject: [PATCH 089/194] Added dim to HessianFormat and started adding Hessian to call_highs_from_csharp.cs --- examples/call_highs_from_csharp.cs | 11 ++++++++++- src/interfaces/highs_csharp_api.cs | 16 +++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/examples/call_highs_from_csharp.cs b/examples/call_highs_from_csharp.cs index 8fca3a77ca..137aa4c735 100644 --- a/examples/call_highs_from_csharp.cs +++ b/examples/call_highs_from_csharp.cs @@ -6,6 +6,7 @@ class Program { static void Main(string[] args) { + // Test with an LP double[] cc = {1, -2}; double[] cl = {0, 0}; double[] cu = {10, 10}; @@ -43,5 +44,13 @@ static void Main(string[] args) { for (int i=0; i Date: Wed, 28 Aug 2024 16:59:36 +0100 Subject: [PATCH 090/194] Make the LP in call_highs_from_csharp.cs feasible! --- examples/call_highs_from_csharp.cs | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/examples/call_highs_from_csharp.cs b/examples/call_highs_from_csharp.cs index 137aa4c735..36f72e6b83 100644 --- a/examples/call_highs_from_csharp.cs +++ b/examples/call_highs_from_csharp.cs @@ -6,18 +6,22 @@ class Program { static void Main(string[] args) { - // Test with an LP - double[] cc = {1, -2}; - double[] cl = {0, 0}; - double[] cu = {10, 10}; - double[] rl = {0, 0}; - double[] ru = {2, 1}; - int[] astart = {0, 2}; - int[] aindex = {0, 1, 0, 1}; - double[] avalue = {1, 2, 1, 3}; + // Illustrate the solution of a QP, after first solving just the LP + // + // minimize x_2 + (1/2)(2x_1^2 - 2x_1x_3 + 0.2x_2^2 + 2x_3^2) + // + // subject to x_1 + x_2 + x_3 >= 1; x>=0 + double[] cc = {0, 1, 0}; + double[] cl = {0, 0, 0}; + double[] cu = {1.0e30, 1.0e30, 1.0e30}; + double[] rl = {1}; + double[] ru = {1.0e30}; + int[] astart = {0, 3}; + int[] aindex = {0, 1, 2}; + double[] avalue = {1, 1, 1}; HighsObjectiveSense sense = HighsObjectiveSense.kMinimize; double offset = 0; - HighsMatrixFormat a_format = HighsMatrixFormat.kColwise; + HighsMatrixFormat a_format = HighsMatrixFormat.kRowwise; HighsModel model = new HighsModel(cc, cl, cu, rl, ru, astart, aindex, avalue, null, offset, a_format, sense); @@ -44,9 +48,9 @@ static void Main(string[] args) { for (int i=0; i Date: Wed, 28 Aug 2024 17:04:29 +0100 Subject: [PATCH 091/194] Completed call_highs_from_csharp.cs by passing a Hessian and resolving --- examples/call_highs_from_csharp.cs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/examples/call_highs_from_csharp.cs b/examples/call_highs_from_csharp.cs index 36f72e6b83..0add20312a 100644 --- a/examples/call_highs_from_csharp.cs +++ b/examples/call_highs_from_csharp.cs @@ -36,6 +36,9 @@ static void Main(string[] args) { Console.WriteLine("Status: " + status); Console.WriteLine("Modelstatus: " + modelStatus); + for (int i=0; i feastol) { // printf("added implied bound cut to pool\n"); - cutpool.addCut(mipsolver, inds, vals, 2, rhs, + cutpool.addCut(mipsolver, inds.data(), vals.data(), 2, rhs, mipsolver.variableType(implics[i].column) != HighsVarType::kContinuous, false, false, false); diff --git a/src/mip/HighsPathSeparator.cpp b/src/mip/HighsPathSeparator.cpp index 83ab962a3d..0989bd098c 100644 --- a/src/mip/HighsPathSeparator.cpp +++ b/src/mip/HighsPathSeparator.cpp @@ -174,7 +174,7 @@ void HighsPathSeparator::separateLpSolution(HighsLpRelaxation& lpRelaxation, HighsInt currentPath[maxPathLen]; std::vector, std::vector>> aggregatedPath; - double scales[2]; + std::array scales; for (HighsInt i = 0; i != lp.num_row_; ++i) { switch (rowtype[i]) { case RowType::kUnusuable: diff --git a/src/util/HighsHashTree.h b/src/util/HighsHashTree.h index c42bc271e9..b33e470e43 100644 --- a/src/util/HighsHashTree.h +++ b/src/util/HighsHashTree.h @@ -113,8 +113,8 @@ class HighsHashTree { // to do a linear scan and key comparisons at all Occupation occupation; int size; - uint64_t hashes[capacity() + 1]; - Entry entries[capacity()]; + std::array hashes; + std::array entries; InnerLeaf() : occupation(0), size(0) { hashes[0] = 0; } From 455c007defe951b6b0d39aa122ffc4e552c94993 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Thu, 29 Aug 2024 10:37:22 +0200 Subject: [PATCH 093/194] Use std::next a little bit --- src/util/HighsHashTree.h | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/util/HighsHashTree.h b/src/util/HighsHashTree.h index b33e470e43..46557b8119 100644 --- a/src/util/HighsHashTree.h +++ b/src/util/HighsHashTree.h @@ -123,7 +123,8 @@ class HighsHashTree { assert(other.size <= capacity()); memcpy((void*)this, (void*)&other, (char*)&other.hashes[other.size + 1] - (char*)&other); - std::move(&other.entries[0], &other.entries[size], &entries[0]); + std::move(other.entries.begin(), std::next(other.entries.begin(), size), + entries.begin()); } int get_num_entries() const { return size; } @@ -190,7 +191,9 @@ class HighsHashTree { --size; if (pos < size) { - std::move(&entries[pos + 1], &entries[size + 1], &entries[pos]); + std::move(std::next(entries.begin(), pos + 1), + std::next(entries.begin(), size + 1), + std::next(entries.begin(), pos)); memmove(&hashes[pos], &hashes[pos + 1], sizeof(hashes[0]) * (size - pos)); if (get_first_chunk16(hashes[startPos]) != hashChunk) @@ -254,7 +257,9 @@ class HighsHashTree { void move_backward(const int& first, const int& last) { // move elements backwards - std::move_backward(&entries[first], &entries[last], &entries[last + 1]); + std::move_backward(std::next(entries.begin(), first), + std::next(entries.begin(), last), + std::next(entries.begin(), last + 1)); memmove(&hashes[first + 1], &hashes[first], sizeof(hashes[0]) * (last - first)); } From d69e295d35aa595c53bce4dd4814cae541ecfb39 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Fri, 30 Aug 2024 16:44:32 +0200 Subject: [PATCH 094/194] Make hashes an std::array --- src/util/HighsHashTree.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/util/HighsHashTree.h b/src/util/HighsHashTree.h index c42bc271e9..026f26195b 100644 --- a/src/util/HighsHashTree.h +++ b/src/util/HighsHashTree.h @@ -113,7 +113,7 @@ class HighsHashTree { // to do a linear scan and key comparisons at all Occupation occupation; int size; - uint64_t hashes[capacity() + 1]; + std::array hashes; Entry entries[capacity()]; InnerLeaf() : occupation(0), size(0) { hashes[0] = 0; } @@ -121,8 +121,10 @@ class HighsHashTree { template InnerLeaf(InnerLeaf&& other) { assert(other.size <= capacity()); - memcpy((void*)this, (void*)&other, - (char*)&other.hashes[other.size + 1] - (char*)&other); + occupation = other.occupation; + size = other.size; + std::copy(other.hashes.cbegin(), + std::next(other.hashes.cbegin(), size + 1), hashes.begin()); std::move(&other.entries[0], &other.entries[size], &entries[0]); } From 584069fb19f2f08050d038d49fea13b2a8274084 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Mon, 2 Sep 2024 08:45:45 +0200 Subject: [PATCH 095/194] Make entries an std::array --- src/util/HighsHashTree.h | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/util/HighsHashTree.h b/src/util/HighsHashTree.h index 026f26195b..c06bb96758 100644 --- a/src/util/HighsHashTree.h +++ b/src/util/HighsHashTree.h @@ -114,7 +114,7 @@ class HighsHashTree { Occupation occupation; int size; std::array hashes; - Entry entries[capacity()]; + std::array entries; InnerLeaf() : occupation(0), size(0) { hashes[0] = 0; } @@ -125,7 +125,8 @@ class HighsHashTree { size = other.size; std::copy(other.hashes.cbegin(), std::next(other.hashes.cbegin(), size + 1), hashes.begin()); - std::move(&other.entries[0], &other.entries[size], &entries[0]); + std::move(other.entries.begin(), std::next(other.entries.begin(), size), + entries.begin()); } int get_num_entries() const { return size; } @@ -192,7 +193,9 @@ class HighsHashTree { --size; if (pos < size) { - std::move(&entries[pos + 1], &entries[size + 1], &entries[pos]); + std::move(std::next(entries.begin(), pos + 1), + std::next(entries.begin(), size + 1), + std::next(entries.begin(), pos)); memmove(&hashes[pos], &hashes[pos + 1], sizeof(hashes[0]) * (size - pos)); if (get_first_chunk16(hashes[startPos]) != hashChunk) @@ -256,7 +259,9 @@ class HighsHashTree { void move_backward(const int& first, const int& last) { // move elements backwards - std::move_backward(&entries[first], &entries[last], &entries[last + 1]); + std::move_backward(std::next(entries.begin(), first), + std::next(entries.begin(), last), + std::next(entries.begin(), last + 1)); memmove(&hashes[first + 1], &hashes[first], sizeof(hashes[0]) * (last - first)); } From 6e32cb553e70a7bb05083585eef50602aff164c5 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Mon, 2 Sep 2024 10:09:34 +0200 Subject: [PATCH 096/194] Fix warning along the way --- src/mip/HighsDomain.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mip/HighsDomain.cpp b/src/mip/HighsDomain.cpp index e20ff5a19a..3fb1950e79 100644 --- a/src/mip/HighsDomain.cpp +++ b/src/mip/HighsDomain.cpp @@ -276,7 +276,7 @@ void HighsDomain::ConflictPoolPropagation::updateActivityUbChange( HighsInt conflict = i >> 1; const HighsDomainChange& domchg = watchedLiterals_[i].domchg; - HighsInt numInactiveDelta = + uint8_t numInactiveDelta = (domchg.boundval < newbound) - (domchg.boundval < oldbound); if (numInactiveDelta != 0) { conflictFlag_[conflict] += numInactiveDelta; From ff778c65a780bfd67994aeb2c9764cef75a838d8 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Tue, 3 Sep 2024 11:39:00 +0200 Subject: [PATCH 097/194] Two minor changes --- src/io/FilereaderLp.cpp | 11 ++++++----- src/util/HighsHashTree.h | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/io/FilereaderLp.cpp b/src/io/FilereaderLp.cpp index 5de2163359..3e6ab1cad5 100644 --- a/src/io/FilereaderLp.cpp +++ b/src/io/FilereaderLp.cpp @@ -216,16 +216,17 @@ FilereaderRetcode FilereaderLp::readModelFromFile(const HighsOptions& options, void FilereaderLp::writeToFile(FILE* file, const char* format, ...) { va_list argptr; va_start(argptr, format); - char stringbuffer[LP_MAX_LINE_LENGTH + 1] = {}; + std::array stringbuffer = {}; HighsInt tokenlength = - vsnprintf(stringbuffer, sizeof stringbuffer, format, argptr); + vsnprintf(stringbuffer.data(), stringbuffer.size(), format, argptr); va_end(argptr); - if (this->linelength + tokenlength >= LP_MAX_LINE_LENGTH) { + if (static_cast(this->linelength + tokenlength) + 1 >= + stringbuffer.size()) { fprintf(file, "\n"); - fprintf(file, "%s", stringbuffer); + fprintf(file, "%s", stringbuffer.data()); this->linelength = tokenlength; } else { - fprintf(file, "%s", stringbuffer); + fprintf(file, "%s", stringbuffer.data()); this->linelength += tokenlength; } } diff --git a/src/util/HighsHashTree.h b/src/util/HighsHashTree.h index c06bb96758..abd9012bde 100644 --- a/src/util/HighsHashTree.h +++ b/src/util/HighsHashTree.h @@ -843,7 +843,7 @@ class HighsHashTree { hash, hashPos + 1, entry); } else { // there are many collisions, determine the exact sizes first - uint8_t sizes[InnerLeaf<4>::capacity() + 1] = {}; + std::array::capacity() + 1> sizes = {}; sizes[occupation.num_set_until(hashChunk) - 1] += 1; for (int i = 0; i < leaf->size; ++i) { int pos = From d772edf6dd42ed84832b38c868155620b1df6701 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Tue, 3 Sep 2024 12:43:53 +0200 Subject: [PATCH 098/194] No need to check for null termination --- src/io/FilereaderLp.cpp | 3 +-- src/io/HighsIO.cpp | 15 +-------------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/io/FilereaderLp.cpp b/src/io/FilereaderLp.cpp index 3e6ab1cad5..8a9e0ba988 100644 --- a/src/io/FilereaderLp.cpp +++ b/src/io/FilereaderLp.cpp @@ -220,8 +220,7 @@ void FilereaderLp::writeToFile(FILE* file, const char* format, ...) { HighsInt tokenlength = vsnprintf(stringbuffer.data(), stringbuffer.size(), format, argptr); va_end(argptr); - if (static_cast(this->linelength + tokenlength) + 1 >= - stringbuffer.size()) { + if (this->linelength + tokenlength >= LP_MAX_LINE_LENGTH) { fprintf(file, "\n"); fprintf(file, "%s", stringbuffer.data()); this->linelength = tokenlength; diff --git a/src/io/HighsIO.cpp b/src/io/HighsIO.cpp index 936b2db5c0..c1e8ad4195 100644 --- a/src/io/HighsIO.cpp +++ b/src/io/HighsIO.cpp @@ -138,7 +138,7 @@ void highsLogUser(const HighsLogOptions& log_options_, const HighsLogType type, HighsLogTypeTag[(int)type]); // assert that there are no encoding errors assert(l >= 0); - len += static_cast(l); + len = static_cast(l); } if (len < msgbuffer.size()) { int l = vsnprintf(msgbuffer.data() + len, msgbuffer.size() - len, format, @@ -147,10 +147,6 @@ void highsLogUser(const HighsLogOptions& log_options_, const HighsLogType type, assert(l >= 0); len += static_cast(l); } - if (len >= msgbuffer.size()) { - // Output was truncated: for now just ensure string is null-terminated - msgbuffer[msgbuffer.size() - 1] = '\0'; - } if (log_options_.user_log_callback) { log_options_.user_log_callback(type, msgbuffer.data(), log_options_.user_log_callback_data); @@ -211,10 +207,6 @@ void highsLogDev(const HighsLogOptions& log_options_, const HighsLogType type, int len = vsnprintf(msgbuffer.data(), msgbuffer.size(), format, argptr); // assert that there are no encoding errors assert(len >= 0); - if (static_cast(len) >= msgbuffer.size()) { - // Output was truncated: for now just ensure string is null-terminated - msgbuffer[msgbuffer.size() - 1] = '\0'; - } if (log_options_.user_log_callback) { log_options_.user_log_callback(type, msgbuffer.data(), log_options_.user_log_callback_data); @@ -274,11 +266,6 @@ std::string highsFormatToString(const char* format, ...) { int len = vsnprintf(msgbuffer.data(), msgbuffer.size(), format, argptr); // assert that there are no encoding errors assert(len >= 0); - - if (static_cast(len) >= msgbuffer.size()) { - // Output was truncated: for now just ensure string is null-terminated - msgbuffer[msgbuffer.size() - 1] = '\0'; - } va_end(argptr); return std::string(msgbuffer.data()); } From cc3e870a05892bc7205f36a18cd31a5950e9d315 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Tue, 3 Sep 2024 15:15:11 +0200 Subject: [PATCH 099/194] Fix typo --- src/parallel/HighsSplitDeque.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parallel/HighsSplitDeque.h b/src/parallel/HighsSplitDeque.h index c4981f14ae..cdf3831953 100644 --- a/src/parallel/HighsSplitDeque.h +++ b/src/parallel/HighsSplitDeque.h @@ -176,7 +176,7 @@ class HighsSplitDeque { static_assert(sizeof(StealerData) <= 64, "sizeof(StealerData) exceeds cache line size"); static_assert(sizeof(WorkerBunkData) <= 64, - "sizeof(GlobalQueueData) exceeds cache line size"); + "sizeof(WorkerBunkData) exceeds cache line size"); alignas(64) OwnerData ownerData; alignas(64) std::atomic splitRequest; From b8a716f0e3f84fbdd8b9a7268aaed8c4a3fbfce0 Mon Sep 17 00:00:00 2001 From: jajhall Date: Wed, 4 Sep 2024 13:30:52 +0100 Subject: [PATCH 100/194] Formatted --- src/io/HighsIO.h | 2 +- src/lp_data/HighsSolve.cpp | 2 +- src/test/DevKkt.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/io/HighsIO.h b/src/io/HighsIO.h index b63d47141f..52bf156ff6 100644 --- a/src/io/HighsIO.h +++ b/src/io/HighsIO.h @@ -18,7 +18,7 @@ #include #include "lp_data/HighsCallback.h" -//#include "util/HighsInt.h" +// #include "util/HighsInt.h" class HighsOptions; diff --git a/src/lp_data/HighsSolve.cpp b/src/lp_data/HighsSolve.cpp index d839bfb88d..b1a33476fb 100644 --- a/src/lp_data/HighsSolve.cpp +++ b/src/lp_data/HighsSolve.cpp @@ -127,7 +127,7 @@ HighsStatus solveLp(HighsLpSolverObject& solver_object, const string message) { return HighsStatus::kError; } } // options.run_crossover == kHighsOnString - } // unwelcome_ipx_status + } // unwelcome_ipx_status } else { // PDLP has been used, so check whether claim of optimality // satisfies the HiGHS criteria diff --git a/src/test/DevKkt.cpp b/src/test/DevKkt.cpp index de393f7bf1..47941d9fd6 100644 --- a/src/test/DevKkt.cpp +++ b/src/test/DevKkt.cpp @@ -261,8 +261,8 @@ void checkComplementarySlackness(const State& state, if (fabs(state.colDual[i]) > tol && fabs(state.colValue[i] - state.colUpper[i]) > tol) { if (dev_print) - std::cout << "Comp. slackness fail: " - << "l[" << i << "]=" << state.colLower[i] << ", x[" << i + std::cout << "Comp. slackness fail: " << "l[" << i + << "]=" << state.colLower[i] << ", x[" << i << "]=" << state.colValue[i] << ", z[" << i << "]=" << state.colDual[i] << std::endl; infeas = fabs(state.colDual[i]); From 08e57a6f1479f294355e01d23b7cf89c86bfb755 Mon Sep 17 00:00:00 2001 From: Mahesh Madhav Date: Fri, 6 Sep 2024 04:29:54 +0000 Subject: [PATCH 101/194] Precompute expensive computations Replace hot FDIVs with FMULs through saving off the inverse values outside the loops, so we don't incur the cost on each loop iteration. --- src/simplex/HEkk.cpp | 7 ++++--- src/simplex/HEkkDual.cpp | 13 +++++++------ src/simplex/HEkkDual.h | 1 + src/util/HFactor.cpp | 11 ++++++----- src/util/HFactor.h | 1 + src/util/HighsSparseMatrix.cpp | 3 ++- 6 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/simplex/HEkk.cpp b/src/simplex/HEkk.cpp index b94d0959f6..0aa3788341 100644 --- a/src/simplex/HEkk.cpp +++ b/src/simplex/HEkk.cpp @@ -2124,6 +2124,7 @@ void HEkk::updateDualSteepestEdgeWeights( const double col_aq_scale = simplex_nla_.variableScaleFactor(variable_in); const double col_ap_scale = simplex_nla_.basicColScaleFactor(row_out); + const double inv_col_ap_scale = 1.0 / col_ap_scale; const bool DSE_check = false; HVector alt_dual_steepest_edge_column; @@ -2163,7 +2164,7 @@ void HEkk::updateDualSteepestEdgeWeights( HighsInt DSE_array_count = 0; for (HighsInt iRow = 0; iRow < num_row; iRow++) { const double dual_steepest_edge_array_value = - dual_steepest_edge_array[iRow] / col_ap_scale; + dual_steepest_edge_array[iRow] * inv_col_ap_scale; if (dual_steepest_edge_array_value) DSE_array_count++; if (std::abs(dual_steepest_edge_array_value) > dse_column_value_tolerance || @@ -2192,7 +2193,7 @@ void HEkk::updateDualSteepestEdgeWeights( (int)alt_dual_steepest_edge_column.count, (int)DSE_array_count); for (HighsInt iRow = 0; iRow < num_row; iRow++) { const double dual_steepest_edge_array_value = - dual_steepest_edge_array[iRow] / col_ap_scale; + dual_steepest_edge_array[iRow] * inv_col_ap_scale; if (alt_dual_steepest_edge_column.array[iRow] != 0 && dual_steepest_edge_array_value != 0) { const double dse_column_error = @@ -2234,7 +2235,7 @@ void HEkk::updateDualSteepestEdgeWeights( double basic_col_scale = simplex_nla_.basicColScaleFactor(iRow); aa_iRow /= basic_col_scale; aa_iRow *= col_aq_scale; - dual_steepest_edge_array_value /= col_ap_scale; + dual_steepest_edge_array_value *= inv_col_ap_scale; } if (DSE_check) { const double pivotal_column_error = diff --git a/src/simplex/HEkkDual.cpp b/src/simplex/HEkkDual.cpp index 6a6ca9d13e..a081dbd054 100644 --- a/src/simplex/HEkkDual.cpp +++ b/src/simplex/HEkkDual.cpp @@ -402,6 +402,7 @@ void HEkkDual::initialiseInstance() { solver_num_col = ekk_instance_.lp_.num_col_; solver_num_row = ekk_instance_.lp_.num_row_; solver_num_tot = solver_num_col + solver_num_row; + inv_solver_num_row = 1.0 / solver_num_row; a_matrix = &ekk_instance_.lp_.a_matrix_; simplex_nla = &ekk_instance_.simplex_nla_; @@ -1276,7 +1277,7 @@ void HEkkDual::iterateTasks() { chooseRow(); // Disable slice when too sparse - if (1.0 * row_ep.count / solver_num_row < 0.01) slice_PRICE = 0; + if (1.0 * row_ep.count * inv_solver_num_row < 0.01) slice_PRICE = 0; analysis->simplexTimerStart(Group1Clock); // #pragma omp parallel @@ -1498,7 +1499,7 @@ void HEkkDual::chooseRow() { move_out = delta_primal < 0 ? -1 : 1; // Update the record of average row_ep (pi_p) density. This ignores // any BTRANs done for skipped candidates - const double local_row_ep_density = (double)row_ep.count / solver_num_row; + const double local_row_ep_density = (double)row_ep.count * inv_solver_num_row; ekk_instance_.updateOperationResultDensity( local_row_ep_density, ekk_instance_.info_.row_ep_density); } @@ -1798,7 +1799,7 @@ void HEkkDual::chooseColumnSlice(HVector* row_ep) { analysis->simplexTimerStop(Chuzc0Clock); // const HighsInt solver_num_row = ekk_instance_.lp_.num_row_; - const double local_density = 1.0 * row_ep->count / solver_num_row; + const double local_density = 1.0 * row_ep->count * inv_solver_num_row; bool use_col_price; bool use_row_price_w_switch; HighsSimplexInfo& info = ekk_instance_.info_; @@ -1961,7 +1962,7 @@ void HEkkDual::updateFtran() { analysis->pointer_serial_factor_clocks); if (analysis->analyse_simplex_summary_data) analysis->operationRecordAfter(kSimplexNlaFtran, col_aq); - const double local_col_aq_density = (double)col_aq.count / solver_num_row; + const double local_col_aq_density = (double)col_aq.count * inv_solver_num_row; ekk_instance_.updateOperationResultDensity( local_col_aq_density, ekk_instance_.info_.col_aq_density); // Save the pivot value computed column-wise - used for numerical checking @@ -2002,7 +2003,7 @@ void HEkkDual::updateFtranBFRT() { if (time_updateFtranBFRT) { analysis->simplexTimerStop(FtranBfrtClock); } - const double local_col_BFRT_density = (double)col_BFRT.count / solver_num_row; + const double local_col_BFRT_density = (double)col_BFRT.count * inv_solver_num_row; ekk_instance_.updateOperationResultDensity( local_col_BFRT_density, ekk_instance_.info_.col_BFRT_density); } @@ -2043,7 +2044,7 @@ void HEkkDual::updateFtranDSE(HVector* DSE_Vector) { analysis->operationRecordAfter(kSimplexNlaFtranDse, *DSE_Vector); analysis->simplexTimerStop(FtranDseClock); const double local_row_DSE_density = - (double)DSE_Vector->count / solver_num_row; + (double)DSE_Vector->count * inv_solver_num_row; ekk_instance_.updateOperationResultDensity( local_row_DSE_density, ekk_instance_.info_.row_DSE_density); } diff --git a/src/simplex/HEkkDual.h b/src/simplex/HEkkDual.h index 573abc921d..c3db2ed15d 100644 --- a/src/simplex/HEkkDual.h +++ b/src/simplex/HEkkDual.h @@ -388,6 +388,7 @@ class HEkkDual { HighsInt solver_num_row; HighsInt solver_num_col; HighsInt solver_num_tot; + double inv_solver_num_row; // 1.0 / solver_num_row const HighsSparseMatrix* a_matrix; const HSimplexNla* simplex_nla; diff --git a/src/util/HFactor.cpp b/src/util/HFactor.cpp index c7436fe55a..458f221684 100644 --- a/src/util/HFactor.cpp +++ b/src/util/HFactor.cpp @@ -211,6 +211,7 @@ void HFactor::setupGeneral( num_row = num_row_; num_col = num_col_; num_basic = num_basic_; + inv_num_row = 1.0/num_row; this->a_matrix_valid = true; a_start = a_start_; a_index = a_index_; @@ -1543,7 +1544,7 @@ void HFactor::ftranL(HVector& rhs, const double expected_density, } // Determine style of solve - double current_density = 1.0 * rhs.count / num_row; + double current_density = 1.0 * rhs.count * inv_num_row; const bool sparse_solve = rhs.count < 0 || current_density > kHyperCancel || expected_density > kHyperFtranL; if (sparse_solve) { @@ -1591,7 +1592,7 @@ void HFactor::btranL(HVector& rhs, const double expected_density, factor_timer.start(FactorBtranLower, factor_timer_clock_pointer); // Determine style of solve - const double current_density = 1.0 * rhs.count / num_row; + const double current_density = 1.0 * rhs.count * inv_num_row; const bool sparse_solve = rhs.count < 0 || current_density > kHyperCancel || expected_density > kHyperBtranL; if (sparse_solve) { @@ -1666,7 +1667,7 @@ void HFactor::ftranU(HVector& rhs, const double expected_density, // The regular part // // Determine style of solve - const double current_density = 1.0 * rhs.count / num_row; + const double current_density = 1.0 * rhs.count * inv_num_row; const bool sparse_solve = rhs.count < 0 || current_density > kHyperCancel || expected_density > kHyperFtranU; if (sparse_solve) { @@ -1720,7 +1721,7 @@ void HFactor::ftranU(HVector& rhs, const double expected_density, rhs_synthetic_tick * 15 + (u_pivot_count - num_row) * 10; factor_timer.stop(use_clock, factor_timer_clock_pointer); if (report_ftran_upper_sparse) { - const double final_density = 1.0 * rhs.count / num_row; + const double final_density = 1.0 * rhs.count * inv_num_row; printf( "FactorFtranUpperSps: expected_density = %10.4g; current_density = " "%10.4g; final_density = %10.4g\n", @@ -1773,7 +1774,7 @@ void HFactor::btranU(HVector& rhs, const double expected_density, // The regular part // // Determine style of solve - const double current_density = 1.0 * rhs.count / num_row; + const double current_density = 1.0 * rhs.count * inv_num_row; const bool sparse_solve = rhs.count < 0 || current_density > kHyperCancel || expected_density > kHyperBtranU; if (sparse_solve) { diff --git a/src/util/HFactor.h b/src/util/HFactor.h index d06a79af67..63ad3d621b 100644 --- a/src/util/HFactor.h +++ b/src/util/HFactor.h @@ -347,6 +347,7 @@ class HFactor { HighsInt num_row; HighsInt num_col; HighsInt num_basic; + double inv_num_row; // 1.0/num_row private: bool a_matrix_valid; diff --git a/src/util/HighsSparseMatrix.cpp b/src/util/HighsSparseMatrix.cpp index ce8e37695b..16648e1506 100644 --- a/src/util/HighsSparseMatrix.cpp +++ b/src/util/HighsSparseMatrix.cpp @@ -1383,6 +1383,7 @@ void HighsSparseMatrix::priceByRowWithSwitch( assert(HighsInt(result.size) == this->num_col_); assert(HighsInt(result.index.size()) == this->num_col_); if (expected_density <= kHyperPriceDensity) { + double inv_num_col = 1.0 / this->num_col_; for (HighsInt ix = next_index; ix < column.count; ix++) { HighsInt iRow = column.index[ix]; // Determine whether p_end_ or the next start_ ends the loop @@ -1394,7 +1395,7 @@ void HighsSparseMatrix::priceByRowWithSwitch( } // Possibly switch to standard row-wise price HighsInt row_num_nz = to_iEl - this->start_[iRow]; - double local_density = (1.0 * result.count) / this->num_col_; + double local_density = (1.0 * result.count) * inv_num_col; bool switch_to_dense = result.count + row_num_nz >= this->num_col_ || local_density > switch_density; if (switch_to_dense) break; From f1fd3a642c49106965a5132d56cbbb61059c3b5f Mon Sep 17 00:00:00 2001 From: Julian Hall Date: Fri, 6 Sep 2024 14:04:36 +0100 Subject: [PATCH 102/194] Revert "Precompute expensive computations" --- src/simplex/HEkk.cpp | 7 +++---- src/simplex/HEkkDual.cpp | 13 ++++++------- src/simplex/HEkkDual.h | 1 - src/util/HFactor.cpp | 11 +++++------ src/util/HFactor.h | 1 - src/util/HighsSparseMatrix.cpp | 3 +-- 6 files changed, 15 insertions(+), 21 deletions(-) diff --git a/src/simplex/HEkk.cpp b/src/simplex/HEkk.cpp index 32fc0b40e3..459bdd7a52 100644 --- a/src/simplex/HEkk.cpp +++ b/src/simplex/HEkk.cpp @@ -2126,7 +2126,6 @@ void HEkk::updateDualSteepestEdgeWeights( const double col_aq_scale = simplex_nla_.variableScaleFactor(variable_in); const double col_ap_scale = simplex_nla_.basicColScaleFactor(row_out); - const double inv_col_ap_scale = 1.0 / col_ap_scale; const bool DSE_check = false; HVector alt_dual_steepest_edge_column; @@ -2166,7 +2165,7 @@ void HEkk::updateDualSteepestEdgeWeights( HighsInt DSE_array_count = 0; for (HighsInt iRow = 0; iRow < num_row; iRow++) { const double dual_steepest_edge_array_value = - dual_steepest_edge_array[iRow] * inv_col_ap_scale; + dual_steepest_edge_array[iRow] / col_ap_scale; if (dual_steepest_edge_array_value) DSE_array_count++; if (std::abs(dual_steepest_edge_array_value) > dse_column_value_tolerance || @@ -2195,7 +2194,7 @@ void HEkk::updateDualSteepestEdgeWeights( (int)alt_dual_steepest_edge_column.count, (int)DSE_array_count); for (HighsInt iRow = 0; iRow < num_row; iRow++) { const double dual_steepest_edge_array_value = - dual_steepest_edge_array[iRow] * inv_col_ap_scale; + dual_steepest_edge_array[iRow] / col_ap_scale; if (alt_dual_steepest_edge_column.array[iRow] != 0 && dual_steepest_edge_array_value != 0) { const double dse_column_error = @@ -2237,7 +2236,7 @@ void HEkk::updateDualSteepestEdgeWeights( double basic_col_scale = simplex_nla_.basicColScaleFactor(iRow); aa_iRow /= basic_col_scale; aa_iRow *= col_aq_scale; - dual_steepest_edge_array_value *= inv_col_ap_scale; + dual_steepest_edge_array_value /= col_ap_scale; } if (DSE_check) { const double pivotal_column_error = diff --git a/src/simplex/HEkkDual.cpp b/src/simplex/HEkkDual.cpp index a081dbd054..6a6ca9d13e 100644 --- a/src/simplex/HEkkDual.cpp +++ b/src/simplex/HEkkDual.cpp @@ -402,7 +402,6 @@ void HEkkDual::initialiseInstance() { solver_num_col = ekk_instance_.lp_.num_col_; solver_num_row = ekk_instance_.lp_.num_row_; solver_num_tot = solver_num_col + solver_num_row; - inv_solver_num_row = 1.0 / solver_num_row; a_matrix = &ekk_instance_.lp_.a_matrix_; simplex_nla = &ekk_instance_.simplex_nla_; @@ -1277,7 +1276,7 @@ void HEkkDual::iterateTasks() { chooseRow(); // Disable slice when too sparse - if (1.0 * row_ep.count * inv_solver_num_row < 0.01) slice_PRICE = 0; + if (1.0 * row_ep.count / solver_num_row < 0.01) slice_PRICE = 0; analysis->simplexTimerStart(Group1Clock); // #pragma omp parallel @@ -1499,7 +1498,7 @@ void HEkkDual::chooseRow() { move_out = delta_primal < 0 ? -1 : 1; // Update the record of average row_ep (pi_p) density. This ignores // any BTRANs done for skipped candidates - const double local_row_ep_density = (double)row_ep.count * inv_solver_num_row; + const double local_row_ep_density = (double)row_ep.count / solver_num_row; ekk_instance_.updateOperationResultDensity( local_row_ep_density, ekk_instance_.info_.row_ep_density); } @@ -1799,7 +1798,7 @@ void HEkkDual::chooseColumnSlice(HVector* row_ep) { analysis->simplexTimerStop(Chuzc0Clock); // const HighsInt solver_num_row = ekk_instance_.lp_.num_row_; - const double local_density = 1.0 * row_ep->count * inv_solver_num_row; + const double local_density = 1.0 * row_ep->count / solver_num_row; bool use_col_price; bool use_row_price_w_switch; HighsSimplexInfo& info = ekk_instance_.info_; @@ -1962,7 +1961,7 @@ void HEkkDual::updateFtran() { analysis->pointer_serial_factor_clocks); if (analysis->analyse_simplex_summary_data) analysis->operationRecordAfter(kSimplexNlaFtran, col_aq); - const double local_col_aq_density = (double)col_aq.count * inv_solver_num_row; + const double local_col_aq_density = (double)col_aq.count / solver_num_row; ekk_instance_.updateOperationResultDensity( local_col_aq_density, ekk_instance_.info_.col_aq_density); // Save the pivot value computed column-wise - used for numerical checking @@ -2003,7 +2002,7 @@ void HEkkDual::updateFtranBFRT() { if (time_updateFtranBFRT) { analysis->simplexTimerStop(FtranBfrtClock); } - const double local_col_BFRT_density = (double)col_BFRT.count * inv_solver_num_row; + const double local_col_BFRT_density = (double)col_BFRT.count / solver_num_row; ekk_instance_.updateOperationResultDensity( local_col_BFRT_density, ekk_instance_.info_.col_BFRT_density); } @@ -2044,7 +2043,7 @@ void HEkkDual::updateFtranDSE(HVector* DSE_Vector) { analysis->operationRecordAfter(kSimplexNlaFtranDse, *DSE_Vector); analysis->simplexTimerStop(FtranDseClock); const double local_row_DSE_density = - (double)DSE_Vector->count * inv_solver_num_row; + (double)DSE_Vector->count / solver_num_row; ekk_instance_.updateOperationResultDensity( local_row_DSE_density, ekk_instance_.info_.row_DSE_density); } diff --git a/src/simplex/HEkkDual.h b/src/simplex/HEkkDual.h index c3db2ed15d..573abc921d 100644 --- a/src/simplex/HEkkDual.h +++ b/src/simplex/HEkkDual.h @@ -388,7 +388,6 @@ class HEkkDual { HighsInt solver_num_row; HighsInt solver_num_col; HighsInt solver_num_tot; - double inv_solver_num_row; // 1.0 / solver_num_row const HighsSparseMatrix* a_matrix; const HSimplexNla* simplex_nla; diff --git a/src/util/HFactor.cpp b/src/util/HFactor.cpp index 458f221684..c7436fe55a 100644 --- a/src/util/HFactor.cpp +++ b/src/util/HFactor.cpp @@ -211,7 +211,6 @@ void HFactor::setupGeneral( num_row = num_row_; num_col = num_col_; num_basic = num_basic_; - inv_num_row = 1.0/num_row; this->a_matrix_valid = true; a_start = a_start_; a_index = a_index_; @@ -1544,7 +1543,7 @@ void HFactor::ftranL(HVector& rhs, const double expected_density, } // Determine style of solve - double current_density = 1.0 * rhs.count * inv_num_row; + double current_density = 1.0 * rhs.count / num_row; const bool sparse_solve = rhs.count < 0 || current_density > kHyperCancel || expected_density > kHyperFtranL; if (sparse_solve) { @@ -1592,7 +1591,7 @@ void HFactor::btranL(HVector& rhs, const double expected_density, factor_timer.start(FactorBtranLower, factor_timer_clock_pointer); // Determine style of solve - const double current_density = 1.0 * rhs.count * inv_num_row; + const double current_density = 1.0 * rhs.count / num_row; const bool sparse_solve = rhs.count < 0 || current_density > kHyperCancel || expected_density > kHyperBtranL; if (sparse_solve) { @@ -1667,7 +1666,7 @@ void HFactor::ftranU(HVector& rhs, const double expected_density, // The regular part // // Determine style of solve - const double current_density = 1.0 * rhs.count * inv_num_row; + const double current_density = 1.0 * rhs.count / num_row; const bool sparse_solve = rhs.count < 0 || current_density > kHyperCancel || expected_density > kHyperFtranU; if (sparse_solve) { @@ -1721,7 +1720,7 @@ void HFactor::ftranU(HVector& rhs, const double expected_density, rhs_synthetic_tick * 15 + (u_pivot_count - num_row) * 10; factor_timer.stop(use_clock, factor_timer_clock_pointer); if (report_ftran_upper_sparse) { - const double final_density = 1.0 * rhs.count * inv_num_row; + const double final_density = 1.0 * rhs.count / num_row; printf( "FactorFtranUpperSps: expected_density = %10.4g; current_density = " "%10.4g; final_density = %10.4g\n", @@ -1774,7 +1773,7 @@ void HFactor::btranU(HVector& rhs, const double expected_density, // The regular part // // Determine style of solve - const double current_density = 1.0 * rhs.count * inv_num_row; + const double current_density = 1.0 * rhs.count / num_row; const bool sparse_solve = rhs.count < 0 || current_density > kHyperCancel || expected_density > kHyperBtranU; if (sparse_solve) { diff --git a/src/util/HFactor.h b/src/util/HFactor.h index 63ad3d621b..d06a79af67 100644 --- a/src/util/HFactor.h +++ b/src/util/HFactor.h @@ -347,7 +347,6 @@ class HFactor { HighsInt num_row; HighsInt num_col; HighsInt num_basic; - double inv_num_row; // 1.0/num_row private: bool a_matrix_valid; diff --git a/src/util/HighsSparseMatrix.cpp b/src/util/HighsSparseMatrix.cpp index 16648e1506..ce8e37695b 100644 --- a/src/util/HighsSparseMatrix.cpp +++ b/src/util/HighsSparseMatrix.cpp @@ -1383,7 +1383,6 @@ void HighsSparseMatrix::priceByRowWithSwitch( assert(HighsInt(result.size) == this->num_col_); assert(HighsInt(result.index.size()) == this->num_col_); if (expected_density <= kHyperPriceDensity) { - double inv_num_col = 1.0 / this->num_col_; for (HighsInt ix = next_index; ix < column.count; ix++) { HighsInt iRow = column.index[ix]; // Determine whether p_end_ or the next start_ ends the loop @@ -1395,7 +1394,7 @@ void HighsSparseMatrix::priceByRowWithSwitch( } // Possibly switch to standard row-wise price HighsInt row_num_nz = to_iEl - this->start_[iRow]; - double local_density = (1.0 * result.count) * inv_num_col; + double local_density = (1.0 * result.count) / this->num_col_; bool switch_to_dense = result.count + row_num_nz >= this->num_col_ || local_density > switch_density; if (switch_to_dense) break; From a7a6e96171f4bc09dc04307616c4c5695f641e6b Mon Sep 17 00:00:00 2001 From: Mahesh Madhav Date: Fri, 6 Sep 2024 20:20:56 +0000 Subject: [PATCH 103/194] Replace expensive computations Replace hot FDIVs with FMULs through precomputing inverse values outside the loops. FDIVs are always more expensive than FMULs, although the amount varies per CPU microarchitecture. --- src/simplex/HEkk.cpp | 7 ++++--- src/simplex/HEkkDual.cpp | 14 ++++++++------ src/simplex/HEkkDual.h | 1 + src/util/HFactor.cpp | 11 ++++++----- src/util/HFactor.h | 1 + src/util/HighsSparseMatrix.cpp | 3 ++- 6 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/simplex/HEkk.cpp b/src/simplex/HEkk.cpp index b94d0959f6..0aa3788341 100644 --- a/src/simplex/HEkk.cpp +++ b/src/simplex/HEkk.cpp @@ -2124,6 +2124,7 @@ void HEkk::updateDualSteepestEdgeWeights( const double col_aq_scale = simplex_nla_.variableScaleFactor(variable_in); const double col_ap_scale = simplex_nla_.basicColScaleFactor(row_out); + const double inv_col_ap_scale = 1.0 / col_ap_scale; const bool DSE_check = false; HVector alt_dual_steepest_edge_column; @@ -2163,7 +2164,7 @@ void HEkk::updateDualSteepestEdgeWeights( HighsInt DSE_array_count = 0; for (HighsInt iRow = 0; iRow < num_row; iRow++) { const double dual_steepest_edge_array_value = - dual_steepest_edge_array[iRow] / col_ap_scale; + dual_steepest_edge_array[iRow] * inv_col_ap_scale; if (dual_steepest_edge_array_value) DSE_array_count++; if (std::abs(dual_steepest_edge_array_value) > dse_column_value_tolerance || @@ -2192,7 +2193,7 @@ void HEkk::updateDualSteepestEdgeWeights( (int)alt_dual_steepest_edge_column.count, (int)DSE_array_count); for (HighsInt iRow = 0; iRow < num_row; iRow++) { const double dual_steepest_edge_array_value = - dual_steepest_edge_array[iRow] / col_ap_scale; + dual_steepest_edge_array[iRow] * inv_col_ap_scale; if (alt_dual_steepest_edge_column.array[iRow] != 0 && dual_steepest_edge_array_value != 0) { const double dse_column_error = @@ -2234,7 +2235,7 @@ void HEkk::updateDualSteepestEdgeWeights( double basic_col_scale = simplex_nla_.basicColScaleFactor(iRow); aa_iRow /= basic_col_scale; aa_iRow *= col_aq_scale; - dual_steepest_edge_array_value /= col_ap_scale; + dual_steepest_edge_array_value *= inv_col_ap_scale; } if (DSE_check) { const double pivotal_column_error = diff --git a/src/simplex/HEkkDual.cpp b/src/simplex/HEkkDual.cpp index 6a6ca9d13e..5e2e49755f 100644 --- a/src/simplex/HEkkDual.cpp +++ b/src/simplex/HEkkDual.cpp @@ -402,6 +402,7 @@ void HEkkDual::initialiseInstance() { solver_num_col = ekk_instance_.lp_.num_col_; solver_num_row = ekk_instance_.lp_.num_row_; solver_num_tot = solver_num_col + solver_num_row; + inv_solver_num_row = 1.0 / solver_num_row; a_matrix = &ekk_instance_.lp_.a_matrix_; simplex_nla = &ekk_instance_.simplex_nla_; @@ -1276,7 +1277,7 @@ void HEkkDual::iterateTasks() { chooseRow(); // Disable slice when too sparse - if (1.0 * row_ep.count / solver_num_row < 0.01) slice_PRICE = 0; + if (1.0 * row_ep.count * inv_solver_num_row < 0.01) slice_PRICE = 0; analysis->simplexTimerStart(Group1Clock); // #pragma omp parallel @@ -1498,7 +1499,7 @@ void HEkkDual::chooseRow() { move_out = delta_primal < 0 ? -1 : 1; // Update the record of average row_ep (pi_p) density. This ignores // any BTRANs done for skipped candidates - const double local_row_ep_density = (double)row_ep.count / solver_num_row; + const double local_row_ep_density = (double)row_ep.count * inv_solver_num_row; ekk_instance_.updateOperationResultDensity( local_row_ep_density, ekk_instance_.info_.row_ep_density); } @@ -1798,7 +1799,7 @@ void HEkkDual::chooseColumnSlice(HVector* row_ep) { analysis->simplexTimerStop(Chuzc0Clock); // const HighsInt solver_num_row = ekk_instance_.lp_.num_row_; - const double local_density = 1.0 * row_ep->count / solver_num_row; + const double local_density = 1.0 * row_ep->count * inv_solver_num_row; bool use_col_price; bool use_row_price_w_switch; HighsSimplexInfo& info = ekk_instance_.info_; @@ -1961,7 +1962,7 @@ void HEkkDual::updateFtran() { analysis->pointer_serial_factor_clocks); if (analysis->analyse_simplex_summary_data) analysis->operationRecordAfter(kSimplexNlaFtran, col_aq); - const double local_col_aq_density = (double)col_aq.count / solver_num_row; + const double local_col_aq_density = (double)col_aq.count * inv_solver_num_row; ekk_instance_.updateOperationResultDensity( local_col_aq_density, ekk_instance_.info_.col_aq_density); // Save the pivot value computed column-wise - used for numerical checking @@ -2002,7 +2003,8 @@ void HEkkDual::updateFtranBFRT() { if (time_updateFtranBFRT) { analysis->simplexTimerStop(FtranBfrtClock); } - const double local_col_BFRT_density = (double)col_BFRT.count / solver_num_row; + const double local_col_BFRT_density = + (double)col_BFRT.count * inv_solver_num_row; ekk_instance_.updateOperationResultDensity( local_col_BFRT_density, ekk_instance_.info_.col_BFRT_density); } @@ -2043,7 +2045,7 @@ void HEkkDual::updateFtranDSE(HVector* DSE_Vector) { analysis->operationRecordAfter(kSimplexNlaFtranDse, *DSE_Vector); analysis->simplexTimerStop(FtranDseClock); const double local_row_DSE_density = - (double)DSE_Vector->count / solver_num_row; + (double)DSE_Vector->count * inv_solver_num_row; ekk_instance_.updateOperationResultDensity( local_row_DSE_density, ekk_instance_.info_.row_DSE_density); } diff --git a/src/simplex/HEkkDual.h b/src/simplex/HEkkDual.h index 573abc921d..d80826c637 100644 --- a/src/simplex/HEkkDual.h +++ b/src/simplex/HEkkDual.h @@ -388,6 +388,7 @@ class HEkkDual { HighsInt solver_num_row; HighsInt solver_num_col; HighsInt solver_num_tot; + double inv_solver_num_row; // 1.0 / solver_num_row const HighsSparseMatrix* a_matrix; const HSimplexNla* simplex_nla; diff --git a/src/util/HFactor.cpp b/src/util/HFactor.cpp index c7436fe55a..baf1bd1370 100644 --- a/src/util/HFactor.cpp +++ b/src/util/HFactor.cpp @@ -211,6 +211,7 @@ void HFactor::setupGeneral( num_row = num_row_; num_col = num_col_; num_basic = num_basic_; + inv_num_row = 1.0 / num_row; this->a_matrix_valid = true; a_start = a_start_; a_index = a_index_; @@ -1543,7 +1544,7 @@ void HFactor::ftranL(HVector& rhs, const double expected_density, } // Determine style of solve - double current_density = 1.0 * rhs.count / num_row; + double current_density = 1.0 * rhs.count * inv_num_row; const bool sparse_solve = rhs.count < 0 || current_density > kHyperCancel || expected_density > kHyperFtranL; if (sparse_solve) { @@ -1591,7 +1592,7 @@ void HFactor::btranL(HVector& rhs, const double expected_density, factor_timer.start(FactorBtranLower, factor_timer_clock_pointer); // Determine style of solve - const double current_density = 1.0 * rhs.count / num_row; + const double current_density = 1.0 * rhs.count * inv_num_row; const bool sparse_solve = rhs.count < 0 || current_density > kHyperCancel || expected_density > kHyperBtranL; if (sparse_solve) { @@ -1666,7 +1667,7 @@ void HFactor::ftranU(HVector& rhs, const double expected_density, // The regular part // // Determine style of solve - const double current_density = 1.0 * rhs.count / num_row; + const double current_density = 1.0 * rhs.count * inv_num_row; const bool sparse_solve = rhs.count < 0 || current_density > kHyperCancel || expected_density > kHyperFtranU; if (sparse_solve) { @@ -1720,7 +1721,7 @@ void HFactor::ftranU(HVector& rhs, const double expected_density, rhs_synthetic_tick * 15 + (u_pivot_count - num_row) * 10; factor_timer.stop(use_clock, factor_timer_clock_pointer); if (report_ftran_upper_sparse) { - const double final_density = 1.0 * rhs.count / num_row; + const double final_density = 1.0 * rhs.count * inv_num_row; printf( "FactorFtranUpperSps: expected_density = %10.4g; current_density = " "%10.4g; final_density = %10.4g\n", @@ -1773,7 +1774,7 @@ void HFactor::btranU(HVector& rhs, const double expected_density, // The regular part // // Determine style of solve - const double current_density = 1.0 * rhs.count / num_row; + const double current_density = 1.0 * rhs.count * inv_num_row; const bool sparse_solve = rhs.count < 0 || current_density > kHyperCancel || expected_density > kHyperBtranU; if (sparse_solve) { diff --git a/src/util/HFactor.h b/src/util/HFactor.h index d06a79af67..2f75260d8a 100644 --- a/src/util/HFactor.h +++ b/src/util/HFactor.h @@ -347,6 +347,7 @@ class HFactor { HighsInt num_row; HighsInt num_col; HighsInt num_basic; + double inv_num_row; // 1.0/num_row private: bool a_matrix_valid; diff --git a/src/util/HighsSparseMatrix.cpp b/src/util/HighsSparseMatrix.cpp index ce8e37695b..16648e1506 100644 --- a/src/util/HighsSparseMatrix.cpp +++ b/src/util/HighsSparseMatrix.cpp @@ -1383,6 +1383,7 @@ void HighsSparseMatrix::priceByRowWithSwitch( assert(HighsInt(result.size) == this->num_col_); assert(HighsInt(result.index.size()) == this->num_col_); if (expected_density <= kHyperPriceDensity) { + double inv_num_col = 1.0 / this->num_col_; for (HighsInt ix = next_index; ix < column.count; ix++) { HighsInt iRow = column.index[ix]; // Determine whether p_end_ or the next start_ ends the loop @@ -1394,7 +1395,7 @@ void HighsSparseMatrix::priceByRowWithSwitch( } // Possibly switch to standard row-wise price HighsInt row_num_nz = to_iEl - this->start_[iRow]; - double local_density = (1.0 * result.count) / this->num_col_; + double local_density = (1.0 * result.count) * inv_num_col; bool switch_to_dense = result.count + row_num_nz >= this->num_col_ || local_density > switch_density; if (switch_to_dense) break; From b42d3140a50051efd4e097df0e605943502148e0 Mon Sep 17 00:00:00 2001 From: jajhall Date: Mon, 9 Sep 2024 00:39:38 +0100 Subject: [PATCH 104/194] Now reading rvb-sub, but need to add the comment trim to other MPS read methods to pass unit test --- check/TestFilereader.cpp | 15 ++++++++++++++- src/io/HMpsFF.cpp | 5 +++++ src/util/stringutil.cpp | 9 +++++++++ src/util/stringutil.h | 17 ++++++++++------- 4 files changed, 38 insertions(+), 8 deletions(-) diff --git a/check/TestFilereader.cpp b/check/TestFilereader.cpp index 14ad114b6e..98c0ecae26 100644 --- a/check/TestFilereader.cpp +++ b/check/TestFilereader.cpp @@ -10,7 +10,7 @@ #include "lp_data/HighsLp.h" #include "lp_data/HighsLpUtils.h" -const bool dev_run = false; +const bool dev_run = true; TEST_CASE("filereader-edge-cases", "[highs_filereader]") { std::string model = ""; @@ -343,3 +343,16 @@ TEST_CASE("filereader-dD2e", "[highs_filereader]") { objective_value = highs.getInfo().objective_function_value; REQUIRE(objective_value == optimal_objective_value); } + +TEST_CASE("filereader-comment", "[highs_filereader]") { + // Check that comments - either whole line with * in first column, + // or rest of line following */$ are handled correctly + const double optimal_objective_value = -4; + std::string model_file = std::string(HIGHS_DIR) + "/check/instances/comment.mps"; + Highs highs; + highs.setOptionValue("output_flag", dev_run); + REQUIRE(highs.readModel(model_file) == HighsStatus::kOk); + REQUIRE(highs.run() == HighsStatus::kOk); + double objective_value = highs.getInfo().objective_function_value; + REQUIRE(objective_value == optimal_objective_value); +} diff --git a/src/io/HMpsFF.cpp b/src/io/HMpsFF.cpp index 14f0bf5dc0..823109a6e5 100644 --- a/src/io/HMpsFF.cpp +++ b/src/io/HMpsFF.cpp @@ -534,6 +534,9 @@ HMpsFF::Parsekey HMpsFF::parseRows(const HighsLogOptions& log_options, assert(row_upper.size() == 0); while (getline(file, strline)) { if (is_empty(strline) || strline[0] == '*') continue; + printf("HMpsFF::parseRows before rtrim: strline = \"%s\"\n", strline.c_str()); + mpsCommentTrim(strline); + printf("HMpsFF::parseRows after rtrim: strline = \"%s\"\n", strline.c_str()); double current = getWallTime(); if (time_limit > 0 && current - start_time > time_limit) return HMpsFF::Parsekey::kTimeout; @@ -588,7 +591,9 @@ HMpsFF::Parsekey HMpsFF::parseRows(const HighsLogOptions& log_options, } std::string rowname = first_word(strline, start + 1); + printf("HMpsFF::parseRows rowname = \"%s\"; strline = \"%s\"\n", rowname.c_str(), strline.c_str()); size_t rowname_end = first_word_end(strline, start + 1); + printf("HMpsFF::parseRows rowname_end = %d\n", int(rowname_end)); // Detect if file is in fixed format. if (!is_end(strline, rowname_end)) { diff --git a/src/util/stringutil.cpp b/src/util/stringutil.cpp index 5b0de58e73..5a4fef80a5 100644 --- a/src/util/stringutil.cpp +++ b/src/util/stringutil.cpp @@ -126,3 +126,12 @@ std::string first_word(std::string& str, size_t start) { assert(next_word_start != std::string::npos); return str.substr(next_word_start, next_word_end - next_word_start); } + +std::string& mpsCommentTrim(std::string& str) { + const size_t p = str.find_first_of(mps_comment_chars); + printf("In rTrimAt, First of \"%s\" in \"%s\" is %zu\n", mps_comment_chars.c_str(), str.c_str(), p); + if (p > str.length()) return str; + str.erase(p); + return str; +} + diff --git a/src/util/stringutil.h b/src/util/stringutil.h index 4fa072485b..95b71e17e1 100644 --- a/src/util/stringutil.h +++ b/src/util/stringutil.h @@ -27,18 +27,21 @@ void strTrim(char* str); void tolower(std::string& str); -const std::string non_chars = "\t\n\v\f\r "; -std::string& ltrim(std::string& str, const std::string& chars = non_chars); -std::string& rtrim(std::string& str, const std::string& chars = non_chars); -std::string& trim(std::string& str, const std::string& chars = non_chars); +const std::string default_non_chars = "\t\n\v\f\r "; +const std::string mps_comment_chars = "*$"; +std::string& ltrim(std::string& str, const std::string& chars = default_non_chars); +std::string& rtrim(std::string& str, const std::string& chars = default_non_chars); +std::string& trim(std::string& str, const std::string& chars = default_non_chars); -bool is_empty(std::string& str, const std::string& chars = non_chars); -bool is_empty(char c, const std::string& chars = non_chars); -bool is_end(std::string& str, size_t end, const std::string& chars = non_chars); +bool is_empty(std::string& str, const std::string& chars = default_non_chars); +bool is_empty(char c, const std::string& chars = default_non_chars); +bool is_end(std::string& str, size_t end, const std::string& chars = default_non_chars); // todo: replace with pair of references rather than string ret value to avoid // copy and also using function below. or do it properly with iterators. std::string first_word(std::string& str, size_t start); size_t first_word_end(std::string& str, size_t start); +std::string& mpsCommentTrim(std::string& str); + #endif From cd80bd5dc7495b1d51ef0e7338701465011f7d0a Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 9 Sep 2024 17:19:58 +0100 Subject: [PATCH 105/194] Formatted with Ubuntu clang-format version 14.0.0-1ubuntu1.1 --- src/lp_data/HighsSolve.cpp | 2 +- src/test/DevKkt.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lp_data/HighsSolve.cpp b/src/lp_data/HighsSolve.cpp index b1a33476fb..d839bfb88d 100644 --- a/src/lp_data/HighsSolve.cpp +++ b/src/lp_data/HighsSolve.cpp @@ -127,7 +127,7 @@ HighsStatus solveLp(HighsLpSolverObject& solver_object, const string message) { return HighsStatus::kError; } } // options.run_crossover == kHighsOnString - } // unwelcome_ipx_status + } // unwelcome_ipx_status } else { // PDLP has been used, so check whether claim of optimality // satisfies the HiGHS criteria diff --git a/src/test/DevKkt.cpp b/src/test/DevKkt.cpp index 47941d9fd6..de393f7bf1 100644 --- a/src/test/DevKkt.cpp +++ b/src/test/DevKkt.cpp @@ -261,8 +261,8 @@ void checkComplementarySlackness(const State& state, if (fabs(state.colDual[i]) > tol && fabs(state.colValue[i] - state.colUpper[i]) > tol) { if (dev_print) - std::cout << "Comp. slackness fail: " << "l[" << i - << "]=" << state.colLower[i] << ", x[" << i + std::cout << "Comp. slackness fail: " + << "l[" << i << "]=" << state.colLower[i] << ", x[" << i << "]=" << state.colValue[i] << ", z[" << i << "]=" << state.colDual[i] << std::endl; infeas = fabs(state.colDual[i]); From c706fe9fb36d3c7ddbf39d7c4dbeaa724d85952c Mon Sep 17 00:00:00 2001 From: fwesselm Date: Tue, 10 Sep 2024 08:07:13 +0200 Subject: [PATCH 106/194] Fix some typos --- src/interfaces/highs_c_api.h | 2 +- src/lp_data/HighsInterface.cpp | 6 +++--- src/lp_data/HighsSolve.cpp | 2 +- src/util/HVectorBase.h | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/interfaces/highs_c_api.h b/src/interfaces/highs_c_api.h index 3a0c9c05c9..43833a9c75 100644 --- a/src/interfaces/highs_c_api.h +++ b/src/interfaces/highs_c_api.h @@ -18,7 +18,7 @@ // Highs_mipCall or Highs_qpCall, and these methods return the // appropriate solution information // -// For sophisticated applications, where esoteric solutiuon +// For sophisticated applications, where esoteric solution // information is needed, or if a sequence of modified models need to // be solved, use the Highs_create method to generate a pointer to an // instance of the C++ Highs class, and then use any of a large number diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 6176c39bf1..f3150c64e8 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -1712,7 +1712,7 @@ HighsStatus Highs::elasticityFilterReturn( if (return_status == HighsStatus::kOk) { // Solution is invalidated by deleting rows and columns, but - // primal values are correct. Have to recompute row acivities, + // primal values are correct. Have to recompute row activities, // though this->model_.lp_.a_matrix_.productQuad(this->solution_.row_value, this->solution_.col_value); @@ -1842,7 +1842,7 @@ HighsStatus Highs::elasticityFilter( const double lower_penalty = has_local_lower_penalty ? local_lower_penalty[iCol] : global_lower_penalty; - // Negative lower penalty and infininte upper bound implies that the + // Negative lower penalty and infinite upper bound implies that the // bounds cannot be violated if (lower_penalty < 0 && upper >= kHighsInf) continue; @@ -1850,7 +1850,7 @@ HighsStatus Highs::elasticityFilter( const double upper_penalty = has_local_upper_penalty ? local_upper_penalty[iCol] : global_upper_penalty; - // Infininte upper bound and negative lower penalty implies that the + // Infinite upper bound and negative lower penalty implies that the // bounds cannot be violated if (lower <= -kHighsInf && upper_penalty < 0) continue; erow_lower.push_back(lower); diff --git a/src/lp_data/HighsSolve.cpp b/src/lp_data/HighsSolve.cpp index d839bfb88d..233b16adcd 100644 --- a/src/lp_data/HighsSolve.cpp +++ b/src/lp_data/HighsSolve.cpp @@ -136,7 +136,7 @@ HighsStatus solveLp(HighsLpSolverObject& solver_object, const string message) { // and duality gap that are within the tolerances supplied by // HiGHS, the HiGHS primal and dual feasibility tolerances may // not be satisfied since they are absolute, and in PDLP they - // are relative. Note that, even when only one PDLP row activit + // are relative. Note that, even when only one PDLP row activity // fails to satisfy the absolute tolerance, the absolute norm // measure reported by PDLP will not necessarily be the same as // with HiGHS, since PDLP uses the 2-norm, and HiGHS the diff --git a/src/util/HVectorBase.h b/src/util/HVectorBase.h index 9db206d809..5b086f3f55 100644 --- a/src/util/HVectorBase.h +++ b/src/util/HVectorBase.h @@ -77,7 +77,7 @@ class HVectorBase { vector packValue; //!< Packed values /** - * @brief Copy from another HVector structure to this instanc + * @brief Copy from another HVector structure to this instance */ template void copy(const HVectorBase* From 0b49a9e71dd064794258f3b0ccc94cbb0f208737 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Tue, 10 Sep 2024 09:15:40 +0200 Subject: [PATCH 107/194] Add utility for computing fractional part --- src/lp_data/Highs.cpp | 5 ++-- src/lp_data/HighsLpUtils.cpp | 3 +- src/mip/HighsLpRelaxation.cpp | 3 +- src/mip/HighsMipSolver.cpp | 3 +- src/mip/HighsMipSolverData.cpp | 15 ++++------ src/mip/HighsSearch.cpp | 3 +- src/mip/HighsTableauSeparator.cpp | 12 ++++---- src/presolve/HPresolve.cpp | 42 +++++++++++----------------- src/presolve/HighsPostsolveStack.cpp | 11 +++----- src/util/HighsUtils.h | 10 +++++++ 10 files changed, 48 insertions(+), 59 deletions(-) diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index 76a4dbc2ac..43cd3094c6 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -3924,10 +3924,9 @@ HighsStatus Highs::callRunPostsolve(const HighsSolution& solution, max_integrality_violation = 0; for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { if (lp.integrality_[iCol] == HighsVarType::kInteger) { - const double value = this->solution_.col_value[iCol]; - double intval = std::floor(value + 0.5); max_integrality_violation = - std::max(fabs(intval - value), max_integrality_violation); + std::max(fractionality(this->solution_.col_value[iCol]), + max_integrality_violation); } } highsLogUser( diff --git a/src/lp_data/HighsLpUtils.cpp b/src/lp_data/HighsLpUtils.cpp index ebe803bab0..465ee64aab 100644 --- a/src/lp_data/HighsLpUtils.cpp +++ b/src/lp_data/HighsLpUtils.cpp @@ -2305,8 +2305,7 @@ void assessColPrimalSolution(const HighsOptions& options, const double primal, } integer_infeasibility = 0; if (type == HighsVarType::kInteger || type == HighsVarType::kSemiInteger) { - double nearest_integer = std::floor(primal + 0.5); - integer_infeasibility = std::fabs(primal - nearest_integer); + integer_infeasibility = fractionality(primal); } if (col_infeasibility > 0 && (type == HighsVarType::kSemiContinuous || type == HighsVarType::kSemiInteger)) { diff --git a/src/mip/HighsLpRelaxation.cpp b/src/mip/HighsLpRelaxation.cpp index 0d50457416..95133d79be 100644 --- a/src/mip/HighsLpRelaxation.cpp +++ b/src/mip/HighsLpRelaxation.cpp @@ -1224,9 +1224,8 @@ HighsLpRelaxation::Status HighsLpRelaxation::resolveLp(HighsDomain* domain) { double val = std::max( std::min(sol.col_value[i], lpsolver.getLp().col_upper_[i]), lpsolver.getLp().col_lower_[i]); - double intval = std::floor(val + 0.5); - if (std::abs(val - intval) > mipsolver.mipdata_->feastol) { + if (fractionality(val) > mipsolver.mipdata_->feastol) { HighsInt col = i; if (roundable && mipsolver.mipdata_->uplocks[col] != 0 && mipsolver.mipdata_->downlocks[col] != 0) diff --git a/src/mip/HighsMipSolver.cpp b/src/mip/HighsMipSolver.cpp index 1cf6e34c51..ba0faed6bc 100644 --- a/src/mip/HighsMipSolver.cpp +++ b/src/mip/HighsMipSolver.cpp @@ -63,9 +63,8 @@ HighsMipSolver::HighsMipSolver(HighsCallback& callback, obj += orig_model_->col_cost_[i] * value; if (orig_model_->integrality_[i] == HighsVarType::kInteger) { - double intval = std::floor(value + 0.5); integrality_violation_ = - std::max(fabs(intval - value), integrality_violation_); + std::max(fractionality(value), integrality_violation_); } const double lower = orig_model_->col_lower_[i]; diff --git a/src/mip/HighsMipSolverData.cpp b/src/mip/HighsMipSolverData.cpp index 181b6adb1e..6a571a0010 100644 --- a/src/mip/HighsMipSolverData.cpp +++ b/src/mip/HighsMipSolverData.cpp @@ -27,7 +27,7 @@ bool HighsMipSolverData::checkSolution( if (solution[i] < mipsolver.model_->col_lower_[i] - feastol) return false; if (solution[i] > mipsolver.model_->col_upper_[i] + feastol) return false; if (mipsolver.variableType(i) == HighsVarType::kInteger && - std::abs(solution[i] - std::floor(solution[i] + 0.5)) > feastol) + fractionality(solution[i]) > feastol) return false; } @@ -57,7 +57,7 @@ bool HighsMipSolverData::trySolution(const std::vector& solution, if (solution[i] < mipsolver.model_->col_lower_[i] - feastol) return false; if (solution[i] > mipsolver.model_->col_upper_[i] + feastol) return false; if (mipsolver.variableType(i) == HighsVarType::kInteger && - std::abs(solution[i] - std::floor(solution[i] + 0.5)) > feastol) + fractionality(solution[i]) > feastol) return false; obj += mipsolver.colCost(i) * solution[i]; @@ -526,9 +526,8 @@ void HighsMipSolverData::runSetup() { if (integral) { if (mipsolver.variableType(ARindex_[j]) == HighsVarType::kContinuous) integral = false; - else { - double intval = std::floor(ARvalue_[j] + 0.5); - if (std::abs(ARvalue_[j] - intval) > epsilon) integral = false; + else if (fractionality(ARvalue_[j]) > epsilon) { + integral = false; } } @@ -597,8 +596,7 @@ void HighsMipSolverData::runSetup() { break; case HighsVarType::kInteger: if (domain.isFixed(i)) { - if (std::abs(domain.col_lower_[i] - - std::floor(domain.col_lower_[i] + 0.5)) > feastol) { + if (fractionality(domain.col_lower_[i]) > feastol) { // integer variable is fixed to a fractional value -> infeasible mipsolver.modelstatus_ = HighsModelStatus::kInfeasible; lower_bound = kHighsInf; @@ -729,8 +727,7 @@ double HighsMipSolverData::transformNewIntegerFeasibleSolution( mipsolver.orig_model_->col_cost_[i] * value; if (mipsolver.orig_model_->integrality_[i] == HighsVarType::kInteger) { - double intval = std::floor(value + 0.5); - double integrality_infeasibility = std::fabs(intval - value); + double integrality_infeasibility = fractionality(value); if (integrality_infeasibility > mipsolver.options_mip_->mip_feasibility_tolerance) { if (debug_report) diff --git a/src/mip/HighsSearch.cpp b/src/mip/HighsSearch.cpp index 4cab953b03..348b9ab468 100644 --- a/src/mip/HighsSearch.cpp +++ b/src/mip/HighsSearch.cpp @@ -48,8 +48,7 @@ double HighsSearch::checkSol(const std::vector& sol, if (!integerfeasible || mipsolver.variableType(i) != HighsVarType::kInteger) continue; - double intval = std::floor(sol[i] + 0.5); - if (std::abs(sol[i] - intval) > mipsolver.mipdata_->feastol) { + if (fractionality(sol[i]) > mipsolver.mipdata_->feastol) { integerfeasible = false; } } diff --git a/src/mip/HighsTableauSeparator.cpp b/src/mip/HighsTableauSeparator.cpp index 5e9ab01bc6..4937f0148b 100644 --- a/src/mip/HighsTableauSeparator.cpp +++ b/src/mip/HighsTableauSeparator.cpp @@ -65,25 +65,23 @@ void HighsTableauSeparator::separateLpSolution(HighsLpRelaxation& lpRelaxation, std::vector fractionalBasisvars; fractionalBasisvars.reserve(numRow); for (HighsInt i = 0; i < numRow; ++i) { - double fractionality; + double my_fractionality; if (basisinds[i] >= numCol) { HighsInt row = basisinds[i] - numCol; if (!lpRelaxation.isRowIntegral(row)) continue; - double solval = lpSolution.row_value[row]; - fractionality = std::fabs(std::round(solval) - solval); + my_fractionality = fractionality(lpSolution.row_value[row]); } else { HighsInt col = basisinds[i]; if (mip.variableType(col) == HighsVarType::kContinuous) continue; - double solval = lpSolution.col_value[col]; - fractionality = std::fabs(std::round(solval) - solval); + my_fractionality = fractionality(lpSolution.col_value[col]); } - if (fractionality < 1000 * mip.mipdata_->feastol) continue; + if (my_fractionality < 1000 * mip.mipdata_->feastol) continue; - fractionalBasisvars.emplace_back(i, fractionality); + fractionalBasisvars.emplace_back(i, my_fractionality); } if (fractionalBasisvars.empty()) return; diff --git a/src/presolve/HPresolve.cpp b/src/presolve/HPresolve.cpp index 9229c8c264..10e17ab95f 100644 --- a/src/presolve/HPresolve.cpp +++ b/src/presolve/HPresolve.cpp @@ -173,8 +173,7 @@ bool HPresolve::okSetInput(HighsMipSolver& mipsolver, bool HPresolve::rowCoefficientsIntegral(HighsInt row, double scale) const { for (const HighsSliceNonzero& nz : getRowVector(row)) { - double val = nz.value() * scale; - if (std::abs(val - std::round(val)) > options->small_matrix_value) + if (fractionality(nz.value() * scale) > options->small_matrix_value) return false; } @@ -252,9 +251,8 @@ bool HPresolve::isImpliedIntegral(HighsInt col) { double scale = 1.0 / nz.value(); if (!rowCoefficientsIntegral(nz.index(), scale)) continue; - double rhs = model->row_lower_[nz.index()] * scale; - - if (std::abs(rhs - std::round(rhs)) > primal_feastol) { + if (fractionality(model->row_lower_[nz.index()] * scale) > + primal_feastol) { // todo infeasible } @@ -323,9 +321,9 @@ bool HPresolve::isImpliedInteger(HighsInt col) { // if there is an equation the dual detection does not need to be tried runDualDetection = false; double scale = 1.0 / nz.value(); - double rhs = model->row_lower_[nz.index()] * scale; - if (std::abs(rhs - std::round(rhs)) > primal_feastol) { + if (fractionality(model->row_lower_[nz.index()] * scale) > + primal_feastol) { continue; } @@ -338,24 +336,20 @@ bool HPresolve::isImpliedInteger(HighsInt col) { if (!runDualDetection) return false; if ((model->col_lower_[col] != -kHighsInf && - std::abs(std::round(model->col_lower_[col]) - model->col_lower_[col]) > - options->small_matrix_value) || + fractionality(model->col_lower_[col]) > options->small_matrix_value) || (model->col_upper_[col] != -kHighsInf && - std::abs(std::round(model->col_upper_[col]) - model->col_upper_[col]) > - options->small_matrix_value)) + fractionality(model->col_upper_[col]) > options->small_matrix_value)) return false; for (const HighsSliceNonzero& nz : getColumnVector(col)) { double scale = 1.0 / nz.value(); - if (model->row_upper_[nz.index()] != kHighsInf) { - double rhs = model->row_upper_[nz.index()]; - if (std::abs(rhs - std::round(rhs)) > primal_feastol) return false; - } + if (model->row_upper_[nz.index()] != kHighsInf && + fractionality(model->row_upper_[nz.index()]) > primal_feastol) + return false; - if (model->row_lower_[nz.index()] != -kHighsInf) { - double rhs = model->row_lower_[nz.index()]; - if (std::abs(rhs - std::round(rhs)) > primal_feastol) return false; - } + if (model->row_lower_[nz.index()] != -kHighsInf && + fractionality(model->row_lower_[nz.index()]) > primal_feastol) + return false; if (!rowCoefficientsIntegral(nz.index(), scale)) return false; } @@ -3305,7 +3299,7 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, if (intScale != 0.0 && intScale <= 1e3) { double rhs = rowUpper * intScale; - if (std::abs(rhs - std::round(rhs)) > primal_feastol) + if (fractionality(rhs) > primal_feastol) return Result::kPrimalInfeasible; rhs = std::round(rhs); @@ -3341,8 +3335,7 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, HighsInt x1Pos = rowpositions[x1Cand]; HighsInt x1 = Acol[x1Pos]; double rhs2 = rhs / static_cast(d); - if (std::abs(std::round(rhs2) - rhs2) <= - mipsolver->mipdata_->epsilon) { + if (fractionality(rhs2) <= mipsolver->mipdata_->epsilon) { // the right hand side is integral, so we can substitute // x1 = d * z @@ -5723,10 +5716,9 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( } double scaleCand = colMax[duplicateCol].first / colMax[col].first; - colScale = std::round(scaleCand); - assert(std::abs(colScale) >= 1.0); - if (std::abs(colScale - scaleCand) > options->small_matrix_value) + if (fractionality(scaleCand, &colScale) > options->small_matrix_value) continue; + assert(std::abs(colScale) >= 1.0); // if the scale is larger than 1, duplicate column cannot compensate for // all values of scaled col due to integrality as the scaled column diff --git a/src/presolve/HighsPostsolveStack.cpp b/src/presolve/HighsPostsolveStack.cpp index bd73f128e4..bec3e4945f 100644 --- a/src/presolve/HighsPostsolveStack.cpp +++ b/src/presolve/HighsPostsolveStack.cpp @@ -15,6 +15,7 @@ #include "lp_data/HConst.h" #include "lp_data/HighsOptions.h" #include "util/HighsCDouble.h" +#include "util/HighsUtils.h" namespace presolve { @@ -728,8 +729,7 @@ void HighsPostsolveStack::DuplicateColumn::undo(const HighsOptions& options, } else if (duplicateColIntegral) { // Doesn't set basis.col_status[duplicateCol], so assume no basis assert(!basis.valid); - double roundVal = std::round(solution.col_value[duplicateCol]); - if (std::abs(roundVal - solution.col_value[duplicateCol]) > + if (fractionality(solution.col_value[duplicateCol]) > options.mip_feasibility_tolerance) { solution.col_value[duplicateCol] = std::floor(solution.col_value[duplicateCol]); @@ -927,9 +927,7 @@ bool HighsPostsolveStack::DuplicateColumn::okMerge( if (x_int) { if (y_int) { // Scale must be integer and not exceed (x_u-x_l)+1 in magnitude - double int_scale = std::floor(scale + 0.5); - bool scale_is_int = std::fabs(int_scale - scale) <= tolerance; - if (!scale_is_int) { + if (fractionality(scale) > tolerance) { if (debug_report) printf( "%sDuplicateColumn::checkMerge: scale must be integer, but is " @@ -1012,8 +1010,7 @@ void HighsPostsolveStack::DuplicateColumn::undoFix( //============================================================================================= auto isInteger = [&](const double v) { - double int_v = std::floor(v + 0.5); - return std::fabs(int_v - v) <= mip_feasibility_tolerance; + return (fractionality(v) <= mip_feasibility_tolerance); }; auto isFeasible = [&](const double l, const double v, const double u) { diff --git a/src/util/HighsUtils.h b/src/util/HighsUtils.h index 27791ff2be..447219a8f9 100644 --- a/src/util/HighsUtils.h +++ b/src/util/HighsUtils.h @@ -207,4 +207,14 @@ void highsAssert(const bool assert_condition, const std::string message = ""); // If pause_condition is true, then keyboard input is required. Allows // breakpoints in VScode where optimization might prevent them. bool highsPause(const bool pause_condition, const std::string message = ""); + +// Utility for computing fractional part +template +inline T fractionality(T input, T* intval = nullptr) { + using std::abs; + using std::round; + T val = round(input); + if (intval != nullptr) *intval = val; + return abs(input - val); +} #endif // UTIL_HIGHSUTILS_H_ From 54b575ea9b5716f451a64269179186c83836f982 Mon Sep 17 00:00:00 2001 From: jajhall Date: Tue, 10 Sep 2024 15:23:40 +0100 Subject: [PATCH 108/194] Added src/solvers.md --- docs/make.jl | 4 +++- docs/src/index.md | 21 +++++---------------- docs/src/solvers.md | 46 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 17 deletions(-) create mode 100644 docs/src/solvers.md diff --git a/docs/make.jl b/docs/make.jl index 0c65c9d2b4..c1f6bc70b9 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -2,8 +2,9 @@ * * * This file is part of the HiGHS linear optimization suite * * * -* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, * +* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, * * Leona Gottwald and Michael Feldmeier * +* * * Available as open-source under the MIT License * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *=# @@ -90,6 +91,7 @@ Documenter.makedocs( "options/definitions.md" ], "Parallel" => "parallel.md", + "Solvers" => "solvers.md", "Terminology" => "terminology.md", ], ) diff --git a/docs/src/index.md b/docs/src/index.md index 1af6ab7ee6..54af09ff19 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -54,23 +54,12 @@ calls. These can be studied via the [C++ header file](https://github.com/ERGO-Co The C interface cannot make use of the C++ structures and enums, and its methods are documented [explicitly](@ref c-api). -## Solution algorithms - -For LPs, HiGHS has implementations of both the revised simplex and interior -point methods. MIPs are solved by branch-and-cut, and QPs by active set. - -For LP, the novel features of the dual simplex solver are described in -_Parallelizing the dual revised simplex method_, Q. Huangfu and -J. A. J. Hall, Mathematical Programming Computation, 10 (1), 119-142, -2018 [DOI: -10.1007/s12532-017-0130-5](https://link.springer.com/article/10.1007/s12532-017-0130-5). For -the interior point solver, the reference is _Implementation of an -interior point method with basis preconditioning_, Mathematical -Programming Computation, 12, 603-635, 2020. [DOI: -10.1007/s12532-020-00181-8](https://link.springer.com/article/10.1007/s12532-020-00181-8). There -are no specific references to the techniques used in the MIP or QP -solvers. +## Solvers +For LPs, HiGHS has implementations of the revised simplex method, +interior point method, and PDLP first order method. MIPs are solved by +branch-and-cut, and QPs by active set. More information on the HiGHS +solvers is [available](@ref solvers). ## Citing HiGHS diff --git a/docs/src/solvers.md b/docs/src/solvers.md new file mode 100644 index 0000000000..1aff0a1555 --- /dev/null +++ b/docs/src/solvers.md @@ -0,0 +1,46 @@ +# [Solvers](@id solvers) + +## LP + +HiGHS has implementations of the three main solution techniques for +LP. HiGHS will choose the most appropriate technique for a given +problem, but this can be over-ridden by setting the option +[__solver__](solver). + +### Simplex + +HiGHS has efficient implementations of both the primal and dual +simplex methods, although the dual simplex solver is likely to be +faster and is more robust, so is used by default. The novel features +of the dual simplex solver are described in + +_Parallelizing the dual revised simplex method_, Q. Huangfu and +J. A. J. Hall, Mathematical Programming Computation, 10 (1), 119-142, +2018 [DOI: +10.1007/s12532-017-0130-5](https://link.springer.com/article/10.1007/s12532-017-0130-5). + +The option [__simplex\_strategy__](simplex\_strategy) determines whether the primal solver or one of hte parallel solvers is to be used. + +### Interior point + +HiGHS has one interior point solver based on the preconditioned conjugate gradient method, as discussed in + +_Implementation of an interior point method with basis +preconditioning_, Mathematical Programming Computation, 12, 603-635, +2020. [DOI: +10.1007/s12532-020-00181-8](https://link.springer.com/article/10.1007/s12532-020-00181-8). + +This solver is serial. An interior point solver based on direct factorization is being developed. + +### MIP + +The HiGHS MIP solver uses established branch-and-cut techniques + +### QP + +The HiGHS solver for convex QP problems uses an established primal +active set method. The new interior point solver will also be able to +solve convex QP problems. + + + From b506d7aa054402a70ffcd8b2b246dad6b7ce024a Mon Sep 17 00:00:00 2001 From: jajhall Date: Tue, 10 Sep 2024 15:34:29 +0100 Subject: [PATCH 109/194] Fixed internal links --- docs/src/executable.md | 2 +- docs/src/index.md | 2 +- docs/src/options/definitions.md | 4 ++-- docs/src/options/intro.md | 4 ++-- docs/src/parallel.md | 2 +- docs/src/solvers.md | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/src/executable.md b/docs/src/executable.md index d9f1777555..b82003127c 100644 --- a/docs/src/executable.md +++ b/docs/src/executable.md @@ -1,4 +1,4 @@ -# Executable +# [Executable](@id executable) For convenience, the executable is assumed to be `bin/highs`. diff --git a/docs/src/index.md b/docs/src/index.md index 54af09ff19..2e0e65a03b 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -39,7 +39,7 @@ Get started by following [Install HiGHS](@ref). ## Overview -The standalone [Executable](@ref) allows models to be solved from +The standalone [executable](@ref executable) allows models to be solved from [MPS](https://en.wikipedia.org/wiki/MPS_(format)) or (CPLEX) [LP](https://web.mit.edu/lpsolve/doc/CPLEX-format.htm) files, with full control of the HiGHS run-time options, and the solution can be written to files in human diff --git a/docs/src/options/definitions.md b/docs/src/options/definitions.md index 2505c4d3c3..31a0eb128d 100644 --- a/docs/src/options/definitions.md +++ b/docs/src/options/definitions.md @@ -5,7 +5,7 @@ - Type: string - Default: "choose" -## solver +## [solver](@id option-solver) - Solver option: "simplex", "choose", "ipm" or "pdlp". If "simplex"/"ipm"/"pdlp" is chosen then, for a MIP (QP) the integrality constraint (quadratic term) will be ignored - Type: string - Default: "choose" @@ -109,7 +109,7 @@ - Range: {-2147483647, 2147483647} - Default: 0 -## simplex\_strategy +## [simplex\_strategy](@id option-simplex_strategy) - Strategy for simplex solver 0 => Choose; 1 => Dual (serial); 2 => Dual (PAMI); 3 => Dual (SIP); 4 => Primal - Type: integer - Range: {0, 4} diff --git a/docs/src/options/intro.md b/docs/src/options/intro.md index ff33cae234..81f0e64246 100644 --- a/docs/src/options/intro.md +++ b/docs/src/options/intro.md @@ -3,12 +3,12 @@ The options that control HiGHS are of four types: `boolean`, `integer`, `double` and `string`. Their values can be specified: - * via the command line when running the [Executable](@ref) + * via the command line when running the [Executable](@ref executable) * via method calls when running HiGHS in an application. ## Options file -When running the [Executable](@ref) via the command line, some options values +When running the [Executable](@ref executable) via the command line, some options values can be set explicitly in the command, and all options can be set by means of an options file. diff --git a/docs/src/parallel.md b/docs/src/parallel.md index 3a857629e3..016834ab98 100644 --- a/docs/src/parallel.md +++ b/docs/src/parallel.md @@ -20,7 +20,7 @@ has a variant allowing concurrent processing. This variant is used when the [parallel](@ref) option is set "on", by specifying `--parallel` when running the -[executable](@ref Executable) via +[executable](@ref executable) via the command line, or by setting it via a library call in an application. diff --git a/docs/src/solvers.md b/docs/src/solvers.md index 1aff0a1555..15a49f9225 100644 --- a/docs/src/solvers.md +++ b/docs/src/solvers.md @@ -5,7 +5,7 @@ HiGHS has implementations of the three main solution techniques for LP. HiGHS will choose the most appropriate technique for a given problem, but this can be over-ridden by setting the option -[__solver__](solver). +[__solver__](@ref option-solver). ### Simplex @@ -19,7 +19,7 @@ J. A. J. Hall, Mathematical Programming Computation, 10 (1), 119-142, 2018 [DOI: 10.1007/s12532-017-0130-5](https://link.springer.com/article/10.1007/s12532-017-0130-5). -The option [__simplex\_strategy__](simplex\_strategy) determines whether the primal solver or one of hte parallel solvers is to be used. +The option [__simplex\_strategy__](@ref option-simplex_strategy) determines whether the primal solver or one of hte parallel solvers is to be used. ### Interior point From cdf2cf4d95737f48a692322a4184f8306ee847b1 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Tue, 10 Sep 2024 19:05:06 +0100 Subject: [PATCH 110/194] Added PDLP! --- docs/src/solvers.md | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/docs/src/solvers.md b/docs/src/solvers.md index 15a49f9225..0b881c14c0 100644 --- a/docs/src/solvers.md +++ b/docs/src/solvers.md @@ -7,7 +7,7 @@ LP. HiGHS will choose the most appropriate technique for a given problem, but this can be over-ridden by setting the option [__solver__](@ref option-solver). -### Simplex +#### Simplex HiGHS has efficient implementations of both the primal and dual simplex methods, although the dual simplex solver is likely to be @@ -19,24 +19,38 @@ J. A. J. Hall, Mathematical Programming Computation, 10 (1), 119-142, 2018 [DOI: 10.1007/s12532-017-0130-5](https://link.springer.com/article/10.1007/s12532-017-0130-5). -The option [__simplex\_strategy__](@ref option-simplex_strategy) determines whether the primal solver or one of hte parallel solvers is to be used. +* Setting the option [__solver__](@ref option-solver) to "simplex" forces the simplex solver to be used +* The option [__simplex\_strategy__](@ref option-simplex_strategy) +determines whether the primal solver or one of the parallel solvers is +to be used. -### Interior point +#### Interior point -HiGHS has one interior point solver based on the preconditioned conjugate gradient method, as discussed in +HiGHS has one interior point (IPM) solver based on the preconditioned conjugate gradient method, as discussed in _Implementation of an interior point method with basis -preconditioning_, Mathematical Programming Computation, 12, 603-635, -2020. [DOI: +preconditioning_, Mathematical Programming Computation, 12, 603-635, 2020. [DOI: 10.1007/s12532-020-00181-8](https://link.springer.com/article/10.1007/s12532-020-00181-8). This solver is serial. An interior point solver based on direct factorization is being developed. -### MIP +Setting the option [__solver__](@ref option-solver) to "ipm" forces the IPM solver to be used + +#### Primal-dual hybrid gradient method + +HiGHS includes the [cuPDLP-C](https://github.com/COPT-Public/cuPDLP-C) +primal-dual hybrid gradient method for LP (PDLP). Currently this only +runs on CPU, so it is unlikely to be competitive with the HiGHS +interior point or simplex solvers. Enabling HiGHS to run PDLP on a GPU +is work in progress. + +Setting the option [__solver__](@ref option-solver) to "pdlp" forces the PDLP solver to be used + +## MIP The HiGHS MIP solver uses established branch-and-cut techniques -### QP +## QP The HiGHS solver for convex QP problems uses an established primal active set method. The new interior point solver will also be able to From 450c026b18c3cca60d05311aa6a9c42c70037361 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 11 Sep 2024 09:25:47 +0200 Subject: [PATCH 111/194] Tiny simplification --- src/mip/HighsMipSolverData.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/mip/HighsMipSolverData.cpp b/src/mip/HighsMipSolverData.cpp index 6a571a0010..884f7a4713 100644 --- a/src/mip/HighsMipSolverData.cpp +++ b/src/mip/HighsMipSolverData.cpp @@ -523,13 +523,10 @@ void HighsMipSolverData::runSetup() { HighsInt end = ARstart_[i + 1]; bool integral = true; for (HighsInt j = start; j != end; ++j) { - if (integral) { - if (mipsolver.variableType(ARindex_[j]) == HighsVarType::kContinuous) - integral = false; - else if (fractionality(ARvalue_[j]) > epsilon) { - integral = false; - } - } + integral = + integral && + mipsolver.variableType(ARindex_[j]) != HighsVarType::kContinuous && + fractionality(ARvalue_[j]) <= epsilon; maxabsval = std::max(maxabsval, std::abs(ARvalue_[j])); } From 5aba249d9e33115656da585131ed974720174aa5 Mon Sep 17 00:00:00 2001 From: jajhall Date: Wed, 11 Sep 2024 16:37:36 +0100 Subject: [PATCH 112/194] Methods receiving matrix data where only small values are explicit zeros (so removed internally) are now silent and return HighsStatus::kOk (since internal matrix is exact) --- FEATURES.md | 7 +++++++ check/TestLpModification.cpp | 17 +++++++++++++++++ check/TestLpValidation.cpp | 2 +- check/TestRays.cpp | 2 ++ src/lp_data/HighsSolve.cpp | 2 +- src/test/DevKkt.cpp | 4 ++-- src/util/HighsMatrixUtils.cpp | 19 ++++++++++++------- 7 files changed, 42 insertions(+), 11 deletions(-) diff --git a/FEATURES.md b/FEATURES.md index f0a52fcf7f..170a933885 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -16,6 +16,13 @@ Added `Highs::feasibilityRelaxation` to solve the problem of minimizing a (possi Added Python utility `examples/plot_highs_log.py` (due to @Thell) to visualise progress of the MIP solver. +Added minimal documentation of solvers and how simplex variants can be run + +Methods receiving matrix data where only small values are explicit zeros (so removed internally) are now silent and return HighsStatus::kOk (since internal matrix is exact) + + + + diff --git a/check/TestLpModification.cpp b/check/TestLpModification.cpp index b34cf18cae..ea55d16264 100644 --- a/check/TestLpModification.cpp +++ b/check/TestLpModification.cpp @@ -1937,3 +1937,20 @@ TEST_CASE("modify-empty-model", "[highs_data]") { REQUIRE(highs.changeColBounds(0, 1, 1) == HighsStatus::kError); REQUIRE(highs.changeRowBounds(0, 1, 1) == HighsStatus::kError); } + +TEST_CASE("zero-matrix-entries", "[highs_data]") { + Highs highs; + highs.setOptionValue("output_flag", dev_run); + HighsLp lp; + lp.num_col_ = 2; + lp.num_row_ = 2; + lp.col_cost_ = {0, 0}; + lp.col_lower_ = {0, 0}; + lp.col_upper_ = {1, 1}; + lp.row_lower_ = {-kHighsInf, -kHighsInf}; + lp.row_upper_ = {5, 8}; + lp.a_matrix_.start_ = {0, 2, 4}; + lp.a_matrix_.index_ = {0, 1, 0, 1}; + lp.a_matrix_.value_ = {1, 0, 0, 1}; + REQUIRE(highs.passModel(lp) == HighsStatus::kOk); +} diff --git a/check/TestLpValidation.cpp b/check/TestLpValidation.cpp index bb0f71c1db..473fdeb681 100644 --- a/check/TestLpValidation.cpp +++ b/check/TestLpValidation.cpp @@ -94,7 +94,7 @@ TEST_CASE("LP-dimension-validation", "[highs_data]") { // Yields duplicate index, but values are still zero, so both are // discarded and a warning is returned lp.a_matrix_.index_[0] = 0; - REQUIRE(highs.passModel(lp) == HighsStatus::kWarning); + REQUIRE(highs.passModel(lp) == HighsStatus::kOk); if (dev_run) printf("Give nonzero a_matrix_.value_[0] and a_matrix_.value_[1]\n"); diff --git a/check/TestRays.cpp b/check/TestRays.cpp index 0b9f6ecfbf..5a42d19bc5 100644 --- a/check/TestRays.cpp +++ b/check/TestRays.cpp @@ -303,6 +303,8 @@ void testUnboundedMpsLp(const std::string model, // Test dual ray for unbounded LP model_file = std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; require_model_status = HighsModelStatus::kUnbounded; + // gas11 contains small nonzero matrix entries, so readModel yield + // HighsStatus::kWarning require_status = model == "gas11" ? HighsStatus::kWarning : HighsStatus::kOk; REQUIRE(highs.readModel(model_file) == require_status); REQUIRE(highs.changeObjectiveSense(sense) == HighsStatus::kOk); diff --git a/src/lp_data/HighsSolve.cpp b/src/lp_data/HighsSolve.cpp index d839bfb88d..b1a33476fb 100644 --- a/src/lp_data/HighsSolve.cpp +++ b/src/lp_data/HighsSolve.cpp @@ -127,7 +127,7 @@ HighsStatus solveLp(HighsLpSolverObject& solver_object, const string message) { return HighsStatus::kError; } } // options.run_crossover == kHighsOnString - } // unwelcome_ipx_status + } // unwelcome_ipx_status } else { // PDLP has been used, so check whether claim of optimality // satisfies the HiGHS criteria diff --git a/src/test/DevKkt.cpp b/src/test/DevKkt.cpp index de393f7bf1..47941d9fd6 100644 --- a/src/test/DevKkt.cpp +++ b/src/test/DevKkt.cpp @@ -261,8 +261,8 @@ void checkComplementarySlackness(const State& state, if (fabs(state.colDual[i]) > tol && fabs(state.colValue[i] - state.colUpper[i]) > tol) { if (dev_print) - std::cout << "Comp. slackness fail: " - << "l[" << i << "]=" << state.colLower[i] << ", x[" << i + std::cout << "Comp. slackness fail: " << "l[" << i + << "]=" << state.colLower[i] << ", x[" << i << "]=" << state.colValue[i] << ", z[" << i << "]=" << state.colDual[i] << std::endl; infeas = fabs(state.colDual[i]); diff --git a/src/util/HighsMatrixUtils.cpp b/src/util/HighsMatrixUtils.cpp index 42f4375063..b55f4df79a 100644 --- a/src/util/HighsMatrixUtils.cpp +++ b/src/util/HighsMatrixUtils.cpp @@ -234,13 +234,18 @@ HighsStatus assessMatrix( error_found = true; assert(num_small_values == 0); } - highsLogUser(log_options, HighsLogType::kWarning, - "%s matrix packed vector contains %" HIGHSINT_FORMAT - " |values| in [%g, %g] " - "less than or equal to %g: ignored\n", - matrix_name.c_str(), num_small_values, min_small_value, - max_small_value, small_matrix_value); - warning_found = true; + // If explicit zeros are ignored, then no model information is + // lost, so only report and return a warning if small nonzeros are + // ignored + if (max_small_value > 0) { + highsLogUser(log_options, HighsLogType::kWarning, + "%s matrix packed vector contains %" HIGHSINT_FORMAT + " |values| in [%g, %g] " + "less than or equal to %g: ignored\n", + matrix_name.c_str(), num_small_values, min_small_value, + max_small_value, small_matrix_value); + warning_found = true; + } } matrix_start[num_vec] = num_new_nz; HighsStatus return_status = HighsStatus::kOk; From ad72151f6471da3ce6e9caca063ec6ee37264848 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 11 Sep 2024 23:03:59 +0100 Subject: [PATCH 113/194] Nullified changes due to clang-format version --- src/lp_data/HighsSolve.cpp | 6 ++++-- src/test/DevKkt.cpp | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/lp_data/HighsSolve.cpp b/src/lp_data/HighsSolve.cpp index b1a33476fb..a276b58235 100644 --- a/src/lp_data/HighsSolve.cpp +++ b/src/lp_data/HighsSolve.cpp @@ -126,8 +126,10 @@ HighsStatus solveLp(HighsLpSolverObject& solver_object, const string message) { "Inconsistent solution returned from solver\n"); return HighsStatus::kError; } - } // options.run_crossover == kHighsOnString - } // unwelcome_ipx_status + } + // options.run_crossover == kHighsOnString + } + // unwelcome_ipx_status } else { // PDLP has been used, so check whether claim of optimality // satisfies the HiGHS criteria diff --git a/src/test/DevKkt.cpp b/src/test/DevKkt.cpp index 47941d9fd6..41f6db6848 100644 --- a/src/test/DevKkt.cpp +++ b/src/test/DevKkt.cpp @@ -261,10 +261,12 @@ void checkComplementarySlackness(const State& state, if (fabs(state.colDual[i]) > tol && fabs(state.colValue[i] - state.colUpper[i]) > tol) { if (dev_print) - std::cout << "Comp. slackness fail: " << "l[" << i - << "]=" << state.colLower[i] << ", x[" << i + // clang-format off + std::cout << "Comp. slackness fail: " + << "l[" << i << "]=" << state.colLower[i] << ", x[" << i << "]=" << state.colValue[i] << ", z[" << i << "]=" << state.colDual[i] << std::endl; + // clang-format on infeas = fabs(state.colDual[i]); } } From 1556a6ed2161ef4d98b6f9b7a6650f1fc300fd3c Mon Sep 17 00:00:00 2001 From: jajhall Date: Wed, 11 Sep 2024 23:17:43 +0100 Subject: [PATCH 114/194] Added comment.mps --- check/instances/comment.mps | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 check/instances/comment.mps diff --git a/check/instances/comment.mps b/check/instances/comment.mps new file mode 100644 index 0000000000..96d7c80985 --- /dev/null +++ b/check/instances/comment.mps @@ -0,0 +1,23 @@ +* Optimal objective is -4 +NAME Comment +* Comment +ROWS + N COST $Comment + G R0 + G R1 *Comment + G R2 $Comment +COLUMNS + C0 R0 1.0 COST -1.0 $Comment + C0 R1 -1.0 $Comment + C0 R2 -1.0 $Comment + C1 R0 1.0 COST -2.0 *Comment + C1 R1 -1.0 *Comment + C1 R2 -1.0 *Comment +RHS + DEMANDS R0 1.0 R1 -2.0 $Comment + DEMANDS R2 -2.0 $Comment +RANGES + test R0 1 $Comment +BOUNDS + UP SERVINGS C0 1.0 $Comment +ENDATA From 1ec433b3caee9dd65fd4534eff88a067c0c0e8c4 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 12 Sep 2024 09:03:44 +0100 Subject: [PATCH 115/194] Introduced HMpsFF::getMpsLine, eliminating mpsCommentTrim --- src/io/HMpsFF.cpp | 25 ++++++++++++++++++++----- src/io/HMpsFF.h | 4 ++++ src/util/stringutil.cpp | 9 --------- src/util/stringutil.h | 3 --- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/io/HMpsFF.cpp b/src/io/HMpsFF.cpp index 823109a6e5..e7bd94e1d9 100644 --- a/src/io/HMpsFF.cpp +++ b/src/io/HMpsFF.cpp @@ -210,6 +210,22 @@ HighsInt HMpsFF::fillHessian(const HighsLogOptions& log_options) { return 0; } +bool HMpsFF::getMpsLine(std::istream& file, std::string& strline, bool& skip) { + skip = false; + if (!getline(file, strline)) return false; + if (is_empty(strline) || strline[0] == '*') { + skip = true; + } else { + // Remove any trailing comment + const size_t p = strline.find_first_of(mps_comment_chars); + printf("In rTrimAt, First of \"%s\" in \"%s\" is %zu\n", mps_comment_chars.c_str(), strline.c_str(), p); + // If a comment character has been found, then erase from it to + // the end of the line + if (p <= strline.length()) strline.erase(p); + } + return true; +} + FreeFormatParserReturnCode HMpsFF::parse(const HighsLogOptions& log_options, const std::string& filename) { HMpsFF::Parsekey keyword = HMpsFF::Parsekey::kNone; @@ -525,6 +541,7 @@ HMpsFF::Parsekey HMpsFF::parseObjsense(const HighsLogOptions& log_options, HMpsFF::Parsekey HMpsFF::parseRows(const HighsLogOptions& log_options, std::istream& file) { std::string strline, word; + bool skip; bool hasobj = false; // Assign a default objective name objective_name = "Objective"; @@ -532,11 +549,9 @@ HMpsFF::Parsekey HMpsFF::parseRows(const HighsLogOptions& log_options, assert(num_row == 0); assert(row_lower.size() == 0); assert(row_upper.size() == 0); - while (getline(file, strline)) { - if (is_empty(strline) || strline[0] == '*') continue; - printf("HMpsFF::parseRows before rtrim: strline = \"%s\"\n", strline.c_str()); - mpsCommentTrim(strline); - printf("HMpsFF::parseRows after rtrim: strline = \"%s\"\n", strline.c_str()); + while (getMpsLine(file, strline, skip)) { + if (skip) continue; + printf("HMpsFF::parseRows: strline = \"%s\"\n", strline.c_str()); double current = getWallTime(); if (time_limit > 0 && current - start_time > time_limit) return HMpsFF::Parsekey::kTimeout; diff --git a/src/io/HMpsFF.h b/src/io/HMpsFF.h index 6c17e0c7ef..cd5dc5c22b 100644 --- a/src/io/HMpsFF.h +++ b/src/io/HMpsFF.h @@ -38,6 +38,8 @@ using Triplet = std::tuple; +const std::string mps_comment_chars = "*$"; + enum class FreeFormatParserReturnCode { kSuccess, kParserError, @@ -189,6 +191,8 @@ class HMpsFF { mutable std::string section_args; + bool getMpsLine(std::istream& file, std::string& strline, bool& skip); + FreeFormatParserReturnCode parse(const HighsLogOptions& log_options, const std::string& filename); // Checks first word of strline and wraps it by it_begin and it_end diff --git a/src/util/stringutil.cpp b/src/util/stringutil.cpp index 5a4fef80a5..5b0de58e73 100644 --- a/src/util/stringutil.cpp +++ b/src/util/stringutil.cpp @@ -126,12 +126,3 @@ std::string first_word(std::string& str, size_t start) { assert(next_word_start != std::string::npos); return str.substr(next_word_start, next_word_end - next_word_start); } - -std::string& mpsCommentTrim(std::string& str) { - const size_t p = str.find_first_of(mps_comment_chars); - printf("In rTrimAt, First of \"%s\" in \"%s\" is %zu\n", mps_comment_chars.c_str(), str.c_str(), p); - if (p > str.length()) return str; - str.erase(p); - return str; -} - diff --git a/src/util/stringutil.h b/src/util/stringutil.h index 95b71e17e1..3c0a9c819d 100644 --- a/src/util/stringutil.h +++ b/src/util/stringutil.h @@ -28,7 +28,6 @@ void strTrim(char* str); void tolower(std::string& str); const std::string default_non_chars = "\t\n\v\f\r "; -const std::string mps_comment_chars = "*$"; std::string& ltrim(std::string& str, const std::string& chars = default_non_chars); std::string& rtrim(std::string& str, const std::string& chars = default_non_chars); std::string& trim(std::string& str, const std::string& chars = default_non_chars); @@ -42,6 +41,4 @@ bool is_end(std::string& str, size_t end, const std::string& chars = default_non std::string first_word(std::string& str, size_t start); size_t first_word_end(std::string& str, size_t start); -std::string& mpsCommentTrim(std::string& str); - #endif From 49c6aa4b1ff62cc0b6d9aa8ba04f49ef50cb2d70 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 12 Sep 2024 09:34:00 +0100 Subject: [PATCH 116/194] Start replacing use of getline by getMpsLine --- src/io/HMpsFF.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/io/HMpsFF.cpp b/src/io/HMpsFF.cpp index e7bd94e1d9..9aa9bbe952 100644 --- a/src/io/HMpsFF.cpp +++ b/src/io/HMpsFF.cpp @@ -218,10 +218,12 @@ bool HMpsFF::getMpsLine(std::istream& file, std::string& strline, bool& skip) { } else { // Remove any trailing comment const size_t p = strline.find_first_of(mps_comment_chars); - printf("In rTrimAt, First of \"%s\" in \"%s\" is %zu\n", mps_comment_chars.c_str(), strline.c_str(), p); - // If a comment character has been found, then erase from it to - // the end of the line - if (p <= strline.length()) strline.erase(p); + if (p <= strline.length()) { + // A comment character has been found, so erase from it to the end + // of the line and check whether the line is now empty + strline.erase(p); + skip = is_empty(strline); + } } return true; } @@ -541,7 +543,6 @@ HMpsFF::Parsekey HMpsFF::parseObjsense(const HighsLogOptions& log_options, HMpsFF::Parsekey HMpsFF::parseRows(const HighsLogOptions& log_options, std::istream& file) { std::string strline, word; - bool skip; bool hasobj = false; // Assign a default objective name objective_name = "Objective"; @@ -549,9 +550,9 @@ HMpsFF::Parsekey HMpsFF::parseRows(const HighsLogOptions& log_options, assert(num_row == 0); assert(row_lower.size() == 0); assert(row_upper.size() == 0); + bool skip; while (getMpsLine(file, strline, skip)) { if (skip) continue; - printf("HMpsFF::parseRows: strline = \"%s\"\n", strline.c_str()); double current = getWallTime(); if (time_limit > 0 && current - start_time > time_limit) return HMpsFF::Parsekey::kTimeout; From aeb80d5d53ca9cb593ac94efa141ad44cff901c6 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 12 Sep 2024 09:51:53 +0100 Subject: [PATCH 117/194] Now using getMpsLine rather than getline --- check/TestFilereader.cpp | 7 +- src/io/HMpsFF.cpp | 163 ++++++++++++++++----------------------- src/io/HMpsFF.h | 1 - src/util/stringutil.h | 12 ++- 4 files changed, 79 insertions(+), 104 deletions(-) diff --git a/check/TestFilereader.cpp b/check/TestFilereader.cpp index 98c0ecae26..bd6c709c1e 100644 --- a/check/TestFilereader.cpp +++ b/check/TestFilereader.cpp @@ -10,7 +10,7 @@ #include "lp_data/HighsLp.h" #include "lp_data/HighsLpUtils.h" -const bool dev_run = true; +const bool dev_run = false; TEST_CASE("filereader-edge-cases", "[highs_filereader]") { std::string model = ""; @@ -348,9 +348,10 @@ TEST_CASE("filereader-comment", "[highs_filereader]") { // Check that comments - either whole line with * in first column, // or rest of line following */$ are handled correctly const double optimal_objective_value = -4; - std::string model_file = std::string(HIGHS_DIR) + "/check/instances/comment.mps"; + std::string model_file = + std::string(HIGHS_DIR) + "/check/instances/comment.mps"; Highs highs; - highs.setOptionValue("output_flag", dev_run); + // highs.setOptionValue("output_flag", dev_run); REQUIRE(highs.readModel(model_file) == HighsStatus::kOk); REQUIRE(highs.run() == HighsStatus::kOk); double objective_value = highs.getInfo().objective_function_value; diff --git a/src/io/HMpsFF.cpp b/src/io/HMpsFF.cpp index 9aa9bbe952..9f9b3fbb55 100644 --- a/src/io/HMpsFF.cpp +++ b/src/io/HMpsFF.cpp @@ -219,8 +219,8 @@ bool HMpsFF::getMpsLine(std::istream& file, std::string& strline, bool& skip) { // Remove any trailing comment const size_t p = strline.find_first_of(mps_comment_chars); if (p <= strline.length()) { - // A comment character has been found, so erase from it to the end - // of the line and check whether the line is now empty + // A comment character has been found, so erase from it to the end + // of the line and check whether the line is now empty strline.erase(p); skip = is_empty(strline); } @@ -466,7 +466,8 @@ HighsInt HMpsFF::getColIdx(const std::string& colname, const bool add_if_new) { HMpsFF::Parsekey HMpsFF::parseDefault(const HighsLogOptions& log_options, std::istream& file) { std::string strline, word; - if (getline(file, strline)) { + bool skip; + if (getMpsLine(file, strline, skip)) { strline = trim(strline); if (strline.empty()) return HMpsFF::Parsekey::kComment; size_t s, e; @@ -513,8 +514,9 @@ HMpsFF::Parsekey HMpsFF::parseObjsense(const HighsLogOptions& log_options, std::istream& file) { std::string strline, word; - while (getline(file, strline)) { - if (is_empty(strline) || strline[0] == '*') continue; + bool skip; + while (getMpsLine(file, strline, skip)) { + if (skip) continue; size_t start = 0; size_t end = 0; @@ -607,9 +609,7 @@ HMpsFF::Parsekey HMpsFF::parseRows(const HighsLogOptions& log_options, } std::string rowname = first_word(strline, start + 1); - printf("HMpsFF::parseRows rowname = \"%s\"; strline = \"%s\"\n", rowname.c_str(), strline.c_str()); size_t rowname_end = first_word_end(strline, start + 1); - printf("HMpsFF::parseRows rowname_end = %d\n", int(rowname_end)); // Detect if file is in fixed format. if (!is_end(strline, rowname_end)) { @@ -691,22 +691,18 @@ typename HMpsFF::Parsekey HMpsFF::parseCols(const HighsLogOptions& log_options, assert(-1 == rowidx || -2 == rowidx); }; - while (getline(file, strline)) { + bool skip; + while (getMpsLine(file, strline, skip)) { double current = getWallTime(); if (time_limit > 0 && current - start_time > time_limit) return HMpsFF::Parsekey::kTimeout; - if (kAnyFirstNonBlankAsStarImpliesComment) { - trim(strline); - if (strline.size() == 0 || strline[0] == '*') continue; - } else { - if (strline.size() > 0) { - // Just look for comment character in column 1 - if (strline[0] == '*') continue; - } - trim(strline); - if (strline.size() == 0) continue; + if (strline.size() > 0) { + // Just look for comment character in column 1 + if (strline[0] == '*') continue; } + trim(strline); + if (strline.size() == 0) continue; HMpsFF::Parsekey key = checkFirstWord(strline, start, end, word); @@ -998,22 +994,18 @@ HMpsFF::Parsekey HMpsFF::parseRhs(const HighsLogOptions& log_options, has_obj_entry_ = false; bool has_entry = false; - while (getline(file, strline)) { + bool skip; + while (getMpsLine(file, strline, skip)) { double current = getWallTime(); if (time_limit > 0 && current - start_time > time_limit) return HMpsFF::Parsekey::kTimeout; - if (kAnyFirstNonBlankAsStarImpliesComment) { - trim(strline); - if (strline.size() == 0 || strline[0] == '*') continue; - } else { - if (strline.size() > 0) { - // Just look for comment character in column 1 - if (strline[0] == '*') continue; - } - trim(strline); - if (strline.size() == 0) continue; + if (strline.size() > 0) { + // Just look for comment character in column 1 + if (strline[0] == '*') continue; } + trim(strline); + if (strline.size() == 0) continue; size_t begin = 0; size_t end = 0; @@ -1164,22 +1156,18 @@ HMpsFF::Parsekey HMpsFF::parseBounds(const HighsLogOptions& log_options, has_lower.assign(num_col, false); has_upper.assign(num_col, false); - while (getline(file, strline)) { + bool skip; + while (getMpsLine(file, strline, skip)) { double current = getWallTime(); if (time_limit > 0 && current - start_time > time_limit) return HMpsFF::Parsekey::kTimeout; - if (kAnyFirstNonBlankAsStarImpliesComment) { - trim(strline); - if (strline.size() == 0 || strline[0] == '*') continue; - } else { - if (strline.size() > 0) { - // Just look for comment character in column 1 - if (strline[0] == '*') continue; - } - trim(strline); - if (strline.size() == 0) continue; + if (strline.size() > 0) { + // Just look for comment character in column 1 + if (strline[0] == '*') continue; } + trim(strline); + if (strline.size() == 0) continue; size_t begin = 0; size_t end = 0; @@ -1443,22 +1431,18 @@ HMpsFF::Parsekey HMpsFF::parseRanges(const HighsLogOptions& log_options, // Initialise tracking for duplicate entries has_row_entry_.assign(num_row, false); - while (getline(file, strline)) { + bool skip; + while (getMpsLine(file, strline, skip)) { double current = getWallTime(); if (time_limit > 0 && current - start_time > time_limit) return HMpsFF::Parsekey::kTimeout; - if (kAnyFirstNonBlankAsStarImpliesComment) { - trim(strline); - if (strline.size() == 0 || strline[0] == '*') continue; - } else { - if (strline.size() > 0) { - // Just look for comment character in column 1 - if (strline[0] == '*') continue; - } - trim(strline); - if (strline.size() == 0) continue; + if (strline.size() > 0) { + // Just look for comment character in column 1 + if (strline[0] == '*') continue; } + trim(strline); + if (strline.size() == 0) continue; size_t begin, end; std::string word; @@ -1596,21 +1580,18 @@ typename HMpsFF::Parsekey HMpsFF::parseHessian( size_t end_coeff_name; HighsInt colidx, rowidx; - while (getline(file, strline)) { + bool skip; + while (getMpsLine(file, strline, skip)) { double current = getWallTime(); if (time_limit > 0 && current - start_time > time_limit) return HMpsFF::Parsekey::kTimeout; - if (kAnyFirstNonBlankAsStarImpliesComment) { - trim(strline); - if (strline.size() == 0 || strline[0] == '*') continue; - } else { - if (strline.size() > 0) { - // Just look for comment character in column 1 - if (strline[0] == '*') continue; - } - trim(strline); - if (strline.size() == 0) continue; + + if (strline.size() > 0) { + // Just look for comment character in column 1 + if (strline[0] == '*') continue; } + trim(strline); + if (strline.size() == 0) continue; size_t begin = 0; size_t end = 0; @@ -1723,7 +1704,8 @@ typename HMpsFF::Parsekey HMpsFF::parseQuadRows( "Row name \"%s\" in %s section is not defined: ignored\n", rowname.c_str(), section_name.c_str()); // read lines until start of new section - while (getline(file, strline)) { + bool skip; + while (getMpsLine(file, strline, skip)) { size_t begin = 0; size_t end = 0; HMpsFF::Parsekey key = checkFirstWord(strline, begin, end, col_name); @@ -1746,21 +1728,18 @@ typename HMpsFF::Parsekey HMpsFF::parseQuadRows( auto& qentries = (rowidx == -1 ? q_entries : qrows_entries[rowidx]); - while (getline(file, strline)) { + bool skip; + while (getMpsLine(file, strline, skip)) { double current = getWallTime(); if (time_limit > 0 && current - start_time > time_limit) return HMpsFF::Parsekey::kTimeout; - if (kAnyFirstNonBlankAsStarImpliesComment) { - trim(strline); - if (strline.size() == 0 || strline[0] == '*') continue; - } else { - if (strline.size() > 0) { - // Just look for comment character in column 1 - if (strline[0] == '*') continue; - } - trim(strline); - if (strline.size() == 0) continue; + + if (strline.size() > 0) { + // Just look for comment character in column 1 + if (strline[0] == '*') continue; } + trim(strline); + if (strline.size() == 0) continue; size_t begin = 0; size_t end = 0; @@ -1899,22 +1878,18 @@ typename HMpsFF::Parsekey HMpsFF::parseCones(const HighsLogOptions& log_options, // now parse the cone entries: one column per line std::string strline; - while (getline(file, strline)) { + bool skip; + while (getMpsLine(file, strline, skip)) { double current = getWallTime(); if (time_limit > 0 && current - start_time > time_limit) return HMpsFF::Parsekey::kTimeout; - if (kAnyFirstNonBlankAsStarImpliesComment) { - trim(strline); - if (strline.size() == 0 || strline[0] == '*') continue; - } else { - if (strline.size() > 0) { - // Just look for comment character in column 1 - if (strline[0] == '*') continue; - } - trim(strline); - if (strline.size() == 0) continue; + if (strline.size() > 0) { + // Just look for comment character in column 1 + if (strline[0] == '*') continue; } + trim(strline); + if (strline.size() == 0) continue; size_t begin; std::string colname; @@ -1942,22 +1917,18 @@ typename HMpsFF::Parsekey HMpsFF::parseSos(const HighsLogOptions& log_options, const HMpsFF::Parsekey keyword) { std::string strline, word; - while (getline(file, strline)) { + bool skip; + while (getMpsLine(file, strline, skip)) { double current = getWallTime(); if (time_limit > 0 && current - start_time > time_limit) return HMpsFF::Parsekey::kTimeout; - if (kAnyFirstNonBlankAsStarImpliesComment) { - trim(strline); - if (strline.size() == 0 || strline[0] == '*') continue; - } else { - if (strline.size() > 0) { - // Just look for comment character in column 1 - if (strline[0] == '*') continue; - } - trim(strline); - if (strline.size() == 0) continue; + if (strline.size() > 0) { + // Just look for comment character in column 1 + if (strline[0] == '*') continue; } + trim(strline); + if (strline.size() == 0) continue; size_t begin, end; std::string word; diff --git a/src/io/HMpsFF.h b/src/io/HMpsFF.h index cd5dc5c22b..b5c86ae177 100644 --- a/src/io/HMpsFF.h +++ b/src/io/HMpsFF.h @@ -126,7 +126,6 @@ class HMpsFF { HighsInt fillMatrix(const HighsLogOptions& log_options); HighsInt fillHessian(const HighsLogOptions& log_options); - const bool kAnyFirstNonBlankAsStarImpliesComment = false; /// how to treat variables that appear in COLUMNS section first /// assume them to be binary as in the original IBM interpretation /// or integer with default bounds diff --git a/src/util/stringutil.h b/src/util/stringutil.h index 3c0a9c819d..25ac72c0ef 100644 --- a/src/util/stringutil.h +++ b/src/util/stringutil.h @@ -28,13 +28,17 @@ void strTrim(char* str); void tolower(std::string& str); const std::string default_non_chars = "\t\n\v\f\r "; -std::string& ltrim(std::string& str, const std::string& chars = default_non_chars); -std::string& rtrim(std::string& str, const std::string& chars = default_non_chars); -std::string& trim(std::string& str, const std::string& chars = default_non_chars); +std::string& ltrim(std::string& str, + const std::string& chars = default_non_chars); +std::string& rtrim(std::string& str, + const std::string& chars = default_non_chars); +std::string& trim(std::string& str, + const std::string& chars = default_non_chars); bool is_empty(std::string& str, const std::string& chars = default_non_chars); bool is_empty(char c, const std::string& chars = default_non_chars); -bool is_end(std::string& str, size_t end, const std::string& chars = default_non_chars); +bool is_end(std::string& str, size_t end, + const std::string& chars = default_non_chars); // todo: replace with pair of references rather than string ret value to avoid // copy and also using function below. or do it properly with iterators. From cd69aea9efb7339636003b24c5aee905b56bf3ff Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 12 Sep 2024 10:51:41 +0100 Subject: [PATCH 118/194] getMpsLine now trims lines and the checks is_empty --- check/TestFilereader.cpp | 2 +- src/io/HMpsFF.cpp | 116 +++++++++++---------------------------- src/io/HMpsFF.h | 1 + 3 files changed, 33 insertions(+), 86 deletions(-) diff --git a/check/TestFilereader.cpp b/check/TestFilereader.cpp index bd6c709c1e..a1b420d316 100644 --- a/check/TestFilereader.cpp +++ b/check/TestFilereader.cpp @@ -351,7 +351,7 @@ TEST_CASE("filereader-comment", "[highs_filereader]") { std::string model_file = std::string(HIGHS_DIR) + "/check/instances/comment.mps"; Highs highs; - // highs.setOptionValue("output_flag", dev_run); + highs.setOptionValue("output_flag", dev_run); REQUIRE(highs.readModel(model_file) == HighsStatus::kOk); REQUIRE(highs.run() == HighsStatus::kOk); double objective_value = highs.getInfo().objective_function_value; diff --git a/src/io/HMpsFF.cpp b/src/io/HMpsFF.cpp index 9f9b3fbb55..414e1fcbbb 100644 --- a/src/io/HMpsFF.cpp +++ b/src/io/HMpsFF.cpp @@ -210,6 +210,10 @@ HighsInt HMpsFF::fillHessian(const HighsLogOptions& log_options) { return 0; } +bool HMpsFF::timeout() { + return time_limit > 0 && getWallTime() - start_time > time_limit; +} + bool HMpsFF::getMpsLine(std::istream& file, std::string& strline, bool& skip) { skip = false; if (!getline(file, strline)) return false; @@ -223,7 +227,10 @@ bool HMpsFF::getMpsLine(std::istream& file, std::string& strline, bool& skip) { // of the line and check whether the line is now empty strline.erase(p); skip = is_empty(strline); + if (skip) return true; } + strline = trim(strline); + skip = is_empty(strline); } return true; } @@ -468,8 +475,9 @@ HMpsFF::Parsekey HMpsFF::parseDefault(const HighsLogOptions& log_options, std::string strline, word; bool skip; if (getMpsLine(file, strline, skip)) { - strline = trim(strline); - if (strline.empty()) return HMpsFF::Parsekey::kComment; + if (skip) return HMpsFF::Parsekey::kComment; + if (timeout()) return HMpsFF::Parsekey::kTimeout; + size_t s, e; HMpsFF::Parsekey key = checkFirstWord(strline, s, e, word); if (key == HMpsFF::Parsekey::kName) { @@ -517,6 +525,7 @@ HMpsFF::Parsekey HMpsFF::parseObjsense(const HighsLogOptions& log_options, bool skip; while (getMpsLine(file, strline, skip)) { if (skip) continue; + if (timeout()) return HMpsFF::Parsekey::kTimeout; size_t start = 0; size_t end = 0; @@ -555,9 +564,7 @@ HMpsFF::Parsekey HMpsFF::parseRows(const HighsLogOptions& log_options, bool skip; while (getMpsLine(file, strline, skip)) { if (skip) continue; - double current = getWallTime(); - if (time_limit > 0 && current - start_time > time_limit) - return HMpsFF::Parsekey::kTimeout; + if (timeout()) return HMpsFF::Parsekey::kTimeout; bool isobj = false; bool isFreeRow = false; @@ -693,16 +700,8 @@ typename HMpsFF::Parsekey HMpsFF::parseCols(const HighsLogOptions& log_options, bool skip; while (getMpsLine(file, strline, skip)) { - double current = getWallTime(); - if (time_limit > 0 && current - start_time > time_limit) - return HMpsFF::Parsekey::kTimeout; - - if (strline.size() > 0) { - // Just look for comment character in column 1 - if (strline[0] == '*') continue; - } - trim(strline); - if (strline.size() == 0) continue; + if (skip) continue; + if (timeout()) return HMpsFF::Parsekey::kTimeout; HMpsFF::Parsekey key = checkFirstWord(strline, start, end, word); @@ -996,16 +995,8 @@ HMpsFF::Parsekey HMpsFF::parseRhs(const HighsLogOptions& log_options, bool skip; while (getMpsLine(file, strline, skip)) { - double current = getWallTime(); - if (time_limit > 0 && current - start_time > time_limit) - return HMpsFF::Parsekey::kTimeout; - - if (strline.size() > 0) { - // Just look for comment character in column 1 - if (strline[0] == '*') continue; - } - trim(strline); - if (strline.size() == 0) continue; + if (skip) continue; + if (timeout()) return HMpsFF::Parsekey::kTimeout; size_t begin = 0; size_t end = 0; @@ -1158,16 +1149,8 @@ HMpsFF::Parsekey HMpsFF::parseBounds(const HighsLogOptions& log_options, bool skip; while (getMpsLine(file, strline, skip)) { - double current = getWallTime(); - if (time_limit > 0 && current - start_time > time_limit) - return HMpsFF::Parsekey::kTimeout; - - if (strline.size() > 0) { - // Just look for comment character in column 1 - if (strline[0] == '*') continue; - } - trim(strline); - if (strline.size() == 0) continue; + if (skip) continue; + if (timeout()) return HMpsFF::Parsekey::kTimeout; size_t begin = 0; size_t end = 0; @@ -1433,16 +1416,8 @@ HMpsFF::Parsekey HMpsFF::parseRanges(const HighsLogOptions& log_options, bool skip; while (getMpsLine(file, strline, skip)) { - double current = getWallTime(); - if (time_limit > 0 && current - start_time > time_limit) - return HMpsFF::Parsekey::kTimeout; - - if (strline.size() > 0) { - // Just look for comment character in column 1 - if (strline[0] == '*') continue; - } - trim(strline); - if (strline.size() == 0) continue; + if (skip) continue; + if (timeout()) return HMpsFF::Parsekey::kTimeout; size_t begin, end; std::string word; @@ -1582,16 +1557,8 @@ typename HMpsFF::Parsekey HMpsFF::parseHessian( bool skip; while (getMpsLine(file, strline, skip)) { - double current = getWallTime(); - if (time_limit > 0 && current - start_time > time_limit) - return HMpsFF::Parsekey::kTimeout; - - if (strline.size() > 0) { - // Just look for comment character in column 1 - if (strline[0] == '*') continue; - } - trim(strline); - if (strline.size() == 0) continue; + if (skip) continue; + if (timeout()) return HMpsFF::Parsekey::kTimeout; size_t begin = 0; size_t end = 0; @@ -1706,6 +1673,9 @@ typename HMpsFF::Parsekey HMpsFF::parseQuadRows( // read lines until start of new section bool skip; while (getMpsLine(file, strline, skip)) { + if (skip) continue; + if (timeout()) return HMpsFF::Parsekey::kTimeout; + size_t begin = 0; size_t end = 0; HMpsFF::Parsekey key = checkFirstWord(strline, begin, end, col_name); @@ -1730,16 +1700,8 @@ typename HMpsFF::Parsekey HMpsFF::parseQuadRows( bool skip; while (getMpsLine(file, strline, skip)) { - double current = getWallTime(); - if (time_limit > 0 && current - start_time > time_limit) - return HMpsFF::Parsekey::kTimeout; - - if (strline.size() > 0) { - // Just look for comment character in column 1 - if (strline[0] == '*') continue; - } - trim(strline); - if (strline.size() == 0) continue; + if (skip) continue; + if (timeout()) return HMpsFF::Parsekey::kTimeout; size_t begin = 0; size_t end = 0; @@ -1880,16 +1842,8 @@ typename HMpsFF::Parsekey HMpsFF::parseCones(const HighsLogOptions& log_options, std::string strline; bool skip; while (getMpsLine(file, strline, skip)) { - double current = getWallTime(); - if (time_limit > 0 && current - start_time > time_limit) - return HMpsFF::Parsekey::kTimeout; - - if (strline.size() > 0) { - // Just look for comment character in column 1 - if (strline[0] == '*') continue; - } - trim(strline); - if (strline.size() == 0) continue; + if (skip) continue; + if (timeout()) return HMpsFF::Parsekey::kTimeout; size_t begin; std::string colname; @@ -1919,16 +1873,8 @@ typename HMpsFF::Parsekey HMpsFF::parseSos(const HighsLogOptions& log_options, bool skip; while (getMpsLine(file, strline, skip)) { - double current = getWallTime(); - if (time_limit > 0 && current - start_time > time_limit) - return HMpsFF::Parsekey::kTimeout; - - if (strline.size() > 0) { - // Just look for comment character in column 1 - if (strline[0] == '*') continue; - } - trim(strline); - if (strline.size() == 0) continue; + if (skip) continue; + if (timeout()) return HMpsFF::Parsekey::kTimeout; size_t begin, end; std::string word; diff --git a/src/io/HMpsFF.h b/src/io/HMpsFF.h index b5c86ae177..2426273ec2 100644 --- a/src/io/HMpsFF.h +++ b/src/io/HMpsFF.h @@ -190,6 +190,7 @@ class HMpsFF { mutable std::string section_args; + bool timeout(); bool getMpsLine(std::istream& file, std::string& strline, bool& skip); FreeFormatParserReturnCode parse(const HighsLogOptions& log_options, From 53a4fac1281843563d64ffb626f3cba5b4801a48 Mon Sep 17 00:00:00 2001 From: Ivet Galabova Date: Thu, 12 Sep 2024 13:09:01 +0300 Subject: [PATCH 119/194] c cc cpp h hpp --- app/cxxopts.hpp | 2449 +-- check/TestCAPI.c | 1157 +- check/matrix_multiplication.hpp | 23 +- examples/call_highs_from_c.c | 270 +- examples/call_highs_from_c_minimal.c | 266 +- examples/call_highs_from_cpp.cpp | 39 +- extern/catch.hpp | 27829 +++++++++++++------------ src/highs_bindings.cpp | 150 +- 8 files changed, 16326 insertions(+), 15857 deletions(-) diff --git a/app/cxxopts.hpp b/app/cxxopts.hpp index 080a7c0391..350e2bcd96 100644 --- a/app/cxxopts.hpp +++ b/app/cxxopts.hpp @@ -25,8 +25,8 @@ THE SOFTWARE. #ifndef CXXOPTS_HPP_INCLUDED #define CXXOPTS_HPP_INCLUDED -#include #include +#include #include #include #include @@ -47,1613 +47,1072 @@ THE SOFTWARE. #define CXXOPTS__VERSION_MINOR 2 #define CXXOPTS__VERSION_PATCH 0 -namespace cxxopts -{ - static constexpr struct { - uint8_t major, minor, patch; - } version = { - CXXOPTS__VERSION_MAJOR, - CXXOPTS__VERSION_MINOR, - CXXOPTS__VERSION_PATCH - }; -} +namespace cxxopts { +static constexpr struct { + uint8_t major, minor, patch; +} version = {CXXOPTS__VERSION_MAJOR, CXXOPTS__VERSION_MINOR, + CXXOPTS__VERSION_PATCH}; +} // namespace cxxopts -//when we ask cxxopts to use Unicode, help strings are processed using ICU, -//which results in the correct lengths being computed for strings when they -//are formatted for the help output -//it is necessary to make sure that can be found by the -//compiler, and that icu-uc is linked in to the binary. +// when we ask cxxopts to use Unicode, help strings are processed using ICU, +// which results in the correct lengths being computed for strings when they +// are formatted for the help output +// it is necessary to make sure that can be found by the +// compiler, and that icu-uc is linked in to the binary. #ifdef CXXOPTS_USE_UNICODE #include -namespace cxxopts -{ - typedef icu::UnicodeString String; +namespace cxxopts { +typedef icu::UnicodeString String; - inline - String - toLocalString(std::string s) - { - return icu::UnicodeString::fromUTF8(std::move(s)); - } +inline String toLocalString(std::string s) { + return icu::UnicodeString::fromUTF8(std::move(s)); +} - class UnicodeStringIterator : public - std::iterator - { - public: +class UnicodeStringIterator + : public std::iterator { + public: + UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) + : s(string), i(pos) {} - UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) - : s(string) - , i(pos) - { - } + value_type operator*() const { return s->char32At(i); } - value_type - operator*() const - { - return s->char32At(i); - } + bool operator==(const UnicodeStringIterator& rhs) const { + return s == rhs.s && i == rhs.i; + } - bool - operator==(const UnicodeStringIterator& rhs) const - { - return s == rhs.s && i == rhs.i; - } + bool operator!=(const UnicodeStringIterator& rhs) const { + return !(*this == rhs); + } - bool - operator!=(const UnicodeStringIterator& rhs) const - { - return !(*this == rhs); - } + UnicodeStringIterator& operator++() { + ++i; + return *this; + } - UnicodeStringIterator& - operator++() - { - ++i; - return *this; - } + UnicodeStringIterator operator+(int32_t v) { + return UnicodeStringIterator(s, i + v); + } - UnicodeStringIterator - operator+(int32_t v) - { - return UnicodeStringIterator(s, i + v); - } + private: + const icu::UnicodeString* s; + int32_t i; +}; - private: - const icu::UnicodeString* s; - int32_t i; - }; +inline String& stringAppend(String& s, String a) { + return s.append(std::move(a)); +} - inline - String& - stringAppend(String&s, String a) - { - return s.append(std::move(a)); +inline String& stringAppend(String& s, int n, UChar32 c) { + for (int i = 0; i != n; ++i) { + s.append(c); } - inline - String& - stringAppend(String& s, int n, UChar32 c) - { - for (int i = 0; i != n; ++i) - { - s.append(c); - } + return s; +} - return s; +template +String& stringAppend(String& s, Iterator begin, Iterator end) { + while (begin != end) { + s.append(*begin); + ++begin; } - template - String& - stringAppend(String& s, Iterator begin, Iterator end) - { - while (begin != end) - { - s.append(*begin); - ++begin; - } + return s; +} - return s; - } +inline size_t stringLength(const String& s) { return s.length(); } - inline - size_t - stringLength(const String& s) - { - return s.length(); - } +inline std::string toUTF8String(const String& s) { + std::string result; + s.toUTF8String(result); - inline - std::string - toUTF8String(const String& s) - { - std::string result; - s.toUTF8String(result); + return result; +} - return result; - } +inline bool empty(const String& s) { return s.isEmpty(); } +} // namespace cxxopts - inline - bool - empty(const String& s) - { - return s.isEmpty(); - } +namespace std { +inline cxxopts::UnicodeStringIterator begin(const icu::UnicodeString& s) { + return cxxopts::UnicodeStringIterator(&s, 0); } -namespace std -{ - inline - cxxopts::UnicodeStringIterator - begin(const icu::UnicodeString& s) - { - return cxxopts::UnicodeStringIterator(&s, 0); - } - - inline - cxxopts::UnicodeStringIterator - end(const icu::UnicodeString& s) - { - return cxxopts::UnicodeStringIterator(&s, s.length()); - } +inline cxxopts::UnicodeStringIterator end(const icu::UnicodeString& s) { + return cxxopts::UnicodeStringIterator(&s, s.length()); } +} // namespace std -//ifdef CXXOPTS_USE_UNICODE +// ifdef CXXOPTS_USE_UNICODE #else -namespace cxxopts -{ - typedef std::string String; +namespace cxxopts { +typedef std::string String; - template - T - toLocalString(T&& t) - { - return std::forward(t); - } - - inline - size_t - stringLength(const String& s) - { - return s.length(); - } +template +T toLocalString(T&& t) { + return std::forward(t); +} - inline - String& - stringAppend(String&s, String a) - { - return s.append(std::move(a)); - } +inline size_t stringLength(const String& s) { return s.length(); } - inline - String& - stringAppend(String& s, size_t n, char c) - { - return s.append(n, c); - } +inline String& stringAppend(String& s, String a) { + return s.append(std::move(a)); +} - template - String& - stringAppend(String& s, Iterator begin, Iterator end) - { - return s.append(begin, end); - } +inline String& stringAppend(String& s, size_t n, char c) { + return s.append(n, c); +} - template - std::string - toUTF8String(T&& t) - { - return std::forward(t); - } +template +String& stringAppend(String& s, Iterator begin, Iterator end) { + return s.append(begin, end); +} - inline - bool - empty(const std::string& s) - { - return s.empty(); - } +template +std::string toUTF8String(T&& t) { + return std::forward(t); } -//ifdef CXXOPTS_USE_UNICODE +inline bool empty(const std::string& s) { return s.empty(); } +} // namespace cxxopts + +// ifdef CXXOPTS_USE_UNICODE #endif -namespace cxxopts -{ - namespace - { +namespace cxxopts { +namespace { #ifdef _WIN32 - const std::string LQUOTE("\'"); - const std::string RQUOTE("\'"); +const std::string LQUOTE("\'"); +const std::string RQUOTE("\'"); #else - const std::string LQUOTE("‘"); - const std::string RQUOTE("’"); +const std::string LQUOTE("‘"); +const std::string RQUOTE("’"); #endif - } - - class Value : public std::enable_shared_from_this - { - public: - - virtual ~Value() = default; - - virtual - std::shared_ptr - clone() const = 0; - - virtual void - parse(const std::string& text) const = 0; - - virtual void - parse() const = 0; - - virtual bool - has_default() const = 0; +} // namespace - virtual bool - is_container() const = 0; - - virtual bool - has_implicit() const = 0; +class Value : public std::enable_shared_from_this { + public: + virtual ~Value() = default; + + virtual std::shared_ptr clone() const = 0; + + virtual void parse(const std::string& text) const = 0; + + virtual void parse() const = 0; + + virtual bool has_default() const = 0; + + virtual bool is_container() const = 0; + + virtual bool has_implicit() const = 0; + + virtual std::string get_default_value() const = 0; + + virtual std::string get_implicit_value() const = 0; + + virtual std::shared_ptr default_value(const std::string& value) = 0; + + virtual std::shared_ptr implicit_value(const std::string& value) = 0; + + virtual bool is_boolean() const = 0; +}; + +class OptionException : public std::exception { + public: + OptionException(const std::string& message) : m_message(message) {} + + virtual const char* what() const noexcept { return m_message.c_str(); } + + private: + std::string m_message; +}; + +class OptionSpecException : public OptionException { + public: + OptionSpecException(const std::string& message) : OptionException(message) {} +}; + +class OptionParseException : public OptionException { + public: + OptionParseException(const std::string& message) : OptionException(message) {} +}; + +class option_exists_error : public OptionSpecException { + public: + option_exists_error(const std::string& option) + : OptionSpecException(u8"Option " + LQUOTE + option + RQUOTE + + u8" already exists") {} +}; + +class invalid_option_format_error : public OptionSpecException { + public: + invalid_option_format_error(const std::string& format) + : OptionSpecException(u8"Invalid option format " + LQUOTE + format + + RQUOTE) {} +}; + +class option_syntax_exception : public OptionParseException { + public: + option_syntax_exception(const std::string& text) + : OptionParseException(u8"Argument " + LQUOTE + text + RQUOTE + + u8" starts with a - but has incorrect syntax") {} +}; + +class option_not_exists_exception : public OptionParseException { + public: + option_not_exists_exception(const std::string& option) + : OptionParseException(u8"Option " + LQUOTE + option + RQUOTE + + u8" does not exist") {} +}; + +class missing_argument_exception : public OptionParseException { + public: + missing_argument_exception(const std::string& option) + : OptionParseException(u8"Option " + LQUOTE + option + RQUOTE + + u8" is missing an argument") {} +}; + +class option_requires_argument_exception : public OptionParseException { + public: + option_requires_argument_exception(const std::string& option) + : OptionParseException(u8"Option " + LQUOTE + option + RQUOTE + + u8" requires an argument") {} +}; + +class option_not_has_argument_exception : public OptionParseException { + public: + option_not_has_argument_exception(const std::string& option, + const std::string& arg) + : OptionParseException(u8"Option " + LQUOTE + option + RQUOTE + + u8" does not take an argument, but argument " + + LQUOTE + arg + RQUOTE + " given") {} +}; + +class option_not_present_exception : public OptionParseException { + public: + option_not_present_exception(const std::string& option) + : OptionParseException(u8"Option " + LQUOTE + option + RQUOTE + + u8" not present") {} +}; + +class argument_incorrect_type : public OptionParseException { + public: + argument_incorrect_type(const std::string& arg) + : OptionParseException(u8"Argument " + LQUOTE + arg + RQUOTE + + u8" failed to parse") {} +}; + +class option_required_exception : public OptionParseException { + public: + option_required_exception(const std::string& option) + : OptionParseException(u8"Option " + LQUOTE + option + RQUOTE + + u8" is required but not present") {} +}; + +namespace values { +namespace { +std::basic_regex integer_pattern("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); +std::basic_regex truthy_pattern("(t|T)(rue)?"); +std::basic_regex falsy_pattern("((f|F)(alse)?)?"); +} // namespace + +namespace detail { +template +struct SignedCheck; + +template +struct SignedCheck { + template + void operator()(bool negative, U u, const std::string& text) { + if (negative) { + if (u > static_cast(std::numeric_limits::min())) { + throw argument_incorrect_type(text); + } + } else { + if (u > static_cast(std::numeric_limits::max())) { + throw argument_incorrect_type(text); + } + } + } +}; - virtual std::string - get_default_value() const = 0; +template +struct SignedCheck { + template + void operator()(bool, U, const std::string&) {} +}; - virtual std::string - get_implicit_value() const = 0; +template +void check_signed_range(bool negative, U value, const std::string& text) { + SignedCheck::is_signed>()(negative, value, text); +} +} // namespace detail + +template +R checked_negate(T&& t, const std::string&, std::true_type) { + // if we got to here, then `t` is a positive number that fits into + // `R`. So to avoid MSVC C4146, we first cast it to `R`. + // See https://github.com/jarro2783/cxxopts/issues/62 for more details. + return -static_cast(t); +} - virtual std::shared_ptr - default_value(const std::string& value) = 0; +template +T checked_negate(T&&, const std::string& text, std::false_type) { + throw argument_incorrect_type(text); +} - virtual std::shared_ptr - implicit_value(const std::string& value) = 0; +template +void integer_parser(const std::string& text, T& value) { + std::smatch match; + std::regex_match(text, match, integer_pattern); - virtual bool - is_boolean() const = 0; - }; + if (match.length() == 0) { + throw argument_incorrect_type(text); + } - class OptionException : public std::exception - { - public: - OptionException(const std::string& message) - : m_message(message) - { - } + if (match.length(4) > 0) { + value = 0; + return; + } - virtual const char* - what() const noexcept - { - return m_message.c_str(); - } + using US = typename std::make_unsigned::type; - private: - std::string m_message; - }; + constexpr auto umax = std::numeric_limits::max(); + constexpr bool is_signed = std::numeric_limits::is_signed; + const bool negative = match.length(1) > 0; + const uint8_t base = match.length(2) > 0 ? 16 : 10; - class OptionSpecException : public OptionException - { - public: + auto value_match = match[3]; - OptionSpecException(const std::string& message) - : OptionException(message) - { - } - }; - - class OptionParseException : public OptionException - { - public: - OptionParseException(const std::string& message) - : OptionException(message) - { - } - }; - - class option_exists_error : public OptionSpecException - { - public: - option_exists_error(const std::string& option) - : OptionSpecException(u8"Option " + LQUOTE + option + RQUOTE + u8" already exists") - { - } - }; - - class invalid_option_format_error : public OptionSpecException - { - public: - invalid_option_format_error(const std::string& format) - : OptionSpecException(u8"Invalid option format " + LQUOTE + format + RQUOTE) - { - } - }; - - class option_syntax_exception : public OptionParseException { - public: - option_syntax_exception(const std::string& text) - : OptionParseException(u8"Argument " + LQUOTE + text + RQUOTE + - u8" starts with a - but has incorrect syntax") - { - } - }; - - class option_not_exists_exception : public OptionParseException - { - public: - option_not_exists_exception(const std::string& option) - : OptionParseException(u8"Option " + LQUOTE + option + RQUOTE + u8" does not exist") - { - } - }; - - class missing_argument_exception : public OptionParseException - { - public: - missing_argument_exception(const std::string& option) - : OptionParseException( - u8"Option " + LQUOTE + option + RQUOTE + u8" is missing an argument" - ) - { - } - }; - - class option_requires_argument_exception : public OptionParseException - { - public: - option_requires_argument_exception(const std::string& option) - : OptionParseException( - u8"Option " + LQUOTE + option + RQUOTE + u8" requires an argument" - ) - { - } - }; - - class option_not_has_argument_exception : public OptionParseException - { - public: - option_not_has_argument_exception - ( - const std::string& option, - const std::string& arg - ) - : OptionParseException( - u8"Option " + LQUOTE + option + RQUOTE + - u8" does not take an argument, but argument " + - LQUOTE + arg + RQUOTE + " given" - ) - { - } - }; - - class option_not_present_exception : public OptionParseException - { - public: - option_not_present_exception(const std::string& option) - : OptionParseException(u8"Option " + LQUOTE + option + RQUOTE + u8" not present") - { - } - }; - - class argument_incorrect_type : public OptionParseException - { - public: - argument_incorrect_type - ( - const std::string& arg - ) - : OptionParseException( - u8"Argument " + LQUOTE + arg + RQUOTE + u8" failed to parse" - ) - { - } - }; - - class option_required_exception : public OptionParseException - { - public: - option_required_exception(const std::string& option) - : OptionParseException( - u8"Option " + LQUOTE + option + RQUOTE + u8" is required but not present" - ) - { - } - }; - - namespace values - { - namespace - { - std::basic_regex integer_pattern - ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); - std::basic_regex truthy_pattern - ("(t|T)(rue)?"); - std::basic_regex falsy_pattern - ("((f|F)(alse)?)?"); - } + US result = 0; - namespace detail - { - template - struct SignedCheck; - - template - struct SignedCheck - { - template - void - operator()(bool negative, U u, const std::string& text) - { - if (negative) - { - if (u > static_cast(std::numeric_limits::min())) - { - throw argument_incorrect_type(text); - } - } - else - { - if (u > static_cast(std::numeric_limits::max())) - { - throw argument_incorrect_type(text); - } - } - } - }; - - template - struct SignedCheck - { - template - void - operator()(bool, U, const std::string&) {} - }; - - template - void - check_signed_range(bool negative, U value, const std::string& text) - { - SignedCheck::is_signed>()(negative, value, text); - } - } + for (auto iter = value_match.first; iter != value_match.second; ++iter) { + US digit = 0; - template - R - checked_negate(T&& t, const std::string&, std::true_type) - { - // if we got to here, then `t` is a positive number that fits into - // `R`. So to avoid MSVC C4146, we first cast it to `R`. - // See https://github.com/jarro2783/cxxopts/issues/62 for more details. - return -static_cast(t); + if (*iter >= '0' && *iter <= '9') { + digit = *iter - '0'; + } else if (base == 16 && *iter >= 'a' && *iter <= 'f') { + digit = *iter - 'a' + 10; + } else if (base == 16 && *iter >= 'A' && *iter <= 'F') { + digit = *iter - 'A' + 10; + } else { + throw argument_incorrect_type(text); } - template - T - checked_negate(T&&, const std::string& text, std::false_type) - { + if (umax - digit < result * base) { throw argument_incorrect_type(text); } - template - void - integer_parser(const std::string& text, T& value) - { - std::smatch match; - std::regex_match(text, match, integer_pattern); - - if (match.length() == 0) - { - throw argument_incorrect_type(text); - } - - if (match.length(4) > 0) - { - value = 0; - return; - } + result = result * base + digit; + } - using US = typename std::make_unsigned::type; + detail::check_signed_range(negative, result, text); - constexpr auto umax = std::numeric_limits::max(); - constexpr bool is_signed = std::numeric_limits::is_signed; - const bool negative = match.length(1) > 0; - const uint8_t base = match.length(2) > 0 ? 16 : 10; + if (negative) { + value = checked_negate(result, text, + std::integral_constant()); + } else { + value = result; + } +} - auto value_match = match[3]; +template +void stringstream_parser(const std::string& text, T& value) { + std::stringstream in(text); + in >> value; + if (!in) { + throw argument_incorrect_type(text); + } +} - US result = 0; +inline void parse_value(const std::string& text, uint8_t& value) { + integer_parser(text, value); +} - for (auto iter = value_match.first; iter != value_match.second; ++iter) - { - US digit = 0; +inline void parse_value(const std::string& text, int8_t& value) { + integer_parser(text, value); +} - if (*iter >= '0' && *iter <= '9') - { - digit = *iter - '0'; - } - else if (base == 16 && *iter >= 'a' && *iter <= 'f') - { - digit = *iter - 'a' + 10; - } - else if (base == 16 && *iter >= 'A' && *iter <= 'F') - { - digit = *iter - 'A' + 10; - } - else - { - throw argument_incorrect_type(text); - } +inline void parse_value(const std::string& text, uint16_t& value) { + integer_parser(text, value); +} - if (umax - digit < result * base) - { - throw argument_incorrect_type(text); - } +inline void parse_value(const std::string& text, int16_t& value) { + integer_parser(text, value); +} - result = result * base + digit; - } +inline void parse_value(const std::string& text, uint32_t& value) { + integer_parser(text, value); +} - detail::check_signed_range(negative, result, text); +inline void parse_value(const std::string& text, int32_t& value) { + integer_parser(text, value); +} - if (negative) - { - value = checked_negate(result, - text, - std::integral_constant()); - } - else - { - value = result; - } - } +inline void parse_value(const std::string& text, uint64_t& value) { + integer_parser(text, value); +} - template - void stringstream_parser(const std::string& text, T& value) - { - std::stringstream in(text); - in >> value; - if (!in) { - throw argument_incorrect_type(text); - } - } +inline void parse_value(const std::string& text, int64_t& value) { + integer_parser(text, value); +} - inline - void - parse_value(const std::string& text, uint8_t& value) - { - integer_parser(text, value); - } +inline void parse_value(const std::string& text, bool& value) { + std::smatch result; + std::regex_match(text, result, truthy_pattern); - inline - void - parse_value(const std::string& text, int8_t& value) - { - integer_parser(text, value); - } + if (!result.empty()) { + value = true; + return; + } - inline - void - parse_value(const std::string& text, uint16_t& value) - { - integer_parser(text, value); - } + std::regex_match(text, result, falsy_pattern); + if (!result.empty()) { + value = false; + return; + } - inline - void - parse_value(const std::string& text, int16_t& value) - { - integer_parser(text, value); - } + throw argument_incorrect_type(text); +} - inline - void - parse_value(const std::string& text, uint32_t& value) - { - integer_parser(text, value); - } +inline void parse_value(const std::string& text, std::string& value) { + value = text; +} - inline - void - parse_value(const std::string& text, int32_t& value) - { - integer_parser(text, value); - } +// The fallback parser. It uses the stringstream parser to parse all types +// that have not been overloaded explicitly. It has to be placed in the +// source code before all other more specialized templates. +template +void parse_value(const std::string& text, T& value) { + stringstream_parser(text, value); +} - inline - void - parse_value(const std::string& text, uint64_t& value) - { - integer_parser(text, value); - } +template +void parse_value(const std::string& text, std::vector& value) { + T v; + parse_value(text, v); + value.push_back(v); +} - inline - void - parse_value(const std::string& text, int64_t& value) - { - integer_parser(text, value); - } +#ifdef CXXOPTS_HAS_OPTIONAL +template +void parse_value(const std::string& text, std::optional& value) { + T result; + parse_value(text, result); + value = std::move(result); +} +#endif - inline - void - parse_value(const std::string& text, bool& value) - { - std::smatch result; - std::regex_match(text, result, truthy_pattern); - - if (!result.empty()) - { - value = true; - return; - } +template +struct type_is_container { + static constexpr bool value = false; +}; - std::regex_match(text, result, falsy_pattern); - if (!result.empty()) - { - value = false; - return; - } +template +struct type_is_container> { + static constexpr bool value = true; +}; - throw argument_incorrect_type(text); - } +template +class abstract_value : public Value { + using Self = abstract_value; - inline - void - parse_value(const std::string& text, std::string& value) - { - value = text; - } + public: + abstract_value() : m_result(std::make_shared()), m_store(m_result.get()) {} - // The fallback parser. It uses the stringstream parser to parse all types - // that have not been overloaded explicitly. It has to be placed in the - // source code before all other more specialized templates. - template - void - parse_value(const std::string& text, T& value) { - stringstream_parser(text, value); - } + abstract_value(T* t) : m_store(t) {} - template - void - parse_value(const std::string& text, std::vector& value) - { - T v; - parse_value(text, v); - value.push_back(v); - } + virtual ~abstract_value() = default; -#ifdef CXXOPTS_HAS_OPTIONAL - template - void - parse_value(const std::string& text, std::optional& value) - { - T result; - parse_value(text, result); - value = std::move(result); + abstract_value(const abstract_value& rhs) { + if (rhs.m_result) { + m_result = std::make_shared(); + m_store = m_result.get(); + } else { + m_store = rhs.m_store; } -#endif - - template - struct type_is_container - { - static constexpr bool value = false; - }; - - template - struct type_is_container> - { - static constexpr bool value = true; - }; - - template - class abstract_value : public Value - { - using Self = abstract_value; - - public: - abstract_value() - : m_result(std::make_shared()) - , m_store(m_result.get()) - { - } - - abstract_value(T* t) - : m_store(t) - { - } - virtual ~abstract_value() = default; + m_default = rhs.m_default; + m_implicit = rhs.m_implicit; + m_default_value = rhs.m_default_value; + m_implicit_value = rhs.m_implicit_value; + } - abstract_value(const abstract_value& rhs) - { - if (rhs.m_result) - { - m_result = std::make_shared(); - m_store = m_result.get(); - } - else - { - m_store = rhs.m_store; - } + void parse(const std::string& text) const { parse_value(text, *m_store); } - m_default = rhs.m_default; - m_implicit = rhs.m_implicit; - m_default_value = rhs.m_default_value; - m_implicit_value = rhs.m_implicit_value; - } + bool is_container() const { return type_is_container::value; } - void - parse(const std::string& text) const - { - parse_value(text, *m_store); - } + void parse() const { parse_value(m_default_value, *m_store); } - bool - is_container() const - { - return type_is_container::value; - } + bool has_default() const { return m_default; } - void - parse() const - { - parse_value(m_default_value, *m_store); - } + bool has_implicit() const { return m_implicit; } - bool - has_default() const - { - return m_default; - } + std::shared_ptr default_value(const std::string& value) { + m_default = true; + m_default_value = value; + return shared_from_this(); + } - bool - has_implicit() const - { - return m_implicit; - } + std::shared_ptr implicit_value(const std::string& value) { + m_implicit = true; + m_implicit_value = value; + return shared_from_this(); + } - std::shared_ptr - default_value(const std::string& value) - { - m_default = true; - m_default_value = value; - return shared_from_this(); - } + std::string get_default_value() const { return m_default_value; } - std::shared_ptr - implicit_value(const std::string& value) - { - m_implicit = true; - m_implicit_value = value; - return shared_from_this(); - } + std::string get_implicit_value() const { return m_implicit_value; } - std::string - get_default_value() const - { - return m_default_value; - } + bool is_boolean() const { return std::is_same::value; } - std::string - get_implicit_value() const - { - return m_implicit_value; - } + const T& get() const { + if (m_store == nullptr) { + return *m_result; + } else { + return *m_store; + } + } - bool - is_boolean() const - { - return std::is_same::value; - } + protected: + std::shared_ptr m_result; + T* m_store; - const T& - get() const - { - if (m_store == nullptr) - { - return *m_result; - } - else - { - return *m_store; - } - } + bool m_default = false; + bool m_implicit = false; - protected: - std::shared_ptr m_result; - T* m_store; + std::string m_default_value; + std::string m_implicit_value; +}; - bool m_default = false; - bool m_implicit = false; +template +class standard_value : public abstract_value { + public: + using abstract_value::abstract_value; - std::string m_default_value; - std::string m_implicit_value; - }; + std::shared_ptr clone() const { + return std::make_shared>(*this); + } +}; - template - class standard_value : public abstract_value - { - public: - using abstract_value::abstract_value; +template <> +class standard_value : public abstract_value { + public: + ~standard_value() = default; - std::shared_ptr - clone() const - { - return std::make_shared>(*this); - } - }; + standard_value() { set_default_and_implicit(); } - template <> - class standard_value : public abstract_value - { - public: - ~standard_value() = default; + standard_value(bool* b) : abstract_value(b) { set_default_and_implicit(); } - standard_value() - { - set_default_and_implicit(); - } + std::shared_ptr clone() const { + return std::make_shared>(*this); + } - standard_value(bool* b) - : abstract_value(b) - { - set_default_and_implicit(); - } + private: + void set_default_and_implicit() { + m_default = true; + m_default_value = "false"; + m_implicit = true; + m_implicit_value = "true"; + } +}; +} // namespace values - std::shared_ptr - clone() const - { - return std::make_shared>(*this); - } +template +std::shared_ptr value() { + return std::make_shared>(); +} - private: +template +std::shared_ptr value(T& t) { + return std::make_shared>(&t); +} - void - set_default_and_implicit() - { - m_default = true; - m_default_value = "false"; - m_implicit = true; - m_implicit_value = "true"; - } - }; +class OptionAdder; + +class OptionDetails { + public: + OptionDetails(const std::string& short_, const std::string& long_, + const String& desc, std::shared_ptr val) + : m_short(short_), + m_long(long_), + m_desc(desc), + m_value(val), + m_count(0) {} + + OptionDetails(const OptionDetails& rhs) + : m_desc(rhs.m_desc), m_count(rhs.m_count) { + m_value = rhs.m_value->clone(); } - template - std::shared_ptr - value() - { - return std::make_shared>(); + OptionDetails(OptionDetails&& rhs) = default; + + const String& description() const { return m_desc; } + + const Value& value() const { return *m_value; } + + std::shared_ptr make_storage() const { return m_value->clone(); } + + const std::string& short_name() const { return m_short; } + + const std::string& long_name() const { return m_long; } + + private: + std::string m_short; + std::string m_long; + String m_desc; + std::shared_ptr m_value; + int m_count; +}; + +struct HelpOptionDetails { + std::string s; + std::string l; + String desc; + bool has_default; + std::string default_value; + bool has_implicit; + std::string implicit_value; + std::string arg_help; + bool is_container; + bool is_boolean; +}; + +struct HelpGroupDetails { + std::string name; + std::string description; + std::vector options; +}; + +class OptionValue { + public: + void parse(std::shared_ptr details, + const std::string& text) { + ensure_value(details); + ++m_count; + m_value->parse(text); } - template - std::shared_ptr - value(T& t) - { - return std::make_shared>(&t); + void parse_default(std::shared_ptr details) { + ensure_value(details); + m_value->parse(); } - class OptionAdder; - - class OptionDetails - { - public: - OptionDetails - ( - const std::string& short_, - const std::string& long_, - const String& desc, - std::shared_ptr val - ) - : m_short(short_) - , m_long(long_) - , m_desc(desc) - , m_value(val) - , m_count(0) - { - } - - OptionDetails(const OptionDetails& rhs) - : m_desc(rhs.m_desc) - , m_count(rhs.m_count) - { - m_value = rhs.m_value->clone(); - } + size_t count() const { return m_count; } - OptionDetails(OptionDetails&& rhs) = default; - - const String& - description() const - { - return m_desc; + template + const T& as() const { + if (m_value == nullptr) { + throw std::domain_error("No value"); } - const Value& value() const { - return *m_value; - } +#ifdef CXXOPTS_NO_RTTI + return static_cast&>(*m_value).get(); +#else + return dynamic_cast&>(*m_value).get(); +#endif + } - std::shared_ptr - make_storage() const - { - return m_value->clone(); + private: + void ensure_value(std::shared_ptr details) { + if (m_value == nullptr) { + m_value = details->make_storage(); } + } - const std::string& - short_name() const - { - return m_short; - } + std::shared_ptr m_value; + size_t m_count = 0; +}; - const std::string& - long_name() const - { - return m_long; - } +class KeyValue { + public: + KeyValue(std::string key_, std::string value_) + : m_key(std::move(key_)), m_value(std::move(value_)) {} - private: - std::string m_short; - std::string m_long; - String m_desc; - std::shared_ptr m_value; - int m_count; - }; - - struct HelpOptionDetails - { - std::string s; - std::string l; - String desc; - bool has_default; - std::string default_value; - bool has_implicit; - std::string implicit_value; - std::string arg_help; - bool is_container; - bool is_boolean; - }; - - struct HelpGroupDetails - { - std::string name; - std::string description; - std::vector options; - }; - - class OptionValue - { - public: - void - parse - ( - std::shared_ptr details, - const std::string& text - ) - { - ensure_value(details); - ++m_count; - m_value->parse(text); - } + const std::string& key() const { return m_key; } - void - parse_default(std::shared_ptr details) - { - ensure_value(details); - m_value->parse(); - } + const std::string value() const { return m_value; } - size_t - count() const - { - return m_count; - } + template + T as() const { + T result; + values::parse_value(m_value, result); + return result; + } - template - const T& - as() const - { - if (m_value == nullptr) { - throw std::domain_error("No value"); - } + private: + std::string m_key; + std::string m_value; +}; -#ifdef CXXOPTS_NO_RTTI - return static_cast&>(*m_value).get(); -#else - return dynamic_cast&>(*m_value).get(); -#endif - } +class ParseResult { + public: + ParseResult( + const std::shared_ptr< + std::unordered_map>>, + std::vector, bool allow_unrecognised, int&, char**&); - private: - void - ensure_value(std::shared_ptr details) - { - if (m_value == nullptr) - { - m_value = details->make_storage(); - } + size_t count(const std::string& o) const { + auto iter = m_options->find(o); + if (iter == m_options->end()) { + return 0; } - std::shared_ptr m_value; - size_t m_count = 0; - }; - - class KeyValue - { - public: - KeyValue(std::string key_, std::string value_) - : m_key(std::move(key_)) - , m_value(std::move(value_)) - { - } + auto riter = m_results.find(iter->second); - const - std::string& - key() const - { - return m_key; - } + return riter->second.count(); + } - const std::string - value() const - { - return m_value; - } + const OptionValue& operator[](const std::string& option) const { + auto iter = m_options->find(option); - template - T - as() const - { - T result; - values::parse_value(m_value, result); - return result; + if (iter == m_options->end()) { + throw option_not_present_exception(option); } - private: - std::string m_key; - std::string m_value; - }; + auto riter = m_results.find(iter->second); - class ParseResult - { - public: + return riter->second; + } - ParseResult( - const std::shared_ptr< - std::unordered_map> - >, - std::vector, - bool allow_unrecognised, - int&, char**&); - - size_t - count(const std::string& o) const - { - auto iter = m_options->find(o); - if (iter == m_options->end()) - { - return 0; - } + const std::vector& arguments() const { return m_sequential; } - auto riter = m_results.find(iter->second); + private: + void parse(int& argc, char**& argv); - return riter->second.count(); - } + void add_to_option(const std::string& option, const std::string& arg); - const OptionValue& - operator[](const std::string& option) const - { - auto iter = m_options->find(option); + bool consume_positional(std::string a); - if (iter == m_options->end()) - { - throw option_not_present_exception(option); - } + void parse_option(std::shared_ptr value, + const std::string& name, const std::string& arg = ""); - auto riter = m_results.find(iter->second); + void parse_default(std::shared_ptr details); - return riter->second; - } + void checked_parse_arg(int argc, char* argv[], int& current, + std::shared_ptr value, + const std::string& name); - const std::vector& - arguments() const - { - return m_sequential; - } + const std::shared_ptr< + std::unordered_map>> + m_options; + std::vector m_positional; + std::vector::iterator m_next_positional; + std::unordered_set m_positional_set; + std::unordered_map, OptionValue> m_results; - private: + bool m_allow_unrecognised; - void - parse(int& argc, char**& argv); + std::vector m_sequential; +}; - void - add_to_option(const std::string& option, const std::string& arg); +class Options { + typedef std::unordered_map> + OptionMap; - bool - consume_positional(std::string a); + public: + Options(std::string program, std::string help_string = "") + : m_program(std::move(program)), + m_help_string(toLocalString(std::move(help_string))), + m_custom_help("[OPTION...]"), + m_positional_help("positional parameters"), + m_show_positional(false), + m_allow_unrecognised(false), + m_options(std::make_shared()), + m_next_positional(m_positional.end()) {} + + Options& positional_help(std::string help_text) { + m_positional_help = std::move(help_text); + return *this; + } - void - parse_option - ( - std::shared_ptr value, - const std::string& name, - const std::string& arg = "" - ); + Options& custom_help(std::string help_text) { + m_custom_help = std::move(help_text); + return *this; + } - void - parse_default(std::shared_ptr details); + Options& show_positional_help() { + m_show_positional = true; + return *this; + } - void - checked_parse_arg - ( - int argc, - char* argv[], - int& current, - std::shared_ptr value, - const std::string& name - ); + Options& allow_unrecognised_options() { + m_allow_unrecognised = true; + return *this; + } - const std::shared_ptr< - std::unordered_map> - > m_options; - std::vector m_positional; - std::vector::iterator m_next_positional; - std::unordered_set m_positional_set; - std::unordered_map, OptionValue> m_results; + ParseResult parse(int& argc, char**& argv); - bool m_allow_unrecognised; + OptionAdder add_options(std::string group = ""); - std::vector m_sequential; - }; + void add_option(const std::string& group, const std::string& s, + const std::string& l, std::string desc, + std::shared_ptr value, std::string arg_help); - class Options - { - typedef std::unordered_map> - OptionMap; - public: - - Options(std::string program, std::string help_string = "") - : m_program(std::move(program)) - , m_help_string(toLocalString(std::move(help_string))) - , m_custom_help("[OPTION...]") - , m_positional_help("positional parameters") - , m_show_positional(false) - , m_allow_unrecognised(false) - , m_options(std::make_shared()) - , m_next_positional(m_positional.end()) - { - } + // parse positional arguments into the given option + void parse_positional(std::string option); - Options& - positional_help(std::string help_text) - { - m_positional_help = std::move(help_text); - return *this; - } + void parse_positional(std::vector options); - Options& - custom_help(std::string help_text) - { - m_custom_help = std::move(help_text); - return *this; - } + void parse_positional(std::initializer_list options); - Options& - show_positional_help() - { - m_show_positional = true; - return *this; - } + template + void parse_positional(Iterator begin, Iterator end) { + parse_positional(std::vector{begin, end}); + } - Options& - allow_unrecognised_options() - { - m_allow_unrecognised = true; - return *this; - } + std::string help(const std::vector& groups = {""}) const; - ParseResult - parse(int& argc, char**& argv); - - OptionAdder - add_options(std::string group = ""); - - void - add_option - ( - const std::string& group, - const std::string& s, - const std::string& l, - std::string desc, - std::shared_ptr value, - std::string arg_help - ); - - //parse positional arguments into the given option - void - parse_positional(std::string option); - - void - parse_positional(std::vector options); - - void - parse_positional(std::initializer_list options); - - template - void - parse_positional(Iterator begin, Iterator end) { - parse_positional(std::vector{begin, end}); - } + const std::vector groups() const; - std::string - help(const std::vector& groups = {""}) const; + const HelpGroupDetails& group_help(const std::string& group) const; - const std::vector - groups() const; + private: + void add_one_option(const std::string& option, + std::shared_ptr details); - const HelpGroupDetails& - group_help(const std::string& group) const; + String help_one_group(const std::string& group) const; - private: + void generate_group_help(String& result, + const std::vector& groups) const; - void - add_one_option - ( - const std::string& option, - std::shared_ptr details - ); + void generate_all_groups_help(String& result) const; - String - help_one_group(const std::string& group) const; + std::string m_program; + String m_help_string; + std::string m_custom_help; + std::string m_positional_help; + bool m_show_positional; + bool m_allow_unrecognised; - void - generate_group_help - ( - String& result, - const std::vector& groups - ) const; + std::shared_ptr m_options; + std::vector m_positional; + std::vector::iterator m_next_positional; + std::unordered_set m_positional_set; - void - generate_all_groups_help(String& result) const; + // mapping from groups to help options + std::map m_help; +}; - std::string m_program; - String m_help_string; - std::string m_custom_help; - std::string m_positional_help; - bool m_show_positional; - bool m_allow_unrecognised; +class OptionAdder { + public: + OptionAdder(Options& options, std::string group) + : m_options(options), m_group(std::move(group)) {} - std::shared_ptr m_options; - std::vector m_positional; - std::vector::iterator m_next_positional; - std::unordered_set m_positional_set; + OptionAdder& operator()( + const std::string& opts, const std::string& desc, + std::shared_ptr value = ::cxxopts::value(), + std::string arg_help = ""); - //mapping from groups to help options - std::map m_help; - }; + private: + Options& m_options; + std::string m_group; +}; - class OptionAdder - { - public: +namespace { +constexpr int OPTION_LONGEST = 30; +constexpr int OPTION_DESC_GAP = 2; - OptionAdder(Options& options, std::string group) - : m_options(options), m_group(std::move(group)) - { - } +std::basic_regex option_matcher( + "--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)"); - OptionAdder& - operator() - ( - const std::string& opts, - const std::string& desc, - std::shared_ptr value - = ::cxxopts::value(), - std::string arg_help = "" - ); - - private: - Options& m_options; - std::string m_group; - }; - - namespace - { - constexpr int OPTION_LONGEST = 30; - constexpr int OPTION_DESC_GAP = 2; - - std::basic_regex option_matcher - ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)"); - - std::basic_regex option_specifier - ("(([[:alnum:]]),)?[ ]*([[:alnum:]][-_[:alnum:]]*)?"); - - String - format_option - ( - const HelpOptionDetails& o - ) - { - auto& s = o.s; - auto& l = o.l; - - String result = " "; - - if (s.size() > 0) - { - result += "-" + toLocalString(s) + ","; - } - else - { - result += " "; - } +std::basic_regex option_specifier( + "(([[:alnum:]]),)?[ ]*([[:alnum:]][-_[:alnum:]]*)?"); - if (l.size() > 0) - { - result += " --" + toLocalString(l); - } +String format_option(const HelpOptionDetails& o) { + auto& s = o.s; + auto& l = o.l; - auto arg = o.arg_help.size() > 0 ? toLocalString(o.arg_help) : "arg"; + String result = " "; - if (!o.is_boolean) - { - if (o.has_implicit) - { - result += " [=" + arg + "(=" + toLocalString(o.implicit_value) + ")]"; - } - else - { - result += " " + arg; - } - } + if (s.size() > 0) { + result += "-" + toLocalString(s) + ","; + } else { + result += " "; + } - return result; - } + if (l.size() > 0) { + result += " --" + toLocalString(l); + } - String - format_description - ( - const HelpOptionDetails& o, - size_t start, - size_t width - ) - { - auto desc = o.desc; - - if (o.has_default && (!o.is_boolean || o.default_value != "false")) - { - desc += toLocalString(" (default: " + o.default_value + ")"); - } + auto arg = o.arg_help.size() > 0 ? toLocalString(o.arg_help) : "arg"; - String result; + if (!o.is_boolean) { + if (o.has_implicit) { + result += " [=" + arg + "(=" + toLocalString(o.implicit_value) + ")]"; + } else { + result += " " + arg; + } + } - auto current = std::begin(desc); - auto startLine = current; - auto lastSpace = current; + return result; +} - auto size = size_t{}; +String format_description(const HelpOptionDetails& o, size_t start, + size_t width) { + auto desc = o.desc; - while (current != std::end(desc)) - { - if (*current == ' ') - { - lastSpace = current; - } + if (o.has_default && (!o.is_boolean || o.default_value != "false")) { + desc += toLocalString(" (default: " + o.default_value + ")"); + } - if (*current == '\n') - { - startLine = current + 1; - lastSpace = startLine; - } - else if (size > width) - { - if (lastSpace == startLine) - { - stringAppend(result, startLine, current + 1); - stringAppend(result, "\n"); - stringAppend(result, start, ' '); - startLine = current + 1; - lastSpace = startLine; - } - else - { - stringAppend(result, startLine, lastSpace); - stringAppend(result, "\n"); - stringAppend(result, start, ' '); - startLine = lastSpace + 1; - } - size = 0; - } - else - { - ++size; - } + String result; - ++current; + auto current = std::begin(desc); + auto startLine = current; + auto lastSpace = current; + + auto size = size_t{}; + + while (current != std::end(desc)) { + if (*current == ' ') { + lastSpace = current; + } + + if (*current == '\n') { + startLine = current + 1; + lastSpace = startLine; + } else if (size > width) { + if (lastSpace == startLine) { + stringAppend(result, startLine, current + 1); + stringAppend(result, "\n"); + stringAppend(result, start, ' '); + startLine = current + 1; + lastSpace = startLine; + } else { + stringAppend(result, startLine, lastSpace); + stringAppend(result, "\n"); + stringAppend(result, start, ' '); + startLine = lastSpace + 1; } - - //append whatever is left - stringAppend(result, startLine, current); - - return result; + size = 0; + } else { + ++size; } + + ++current; } -inline -ParseResult::ParseResult -( - const std::shared_ptr< - std::unordered_map> - > options, - std::vector positional, - bool allow_unrecognised, - int& argc, char**& argv -) -: m_options(options) -, m_positional(std::move(positional)) -, m_next_positional(m_positional.begin()) -, m_allow_unrecognised(allow_unrecognised) -{ + // append whatever is left + stringAppend(result, startLine, current); + + return result; +} +} // namespace + +inline ParseResult::ParseResult( + const std::shared_ptr< + std::unordered_map>> + options, + std::vector positional, bool allow_unrecognised, int& argc, + char**& argv) + : m_options(options), + m_positional(std::move(positional)), + m_next_positional(m_positional.begin()), + m_allow_unrecognised(allow_unrecognised) { parse(argc, argv); } -inline -OptionAdder -Options::add_options(std::string group) -{ +inline OptionAdder Options::add_options(std::string group) { return OptionAdder(*this, std::move(group)); } -inline -OptionAdder& -OptionAdder::operator() -( - const std::string& opts, - const std::string& desc, - std::shared_ptr value, - std::string arg_help -) -{ +inline OptionAdder& OptionAdder::operator()(const std::string& opts, + const std::string& desc, + std::shared_ptr value, + std::string arg_help) { std::match_results result; std::regex_match(opts.c_str(), result, option_specifier); - if (result.empty()) - { + if (result.empty()) { throw invalid_option_format_error(opts); } const auto& short_match = result[2]; const auto& long_match = result[3]; - if (!short_match.length() && !long_match.length()) - { + if (!short_match.length() && !long_match.length()) { throw invalid_option_format_error(opts); - } else if (long_match.length() == 1 && short_match.length()) - { + } else if (long_match.length() == 1 && short_match.length()) { throw invalid_option_format_error(opts); } - auto option_names = [] - ( - const std::sub_match& short_, - const std::sub_match& long_ - ) - { - if (long_.length() == 1) - { + auto option_names = [](const std::sub_match& short_, + const std::sub_match& long_) { + if (long_.length() == 1) { return std::make_tuple(long_.str(), short_.str()); - } - else - { + } else { return std::make_tuple(short_.str(), long_.str()); } }(short_match, long_match); - m_options.add_option - ( - m_group, - std::get<0>(option_names), - std::get<1>(option_names), - desc, - value, - std::move(arg_help) - ); + m_options.add_option(m_group, std::get<0>(option_names), + std::get<1>(option_names), desc, value, + std::move(arg_help)); return *this; } -inline -void -ParseResult::parse_default(std::shared_ptr details) -{ +inline void ParseResult::parse_default(std::shared_ptr details) { m_results[details].parse_default(details); } -inline -void -ParseResult::parse_option -( - std::shared_ptr value, - const std::string& /*name*/, - const std::string& arg -) -{ +inline void ParseResult::parse_option(std::shared_ptr value, + const std::string& /*name*/, + const std::string& arg) { auto& result = m_results[value]; result.parse(value, arg); m_sequential.emplace_back(value->long_name(), arg); } -inline -void -ParseResult::checked_parse_arg -( - int argc, - char* argv[], - int& current, - std::shared_ptr value, - const std::string& name -) -{ - if (current + 1 >= argc) - { - if (value->value().has_implicit()) - { +inline void ParseResult::checked_parse_arg(int argc, char* argv[], int& current, + std::shared_ptr value, + const std::string& name) { + if (current + 1 >= argc) { + if (value->value().has_implicit()) { parse_option(value, name, value->value().get_implicit_value()); - } - else - { + } else { throw missing_argument_exception(name); } - } - else - { - if (value->value().has_implicit()) - { + } else { + if (value->value().has_implicit()) { parse_option(value, name, value->value().get_implicit_value()); - } - else - { + } else { parse_option(value, name, argv[current + 1]); ++current; } } } -inline -void -ParseResult::add_to_option(const std::string& option, const std::string& arg) -{ +inline void ParseResult::add_to_option(const std::string& option, + const std::string& arg) { auto iter = m_options->find(option); - if (iter == m_options->end()) - { + if (iter == m_options->end()) { throw option_not_exists_exception(option); } parse_option(iter->second, option, arg); } -inline -bool -ParseResult::consume_positional(std::string a) -{ - while (m_next_positional != m_positional.end()) - { +inline bool ParseResult::consume_positional(std::string a) { + while (m_next_positional != m_positional.end()) { auto iter = m_options->find(*m_next_positional); - if (iter != m_options->end()) - { + if (iter != m_options->end()) { auto& result = m_results[iter->second]; - if (!iter->second->value().is_container()) - { - if (result.count() == 0) - { + if (!iter->second->value().is_container()) { + if (result.count() == 0) { add_to_option(*m_next_positional, a); ++m_next_positional; return true; - } - else - { + } else { ++m_next_positional; continue; } - } - else - { + } else { add_to_option(*m_next_positional, a); return true; } @@ -1664,52 +1123,36 @@ ParseResult::consume_positional(std::string a) return false; } -inline -void -Options::parse_positional(std::string option) -{ +inline void Options::parse_positional(std::string option) { parse_positional(std::vector{std::move(option)}); } -inline -void -Options::parse_positional(std::vector options) -{ +inline void Options::parse_positional(std::vector options) { m_positional = std::move(options); m_next_positional = m_positional.begin(); m_positional_set.insert(m_positional.begin(), m_positional.end()); } -inline -void -Options::parse_positional(std::initializer_list options) -{ +inline void Options::parse_positional( + std::initializer_list options) { parse_positional(std::vector(std::move(options))); } -inline -ParseResult -Options::parse(int& argc, char**& argv) -{ +inline ParseResult Options::parse(int& argc, char**& argv) { ParseResult result(m_options, m_positional, m_allow_unrecognised, argc, argv); return result; } -inline -void -ParseResult::parse(int& argc, char**& argv) -{ +inline void ParseResult::parse(int& argc, char**& argv) { int current = 1; int nextKeep = 1; bool consume_remaining = false; - while (current != argc) - { - if (strcmp(argv[current], "--") == 0) - { + while (current != argc) { + if (strcmp(argv[current], "--") == 0) { consume_remaining = true; ++current; break; @@ -1718,137 +1161,107 @@ ParseResult::parse(int& argc, char**& argv) std::match_results result; std::regex_match(argv[current], result, option_matcher); - if (result.empty()) - { - //not a flag + if (result.empty()) { + // not a flag // but if it starts with a `-`, then it's an error if (argv[current][0] == '-') { throw option_syntax_exception(argv[current]); } - //if true is returned here then it was consumed, otherwise it is - //ignored - if (consume_positional(argv[current])) - { - } - else - { + // if true is returned here then it was consumed, otherwise it is + // ignored + if (consume_positional(argv[current])) { + } else { argv[nextKeep] = argv[current]; ++nextKeep; } - //if we return from here then it was parsed successfully, so continue - } - else - { - //short or long option? - if (result[4].length() != 0) - { + // if we return from here then it was parsed successfully, so continue + } else { + // short or long option? + if (result[4].length() != 0) { const std::string& s = result[4]; - for (std::size_t i = 0; i != s.size(); ++i) - { + for (std::size_t i = 0; i != s.size(); ++i) { std::string name(1, s[i]); auto iter = m_options->find(name); - if (iter == m_options->end()) - { - if (m_allow_unrecognised) - { + if (iter == m_options->end()) { + if (m_allow_unrecognised) { continue; - } - else - { - //error + } else { + // error throw option_not_exists_exception(name); } } auto value = iter->second; - if (i + 1 == s.size()) - { - //it must be the last argument + if (i + 1 == s.size()) { + // it must be the last argument checked_parse_arg(argc, argv, current, value, name); - } - else if (value->value().has_implicit()) - { + } else if (value->value().has_implicit()) { parse_option(value, name, value->value().get_implicit_value()); - } - else - { - //error + } else { + // error throw option_requires_argument_exception(name); } } - } - else if (result[1].length() != 0) - { + } else if (result[1].length() != 0) { const std::string& name = result[1]; auto iter = m_options->find(name); - if (iter == m_options->end()) - { - if (m_allow_unrecognised) - { + if (iter == m_options->end()) { + if (m_allow_unrecognised) { // keep unrecognised options in argument list, skip to next argument argv[nextKeep] = argv[current]; ++nextKeep; ++current; continue; - } - else - { - //error + } else { + // error throw option_not_exists_exception(name); } } auto opt = iter->second; - //equals provided for long option? - if (result[2].length() != 0) - { - //parse the option given + // equals provided for long option? + if (result[2].length() != 0) { + // parse the option given parse_option(opt, name, result[3]); - } - else - { - //parse the next argument + } else { + // parse the next argument checked_parse_arg(argc, argv, current, opt, name); } } - } ++current; } - for (auto& opt : *m_options) - { + for (auto& opt : *m_options) { auto& detail = opt.second; auto& value = detail->value(); auto& store = m_results[detail]; - if(!store.count() && value.has_default()){ + if (!store.count() && value.has_default()) { parse_default(detail); } } - if (consume_remaining) - { - while (current < argc) - { + if (consume_remaining) { + while (current < argc) { if (!consume_positional(argv[current])) { break; } ++current; } - //adjust argv for any that couldn't be swallowed + // adjust argv for any that couldn't be swallowed while (current != argc) { argv[nextKeep] = argv[current]; ++nextKeep; @@ -1857,70 +1270,46 @@ ParseResult::parse(int& argc, char**& argv) } argc = nextKeep; - } -inline -void -Options::add_option -( - const std::string& group, - const std::string& s, - const std::string& l, - std::string desc, - std::shared_ptr value, - std::string arg_help -) -{ +inline void Options::add_option(const std::string& group, const std::string& s, + const std::string& l, std::string desc, + std::shared_ptr value, + std::string arg_help) { auto stringDesc = toLocalString(std::move(desc)); auto option = std::make_shared(s, l, stringDesc, value); - if (s.size() > 0) - { + if (s.size() > 0) { add_one_option(s, option); } - if (l.size() > 0) - { + if (l.size() > 0) { add_one_option(l, option); } - //add the help details + // add the help details auto& options = m_help[group]; - options.options.emplace_back(HelpOptionDetails{s, l, stringDesc, - value->has_default(), value->get_default_value(), - value->has_implicit(), value->get_implicit_value(), - std::move(arg_help), - value->is_container(), - value->is_boolean()}); + options.options.emplace_back(HelpOptionDetails{ + s, l, stringDesc, value->has_default(), value->get_default_value(), + value->has_implicit(), value->get_implicit_value(), std::move(arg_help), + value->is_container(), value->is_boolean()}); } -inline -void -Options::add_one_option -( - const std::string& option, - std::shared_ptr details -) -{ +inline void Options::add_one_option(const std::string& option, + std::shared_ptr details) { auto in = m_options->emplace(option, details); - if (!in.second) - { + if (!in.second) { throw option_exists_error(option); } } -inline -String -Options::help_one_group(const std::string& g) const -{ +inline String Options::help_one_group(const std::string& g) const { typedef std::vector> OptionHelp; auto group = m_help.find(g); - if (group == m_help.end()) - { + if (group == m_help.end()) { return ""; } @@ -1930,17 +1319,14 @@ Options::help_one_group(const std::string& g) const String result; - if (!g.empty()) - { + if (!g.empty()) { result += toLocalString(" " + g + " options:\n"); } - for (const auto& o : group->second.options) - { + for (const auto& o : group->second.options) { if (o.is_container && m_positional_set.find(o.l) != m_positional_set.end() && - !m_show_positional) - { + !m_show_positional) { continue; } @@ -1951,32 +1337,26 @@ Options::help_one_group(const std::string& g) const longest = std::min(longest, static_cast(OPTION_LONGEST)); - //widest allowed description + // widest allowed description auto allowed = size_t{76} - longest - OPTION_DESC_GAP; auto fiter = format.begin(); - for (const auto& o : group->second.options) - { + for (const auto& o : group->second.options) { if (o.is_container && m_positional_set.find(o.l) != m_positional_set.end() && - !m_show_positional) - { + !m_show_positional) { continue; } auto d = format_description(o, longest + OPTION_DESC_GAP, allowed); result += fiter->first; - if (stringLength(fiter->first) > longest) - { + if (stringLength(fiter->first) > longest) { result += '\n'; result += toLocalString(std::string(longest + OPTION_DESC_GAP, ' ')); - } - else - { - result += toLocalString(std::string(longest + OPTION_DESC_GAP - - stringLength(fiter->first), - ' ')); + } else { + result += toLocalString(std::string( + longest + OPTION_DESC_GAP - stringLength(fiter->first), ' ')); } result += d; result += '\n'; @@ -1987,50 +1367,35 @@ Options::help_one_group(const std::string& g) const return result; } -inline -void -Options::generate_group_help -( - String& result, - const std::vector& print_groups -) const -{ - for (size_t i = 0; i != print_groups.size(); ++i) - { +inline void Options::generate_group_help( + String& result, const std::vector& print_groups) const { + for (size_t i = 0; i != print_groups.size(); ++i) { const String& group_help_text = help_one_group(print_groups[i]); - if (empty(group_help_text)) - { + if (empty(group_help_text)) { continue; } result += group_help_text; - if (i < print_groups.size() - 1) - { + if (i < print_groups.size() - 1) { result += '\n'; } } } -inline -void -Options::generate_all_groups_help(String& result) const -{ +inline void Options::generate_all_groups_help(String& result) const { std::vector all_groups; all_groups.reserve(m_help.size()); - for (auto& group : m_help) - { + for (auto& group : m_help) { all_groups.push_back(group.first); } generate_group_help(result, all_groups); } -inline -std::string -Options::help(const std::vector& help_groups) const -{ - String result = m_help_string + "\nUsage:\n " + - toLocalString(m_program) + " " + toLocalString(m_custom_help); +inline std::string Options::help( + const std::vector& help_groups) const { + String result = m_help_string + "\nUsage:\n " + toLocalString(m_program) + + " " + toLocalString(m_custom_help); if (m_positional.size() > 0 && m_positional_help.size() > 0) { result += " " + toLocalString(m_positional_help); @@ -2038,44 +1403,32 @@ Options::help(const std::vector& help_groups) const result += "\n\n"; - if (help_groups.size() == 0) - { + if (help_groups.size() == 0) { generate_all_groups_help(result); - } - else - { + } else { generate_group_help(result, help_groups); } return toUTF8String(result); } -inline -const std::vector -Options::groups() const -{ +inline const std::vector Options::groups() const { std::vector g; std::transform( - m_help.begin(), - m_help.end(), - std::back_inserter(g), - [] (const std::map::value_type& pair) - { - return pair.first; - } - ); + m_help.begin(), m_help.end(), std::back_inserter(g), + [](const std::map::value_type& pair) { + return pair.first; + }); return g; } -inline -const HelpGroupDetails& -Options::group_help(const std::string& group) const -{ +inline const HelpGroupDetails& Options::group_help( + const std::string& group) const { return m_help.at(group); } -} +} // namespace cxxopts -#endif //CXXOPTS_HPP_INCLUDED +#endif // CXXOPTS_HPP_INCLUDED diff --git a/check/TestCAPI.c b/check/TestCAPI.c index 53702a320a..e5848f67a1 100644 --- a/check/TestCAPI.c +++ b/check/TestCAPI.c @@ -1,8 +1,8 @@ -#include "interfaces/highs_c_api.h" - -#include "HCheckConfig.h" #include #include + +#include "HCheckConfig.h" +#include "interfaces/highs_c_api.h" // Force asserts to be checked always. #undef NDEBUG #include @@ -12,19 +12,24 @@ const HighsInt dev_run = 0; const double double_equal_tolerance = 1e-5; -void checkGetCallbackDataOutPointer(const HighsCallbackDataOut* data_out, const char* name, HighsInt valid) { +void checkGetCallbackDataOutPointer(const HighsCallbackDataOut* data_out, + const char* name, HighsInt valid) { const void* name_p = Highs_getCallbackDataOutItem(data_out, name); if (valid) { - if (!name_p) printf("checkGetCallbackDataOutItem fail for %s (valid = %d)\n", name, (int)valid); + if (!name_p) + printf("checkGetCallbackDataOutItem fail for %s (valid = %d)\n", name, + (int)valid); assert(name_p); } else { - if (name_p) printf("checkGetCallbackDataOutItem fail for %s (valid = %d)\n", - name, (int)valid); + if (name_p) + printf("checkGetCallbackDataOutItem fail for %s (valid = %d)\n", name, + (int)valid); assert(!name_p); } } - -void checkGetCallbackDataOutHighsInt(const HighsCallbackDataOut* data_out, const char* name, HighsInt value) { + +void checkGetCallbackDataOutHighsInt(const HighsCallbackDataOut* data_out, + const char* name, HighsInt value) { const void* name_p = Highs_getCallbackDataOutItem(data_out, name); if (!name_p) { printf("checkGetCallbackDataOutItem fail for %s\n", name); @@ -32,13 +37,17 @@ void checkGetCallbackDataOutHighsInt(const HighsCallbackDataOut* data_out, const } else { HighsInt check_value = *(HighsInt*)(name_p); HighsInt value_ok = check_value == value; - if (!value_ok) printf("checkGetCallbackDataOutItem fail for %s (%d = check_value != value = %d)\n", - name, (int)check_value, (int)value); + if (!value_ok) + printf( + "checkGetCallbackDataOutItem fail for %s (%d = check_value != value " + "= %d)\n", + name, (int)check_value, (int)value); assert(value_ok); } } - -void checkGetCallbackDataOutInt(const HighsCallbackDataOut* data_out, const char* name, int value) { + +void checkGetCallbackDataOutInt(const HighsCallbackDataOut* data_out, + const char* name, int value) { const void* name_p = Highs_getCallbackDataOutItem(data_out, name); if (!name_p) { printf("checkGetCallbackDataOutInt fail for %s\n", name); @@ -46,13 +55,17 @@ void checkGetCallbackDataOutInt(const HighsCallbackDataOut* data_out, const char } else { int check_value = *(int*)(name_p); int value_ok = check_value == value; - if (!value_ok) printf("checkGetCallbackDataOutInt fail for %s (%d = check_value != value = %d)\n", - name, check_value, value); + if (!value_ok) + printf( + "checkGetCallbackDataOutInt fail for %s (%d = check_value != value = " + "%d)\n", + name, check_value, value); assert(value_ok); } } - -void checkGetCallbackDataOutInt64(const HighsCallbackDataOut* data_out, const char* name, int64_t value) { + +void checkGetCallbackDataOutInt64(const HighsCallbackDataOut* data_out, + const char* name, int64_t value) { const void* name_p = Highs_getCallbackDataOutItem(data_out, name); if (!name_p) { printf("checkGetCallbackDataOutInt64 fail for %s\n", name); @@ -60,13 +73,17 @@ void checkGetCallbackDataOutInt64(const HighsCallbackDataOut* data_out, const ch } else { int64_t check_value = *(int*)(name_p); int value_ok = check_value == value; - if (!value_ok) printf("checkGetCallbackDataOutInt64 fail for %s (%d = check_value != value = %d)\n", - name, (int)check_value, (int)value); + if (!value_ok) + printf( + "checkGetCallbackDataOutInt64 fail for %s (%d = check_value != value " + "= %d)\n", + name, (int)check_value, (int)value); assert(value_ok); } } - -void checkGetCallbackDataOutDouble(const HighsCallbackDataOut* data_out, const char* name, double value) { + +void checkGetCallbackDataOutDouble(const HighsCallbackDataOut* data_out, + const char* name, double value) { const void* name_p = Highs_getCallbackDataOutItem(data_out, name); if (!name_p) { printf("checkGetCallbackDataOutDouble fail for %s\n", name); @@ -74,64 +91,67 @@ void checkGetCallbackDataOutDouble(const HighsCallbackDataOut* data_out, const c } else { double check_value = *(double*)(name_p); double value_ok = check_value == value; - if (!value_ok) printf("checkGetCallbackDataOutDouble fail for %s (%g = check_value != value = %g)\n", - name, check_value, value); + if (!value_ok) + printf( + "checkGetCallbackDataOutDouble fail for %s (%g = check_value != " + "value = %g)\n", + name, check_value, value); assert(value_ok); } } - + static void userCallback(const int callback_type, const char* message, - const HighsCallbackDataOut* data_out, - HighsCallbackDataIn* data_in, - void* user_callback_data) { + const HighsCallbackDataOut* data_out, + HighsCallbackDataIn* data_in, + void* user_callback_data) { // Extract the double value pointed to from void* user_callback_data - const double local_callback_data = user_callback_data == NULL ? -1 : *(double*)user_callback_data; + const double local_callback_data = + user_callback_data == NULL ? -1 : *(double*)user_callback_data; if (callback_type == kHighsCallbackLogging) { - if (dev_run) printf("userCallback(%11.4g): %s\n", local_callback_data, message); + if (dev_run) + printf("userCallback(%11.4g): %s\n", local_callback_data, message); } else if (callback_type == kHighsCallbackMipImprovingSolution) { // Test the accessor function for data_out // // Check that passing an valid name returns a non-null pointer, // and that the corresponding value is the same as obtained using // the struct - const void* objective_function_value_p = - Highs_getCallbackDataOutItem(data_out, kHighsCallbackDataOutObjectiveFunctionValueName); + const void* objective_function_value_p = Highs_getCallbackDataOutItem( + data_out, kHighsCallbackDataOutObjectiveFunctionValueName); assert(objective_function_value_p); double objective_function_value = *(double*)(objective_function_value_p); assert(objective_function_value == data_out->objective_function_value); - if (dev_run) printf("userCallback(%11.4g): improving solution with objective = %g\n", - local_callback_data, objective_function_value); + if (dev_run) + printf("userCallback(%11.4g): improving solution with objective = %g\n", + local_callback_data, objective_function_value); // Now test all more simply - checkGetCallbackDataOutInt(data_out, - kHighsCallbackDataOutLogTypeName, -1); - checkGetCallbackDataOutDouble(data_out, - kHighsCallbackDataOutRunningTimeName, - data_out->running_time); - checkGetCallbackDataOutHighsInt(data_out, - kHighsCallbackDataOutSimplexIterationCountName, - data_out->simplex_iteration_count); + checkGetCallbackDataOutInt(data_out, kHighsCallbackDataOutLogTypeName, -1); + checkGetCallbackDataOutDouble( + data_out, kHighsCallbackDataOutRunningTimeName, data_out->running_time); + checkGetCallbackDataOutHighsInt( + data_out, kHighsCallbackDataOutSimplexIterationCountName, + data_out->simplex_iteration_count); checkGetCallbackDataOutHighsInt(data_out, - kHighsCallbackDataOutIpmIterationCountName, - data_out->ipm_iteration_count); + kHighsCallbackDataOutIpmIterationCountName, + data_out->ipm_iteration_count); checkGetCallbackDataOutHighsInt(data_out, - kHighsCallbackDataOutPdlpIterationCountName, - data_out->pdlp_iteration_count); - checkGetCallbackDataOutDouble(data_out, - kHighsCallbackDataOutObjectiveFunctionValueName, - data_out->objective_function_value); + kHighsCallbackDataOutPdlpIterationCountName, + data_out->pdlp_iteration_count); + checkGetCallbackDataOutDouble( + data_out, kHighsCallbackDataOutObjectiveFunctionValueName, + data_out->objective_function_value); checkGetCallbackDataOutInt64(data_out, - kHighsCallbackDataOutMipNodeCountName, - data_out->mip_node_count); - checkGetCallbackDataOutDouble(data_out, - kHighsCallbackDataOutMipPrimalBoundName, - data_out->mip_primal_bound); + kHighsCallbackDataOutMipNodeCountName, + data_out->mip_node_count); checkGetCallbackDataOutDouble(data_out, - kHighsCallbackDataOutMipDualBoundName, - data_out->mip_dual_bound); + kHighsCallbackDataOutMipPrimalBoundName, + data_out->mip_primal_bound); checkGetCallbackDataOutDouble(data_out, - kHighsCallbackDataOutMipGapName, - data_out->mip_gap); + kHighsCallbackDataOutMipDualBoundName, + data_out->mip_dual_bound); + checkGetCallbackDataOutDouble(data_out, kHighsCallbackDataOutMipGapName, + data_out->mip_gap); // Cutpool data structure is not assigned, so num_col, num_cut and // num_nz are unassigned // checkGetCallbackDataOutHighsInt(data_out, @@ -147,68 +167,83 @@ static void userCallback(const int callback_type, const char* message, // Check that passing the name of an assigned vector returns // non-NULL, and that the corresponding value is the same as // obtained using the struct - const void* mip_solution_void_p = - Highs_getCallbackDataOutItem(data_out, - kHighsCallbackDataOutMipSolutionName); + const void* mip_solution_void_p = Highs_getCallbackDataOutItem( + data_out, kHighsCallbackDataOutMipSolutionName); assert(mip_solution_void_p); double mip_solution0 = *(double*)(mip_solution_void_p); assert(mip_solution0 == *(data_out->mip_solution)); - if (dev_run) printf("userCallback(%11.4g): improving solution with value[0] = %g\n", - local_callback_data, mip_solution0); + if (dev_run) + printf("userCallback(%11.4g): improving solution with value[0] = %g\n", + local_callback_data, mip_solution0); // Cutpool data structure is not assigned, so cannot check that // passing names of the unassigned vectors returns NULL - // assert(!Highs_getCallbackDataOutItem(data_out, kHighsCallbackDataOutCutpoolStartName)); - // assert(!Highs_getCallbackDataOutItem(data_out, kHighsCallbackDataOutCutpoolIndexName)); - // assert(!Highs_getCallbackDataOutItem(data_out, kHighsCallbackDataOutCutpoolValueName)); - // assert(!Highs_getCallbackDataOutItem(data_out, kHighsCallbackDataOutCutpoolLowerName)); - // assert(!Highs_getCallbackDataOutItem(data_out, kHighsCallbackDataOutCutpoolUpperName)); + // assert(!Highs_getCallbackDataOutItem(data_out, + // kHighsCallbackDataOutCutpoolStartName)); + // assert(!Highs_getCallbackDataOutItem(data_out, + // kHighsCallbackDataOutCutpoolIndexName)); + // assert(!Highs_getCallbackDataOutItem(data_out, + // kHighsCallbackDataOutCutpoolValueName)); + // assert(!Highs_getCallbackDataOutItem(data_out, + // kHighsCallbackDataOutCutpoolLowerName)); + // assert(!Highs_getCallbackDataOutItem(data_out, + // kHighsCallbackDataOutCutpoolUpperName)); } else if (callback_type == kHighsCallbackMipLogging) { - if (dev_run) printf("userCallback(%11.4g): MIP logging\n", local_callback_data); + if (dev_run) + printf("userCallback(%11.4g): MIP logging\n", local_callback_data); data_in->user_interrupt = 1; } else if (callback_type == kHighsCallbackMipInterrupt) { - if (dev_run) printf("userCallback(%11.4g): MIP interrupt\n", local_callback_data); + if (dev_run) + printf("userCallback(%11.4g): MIP interrupt\n", local_callback_data); data_in->user_interrupt = 1; } } -HighsInt highsIntArraysEqual(const HighsInt dim, const HighsInt* array0, const HighsInt* array1) { - for (HighsInt ix = 0; ix < dim; ix++) if (array0[ix] != array1[ix]) return 0; +HighsInt highsIntArraysEqual(const HighsInt dim, const HighsInt* array0, + const HighsInt* array1) { + for (HighsInt ix = 0; ix < dim; ix++) + if (array0[ix] != array1[ix]) return 0; return 1; } -HighsInt doubleArraysEqual(const double dim, const double* array0, const double* array1) { - for (HighsInt ix = 0; ix < dim; ix++) if (array0[ix] != array1[ix]) return 0; +HighsInt doubleArraysEqual(const double dim, const double* array0, + const double* array1) { + for (HighsInt ix = 0; ix < dim; ix++) + if (array0[ix] != array1[ix]) return 0; return 1; } -void assertDoubleValuesEqual(const char* name, const double is, const double should_be) { - const double dl = fabs(is-should_be); +void assertDoubleValuesEqual(const char* name, const double is, + const double should_be) { + const double dl = fabs(is - should_be); if (dl > double_equal_tolerance) { - printf("Value %s = %g differs from %g by %g but should be equal\n", name, is, should_be, dl); - assert(1==0); + printf("Value %s = %g differs from %g by %g but should be equal\n", name, + is, should_be, dl); + assert(1 == 0); } } -void assertIntValuesEqual(const char* name, const HighsInt is, const HighsInt should_be) { +void assertIntValuesEqual(const char* name, const HighsInt is, + const HighsInt should_be) { if (is != should_be) { - printf("Value %s = %"HIGHSINT_FORMAT" should be %"HIGHSINT_FORMAT"\n", name, is, should_be); - assert(1==0); + printf("Value %s = %" HIGHSINT_FORMAT " should be %" HIGHSINT_FORMAT "\n", + name, is, should_be); + assert(1 == 0); } } void assertLogical(const char* name, const HighsInt is) { if (is == 0) { - printf("Value %s = %"HIGHSINT_FORMAT" should not be 0\n", name, is); - assert(1==0); + printf("Value %s = %" HIGHSINT_FORMAT " should not be 0\n", name, is); + assert(1 == 0); } } void version_api() { if (dev_run) { printf("HiGHS version %s\n", Highs_version()); - printf("HiGHS version major %"HIGHSINT_FORMAT"\n", Highs_versionMajor()); - printf("HiGHS version minor %"HIGHSINT_FORMAT"\n", Highs_versionMinor()); - printf("HiGHS version patch %"HIGHSINT_FORMAT"\n", Highs_versionPatch()); + printf("HiGHS version major %" HIGHSINT_FORMAT "\n", Highs_versionMajor()); + printf("HiGHS version minor %" HIGHSINT_FORMAT "\n", Highs_versionMinor()); + printf("HiGHS version patch %" HIGHSINT_FORMAT "\n", Highs_versionPatch()); printf("HiGHS githash: %s\n", Highs_githash()); // Compilation date is deprecated. // printf("HiGHS compilation date %s\n", Highs_compilationDate()); @@ -299,31 +334,34 @@ void minimal_api_lp() { HighsInt model_status; - HighsInt return_status = Highs_lpCall(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, - col_value, col_dual, row_value, row_dual, - col_basis_status, row_basis_status, - &model_status); + HighsInt return_status = + Highs_lpCall(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, col_value, col_dual, row_value, row_dual, + col_basis_status, row_basis_status, &model_status); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); if (dev_run) { - printf("Run status = %"HIGHSINT_FORMAT"; Model status = %"HIGHSINT_FORMAT"\n", return_status, model_status); - + printf("Run status = %" HIGHSINT_FORMAT "; Model status = %" HIGHSINT_FORMAT + "\n", + return_status, model_status); + HighsInt i; if (model_status == kHighsModelStatusOptimal) { double objective_value = 0; // Report the column primal and dual values, and basis status for (i = 0; i < num_col; i++) { - printf("Col%"HIGHSINT_FORMAT" = %lf; dual = %lf; status = %"HIGHSINT_FORMAT"; \n", - i, col_value[i], col_dual[i], col_basis_status[i]); - objective_value += col_value[i]*col_cost[i]; + printf("Col%" HIGHSINT_FORMAT + " = %lf; dual = %lf; status = %" HIGHSINT_FORMAT "; \n", + i, col_value[i], col_dual[i], col_basis_status[i]); + objective_value += col_value[i] * col_cost[i]; } // Report the row primal and dual values, and basis status for (i = 0; i < num_row; i++) { - printf("Row%"HIGHSINT_FORMAT" = %lf; dual = %lf; status = %"HIGHSINT_FORMAT"; \n", - i, row_value[i], row_dual[i], row_basis_status[i]); + printf("Row%" HIGHSINT_FORMAT + " = %lf; dual = %lf; status = %" HIGHSINT_FORMAT "; \n", + i, row_value[i], row_dual[i], row_basis_status[i]); } printf("Optimal objective value = %g\n", objective_value); } @@ -358,14 +396,15 @@ void minimal_api_mip() { double col_upper[3] = {1.0e30, 1.0e30, 1.0}; // Define the row lower bounds and upper bounds double row_lower[2] = {-1.0e30, 12.0}; - double row_upper[2] = { 7.0, 12.0}; + double row_upper[2] = {7.0, 12.0}; // Define the constraint matrix column-wise HighsInt a_start[3] = {0, 2, 4}; HighsInt a_index[6] = {0, 1, 0, 1, 0, 1}; double a_value[6] = {1.0, 4.0, 1.0, 2.0, 1.0, 1.0}; // Give an illegal value to an entry in integrality - HighsInt integrality[3] = {kHighsVarTypeContinuous, kHighsVarTypeContinuous, -1}; + HighsInt integrality[3] = {kHighsVarTypeContinuous, kHighsVarTypeContinuous, + -1}; double* col_value = (double*)malloc(sizeof(double) * num_col); double* row_value = (double*)malloc(sizeof(double) * num_row); @@ -373,42 +412,40 @@ void minimal_api_mip() { HighsInt model_status; HighsInt return_status; - return_status = Highs_mipCall(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, - integrality, - col_value, row_value, - &model_status); + return_status = Highs_mipCall( + 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, integrality, + col_value, row_value, &model_status); // Should return error, with model status not set - assert( return_status == kHighsStatusError ); - assert( model_status == kHighsModelStatusNotset ); + assert(return_status == kHighsStatusError); + assert(model_status == kHighsModelStatusNotset); // Correct integrality - integrality[num_col-1] = kHighsVarTypeInteger; - - return_status = Highs_mipCall(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, - integrality, - col_value, row_value, - &model_status); + integrality[num_col - 1] = kHighsVarTypeInteger; + + return_status = Highs_mipCall( + 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, integrality, + col_value, row_value, &model_status); // Should return OK - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); if (dev_run) { - printf("Run status = %"HIGHSINT_FORMAT"; Model status = %"HIGHSINT_FORMAT"\n", return_status, model_status); - + printf("Run status = %" HIGHSINT_FORMAT "; Model status = %" HIGHSINT_FORMAT + "\n", + return_status, model_status); + HighsInt i; if (model_status == kHighsModelStatusOptimal) { double objective_value = 0; // Report the column primal values for (i = 0; i < num_col; i++) { - printf("Col%"HIGHSINT_FORMAT" = %lf; \n", i, col_value[i]); - objective_value += col_value[i]*col_cost[i]; + printf("Col%" HIGHSINT_FORMAT " = %lf; \n", i, col_value[i]); + objective_value += col_value[i] * col_cost[i]; } // Report the row primal values for (i = 0; i < num_row; i++) { - printf("Row%"HIGHSINT_FORMAT" = %lf; \n", i, row_value[i]); + printf("Row%" HIGHSINT_FORMAT " = %lf; \n", i, row_value[i]); } printf("Optimal objective value = %g\n", objective_value); } @@ -416,7 +453,6 @@ void minimal_api_mip() { free(col_value); free(row_value); - } void minimal_api_qp() { @@ -445,20 +481,23 @@ void minimal_api_qp() { HighsInt q_start[3] = {0, 2, 3}; HighsInt q_index[4] = {0, 2, 1, 2}; double q_value[4] = {2.0, -1.0, 0.2, 2.0}; - + double* col_value = (double*)malloc(sizeof(double) * num_col); HighsInt model_status; - HighsInt return_status = Highs_qpCall(num_col, num_row, num_nz, q_num_nz, a_format, q_format, sense, offset, - col_cost, col_lower, col_upper, row_lower, row_upper, - a_start, a_index, a_value, q_start, q_index, q_value, - col_value, NULL, NULL, NULL, NULL, NULL, &model_status); - assert( return_status == kHighsStatusOk ); - assertIntValuesEqual("Model status for QP qph", model_status, kHighsModelStatusOptimal); + HighsInt return_status = Highs_qpCall( + num_col, num_row, num_nz, q_num_nz, a_format, q_format, sense, offset, + col_cost, col_lower, col_upper, row_lower, row_upper, a_start, a_index, + a_value, q_start, q_index, q_value, col_value, NULL, NULL, NULL, NULL, + NULL, &model_status); + assert(return_status == kHighsStatusOk); + assertIntValuesEqual("Model status for QP qph", model_status, + kHighsModelStatusOptimal); double required_x[3] = {0.5, 5.0, 1.5}; if (dev_run) { for (HighsInt iCol = 0; iCol < num_col; iCol++) { printf("x%d1 = %g\n", (int)iCol, col_value[iCol]); - assertDoubleValuesEqual("Solution value for QP qph", col_value[iCol], required_x[iCol]); + assertDoubleValuesEqual("Solution value for QP qph", col_value[iCol], + required_x[iCol]); } } free(col_value); @@ -478,19 +517,17 @@ void minimal_api_illegal_lp() { double row_lower[1] = {-inf}; double row_upper[1] = {2}; HighsInt a_start[1] = {0}; - HighsInt a_index[2] = {0, -1}; // Illegal index + HighsInt a_index[2] = {0, -1}; // Illegal index double a_value[2] = {1.0, 1.0}; HighsInt model_status; - HighsInt return_status = Highs_lpCall(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, - NULL, NULL, NULL, NULL, - NULL, NULL, - &model_status); + HighsInt return_status = + Highs_lpCall(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, NULL, NULL, NULL, NULL, NULL, NULL, &model_status); // Should return error, with model status not set - assert( return_status == kHighsStatusError ); - assert( model_status == kHighsModelStatusNotset ); + assert(return_status == kHighsStatusError); + assert(model_status == kHighsModelStatusNotset); } void full_api() { @@ -513,13 +550,13 @@ void full_api() { HighsInt a_index[4] = {0, 1, 0, 1}; double a_value[4] = {1.0, 2.0, 1.0, 3.0}; - assert( Highs_addCols(highs, 2, cc, cl, cu, 0, NULL, NULL, NULL) == 0); - assert( Highs_addRows(highs, 2, rl, ru, 4, a_start, a_index, a_value) == 0); + assert(Highs_addCols(highs, 2, cc, cl, cu, 0, NULL, NULL, NULL) == 0); + assert(Highs_addRows(highs, 2, rl, ru, 4, a_start, a_index, a_value) == 0); - assert( Highs_getNumCols(highs) == num_col); - assert( Highs_getNumRows(highs) == num_row); - assert( Highs_getNumNz(highs) == num_nz); - assert( Highs_getHessianNumNz(highs) == 0); + assert(Highs_getNumCols(highs) == num_col); + assert(Highs_getNumRows(highs) == num_row); + assert(Highs_getNumNz(highs) == num_nz); + assert(Highs_getHessianNumNz(highs) == 0); HighsInt ck_num_col; HighsInt ck_num_row; @@ -537,50 +574,48 @@ void full_api() { HighsInt ck_a_index[4]; double ck_a_value[4]; HighsInt return_status; - return_status = Highs_getModel(highs, a_format, 0, - &ck_num_col, &ck_num_row, &ck_num_nz, NULL, - &ck_sense, &ck_offset, - ck_cc, ck_cl, ck_cu, ck_rl, ck_ru, - ck_a_start, ck_a_index, ck_a_value, - NULL, NULL, NULL, NULL); - assert( return_status == kHighsStatusOk ); - - assert( ck_num_col == num_col ); - assert( ck_num_row == num_row ); - assert( ck_num_nz == num_nz ); - assert( ck_sense == sense ); - assert( ck_offset == offset ); - assert( doubleArraysEqual(num_col, ck_cc, cc) ); - assert( doubleArraysEqual(num_col, ck_cl, cl) ); - assert( doubleArraysEqual(num_col, ck_cu, cu) ); - assert( doubleArraysEqual(num_row, ck_rl, rl) ); - assert( doubleArraysEqual(num_row, ck_ru, ru) ); - assert( highsIntArraysEqual(num_col, ck_a_start, a_start) ); - assert( highsIntArraysEqual(num_nz, ck_a_index, a_index) ); - assert( doubleArraysEqual(num_nz, ck_a_value, a_value) ); + return_status = Highs_getModel( + highs, a_format, 0, &ck_num_col, &ck_num_row, &ck_num_nz, NULL, &ck_sense, + &ck_offset, ck_cc, ck_cl, ck_cu, ck_rl, ck_ru, ck_a_start, ck_a_index, + ck_a_value, NULL, NULL, NULL, NULL); + assert(return_status == kHighsStatusOk); + + assert(ck_num_col == num_col); + assert(ck_num_row == num_row); + assert(ck_num_nz == num_nz); + assert(ck_sense == sense); + assert(ck_offset == offset); + assert(doubleArraysEqual(num_col, ck_cc, cc)); + assert(doubleArraysEqual(num_col, ck_cl, cl)); + assert(doubleArraysEqual(num_col, ck_cu, cu)); + assert(doubleArraysEqual(num_row, ck_rl, rl)); + assert(doubleArraysEqual(num_row, ck_ru, ru)); + assert(highsIntArraysEqual(num_col, ck_a_start, a_start)); + assert(highsIntArraysEqual(num_nz, ck_a_index, a_index)); + assert(doubleArraysEqual(num_nz, ck_a_value, a_value)); return_status = Highs_run(highs); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); char* col_prefix = "Col"; char* row_prefix = "Row"; // Check index out of bounds return_status = Highs_passColName(highs, -1, col_prefix); - assert( return_status == kHighsStatusError ); + assert(return_status == kHighsStatusError); return_status = Highs_passColName(highs, num_col, col_prefix); - assert( return_status == kHighsStatusError ); + assert(return_status == kHighsStatusError); return_status = Highs_passRowName(highs, -1, row_prefix); - assert( return_status == kHighsStatusError ); + assert(return_status == kHighsStatusError); return_status = Highs_passRowName(highs, num_row, row_prefix); - assert( return_status == kHighsStatusError ); + assert(return_status == kHighsStatusError); // Define all column names to be the same for (HighsInt iCol = 0; iCol < num_col; iCol++) { return_status = Highs_passColName(highs, iCol, col_prefix); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); } return_status = Highs_writeModel(highs, ""); - assert( return_status == kHighsStatusError ); + assert(return_status == kHighsStatusError); // Define all column names to be different for (HighsInt iCol = 0; iCol < num_col; iCol++) { @@ -590,32 +625,32 @@ void full_api() { sprintf(name, "%s%" HIGHSINT_FORMAT "", col_prefix, iCol); const char* name_p = name; return_status = Highs_passColName(highs, iCol, name_p); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); } return_status = Highs_writeModel(highs, ""); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); // Check that the columns can be found by name HighsInt ck_iCol; for (HighsInt iCol = 0; iCol < num_col; iCol++) { char name[5]; return_status = Highs_getColName(highs, iCol, name); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); return_status = Highs_getColByName(highs, name, &ck_iCol); - assert( return_status == kHighsStatusOk ); - assert( ck_iCol == iCol ); + assert(return_status == kHighsStatusOk); + assert(ck_iCol == iCol); } return_status = Highs_getColByName(highs, "FRED", &ck_iCol); - assert( return_status == kHighsStatusError ); - + assert(return_status == kHighsStatusError); + // Define all row names to be the same for (HighsInt iRow = 0; iRow < num_row; iRow++) { return_status = Highs_passRowName(highs, iRow, row_prefix); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); } return_status = Highs_writeModel(highs, ""); - assert( return_status == kHighsStatusError ); - + assert(return_status == kHighsStatusError); + // Define all row names to be different for (HighsInt iRow = 0; iRow < num_row; iRow++) { const char suffix = iRow + '0'; @@ -624,38 +659,40 @@ void full_api() { sprintf(name, "%s%" HIGHSINT_FORMAT "", row_prefix, iRow); const char* name_p = name; return_status = Highs_passRowName(highs, iRow, name_p); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); } return_status = Highs_writeModel(highs, ""); - assert( return_status == kHighsStatusOk ); - + assert(return_status == kHighsStatusOk); + // Check that the rows can be found by name HighsInt ck_iRow; for (HighsInt iRow = 0; iRow < num_row; iRow++) { char name[5]; return_status = Highs_getRowName(highs, iRow, name); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); return_status = Highs_getRowByName(highs, name, &ck_iRow); - assert( return_status == kHighsStatusOk ); - assert( ck_iRow == iRow ); + assert(return_status == kHighsStatusOk); + assert(ck_iRow == iRow); } return_status = Highs_getRowByName(highs, "FRED", &ck_iRow); - assert( return_status == kHighsStatusError ); - + assert(return_status == kHighsStatusError); + for (HighsInt iCol = 0; iCol < num_col; iCol++) { char name[5]; char* name_p = name; return_status = Highs_getColName(highs, iCol, name_p); - assert( return_status == kHighsStatusOk ); - if (dev_run) printf("Column %" HIGHSINT_FORMAT " has name %s\n", iCol, name_p); + assert(return_status == kHighsStatusOk); + if (dev_run) + printf("Column %" HIGHSINT_FORMAT " has name %s\n", iCol, name_p); } - + for (HighsInt iRow = 0; iRow < num_row; iRow++) { char name[5]; char* name_p = name; return_status = Highs_getRowName(highs, iRow, name_p); - assert( return_status == kHighsStatusOk ); - if (dev_run) printf("Row %" HIGHSINT_FORMAT " has name %s\n", iRow, name_p); + assert(return_status == kHighsStatusOk); + if (dev_run) + printf("Row %" HIGHSINT_FORMAT " has name %s\n", iRow, name_p); } Highs_destroy(highs); @@ -670,114 +707,122 @@ void full_api_options() { const double kHighsInf = Highs_getInfinity(highs); HighsInt simplex_scale_strategy; HighsInt return_status; - return_status = Highs_getIntOptionValue(highs, "simplex_scale_strategy", &simplex_scale_strategy); - assert( return_status == kHighsStatusOk ); + return_status = Highs_getIntOptionValue(highs, "simplex_scale_strategy", + &simplex_scale_strategy); + assert(return_status == kHighsStatusOk); if (dev_run) - printf("simplex_scale_strategy = %"HIGHSINT_FORMAT": setting it to 3\n", simplex_scale_strategy); + printf("simplex_scale_strategy = %" HIGHSINT_FORMAT ": setting it to 3\n", + simplex_scale_strategy); simplex_scale_strategy = 3; - return_status = Highs_setIntOptionValue(highs, "simplex_scale_strategy", simplex_scale_strategy); + return_status = Highs_setIntOptionValue(highs, "simplex_scale_strategy", + simplex_scale_strategy); const HighsInt presolve_index = 0; char* name = NULL; return_status = Highs_getOptionName(highs, presolve_index, &name); - if (dev_run) printf("option %"HIGHSINT_FORMAT" has name %s\n", presolve_index, name); + if (dev_run) + printf("option %" HIGHSINT_FORMAT " has name %s\n", presolve_index, name); const char* presolve = "presolve"; - assert( *name == *presolve ); + assert(*name == *presolve); free(name); HighsInt check_simplex_scale_strategy; HighsInt min_simplex_scale_strategy; HighsInt max_simplex_scale_strategy; HighsInt default_simplex_scale_strategy; - return_status = Highs_getIntOptionValues(highs, "scale_strategy", NULL, NULL, NULL, NULL); - assert( return_status == kHighsStatusError ); - return_status = Highs_getDoubleOptionValues(highs, "simplex_scale_strategy", NULL, NULL, NULL, NULL); - assert( return_status == kHighsStatusError ); - return_status = Highs_getIntOptionValues(highs, "simplex_scale_strategy", - &check_simplex_scale_strategy, - &min_simplex_scale_strategy, - &max_simplex_scale_strategy, - &default_simplex_scale_strategy); - assert( return_status == kHighsStatusOk ); - assert( check_simplex_scale_strategy == simplex_scale_strategy ); - assert( min_simplex_scale_strategy == 0 ); - assert( max_simplex_scale_strategy == 5 ); - assert( default_simplex_scale_strategy == 1 ); - + return_status = + Highs_getIntOptionValues(highs, "scale_strategy", NULL, NULL, NULL, NULL); + assert(return_status == kHighsStatusError); + return_status = Highs_getDoubleOptionValues(highs, "simplex_scale_strategy", + NULL, NULL, NULL, NULL); + assert(return_status == kHighsStatusError); + return_status = Highs_getIntOptionValues( + highs, "simplex_scale_strategy", &check_simplex_scale_strategy, + &min_simplex_scale_strategy, &max_simplex_scale_strategy, + &default_simplex_scale_strategy); + assert(return_status == kHighsStatusOk); + assert(check_simplex_scale_strategy == simplex_scale_strategy); + assert(min_simplex_scale_strategy == 0); + assert(max_simplex_scale_strategy == 5); + assert(default_simplex_scale_strategy == 1); // There are some functions to check what type of option value you should // provide. HighsInt option_type; - return_status = Highs_getOptionType(highs, "simplex_scale_strategy", &option_type); - assert( return_status == kHighsStatusOk ); - assert( option_type == kHighsOptionTypeInt ); + return_status = + Highs_getOptionType(highs, "simplex_scale_strategy", &option_type); + assert(return_status == kHighsStatusOk); + assert(option_type == kHighsOptionTypeInt); return_status = Highs_getOptionType(highs, "bad_option", &option_type); - assert( return_status == kHighsStatusError ); + assert(return_status == kHighsStatusError); double primal_feasibility_tolerance; - return_status = Highs_getDoubleOptionValue(highs, "primal_feasibility_tolerance", &primal_feasibility_tolerance); - assert( return_status == kHighsStatusOk ); + return_status = Highs_getDoubleOptionValue( + highs, "primal_feasibility_tolerance", &primal_feasibility_tolerance); + assert(return_status == kHighsStatusOk); if (dev_run) - printf("primal_feasibility_tolerance = %g: setting it to 1e-6\n", primal_feasibility_tolerance); + printf("primal_feasibility_tolerance = %g: setting it to 1e-6\n", + primal_feasibility_tolerance); primal_feasibility_tolerance = 1e-6; - return_status = Highs_setDoubleOptionValue(highs, "primal_feasibility_tolerance", primal_feasibility_tolerance); - assert( return_status == kHighsStatusOk ); + return_status = Highs_setDoubleOptionValue( + highs, "primal_feasibility_tolerance", primal_feasibility_tolerance); + assert(return_status == kHighsStatusOk); double check_primal_feasibility_tolerance; - return_status = Highs_getDoubleOptionValues(highs, "primal_feasibility_tolerance", - &check_primal_feasibility_tolerance, NULL, NULL, NULL); - assert( return_status == kHighsStatusOk ); - assert( check_primal_feasibility_tolerance == primal_feasibility_tolerance ); + return_status = Highs_getDoubleOptionValues( + highs, "primal_feasibility_tolerance", + &check_primal_feasibility_tolerance, NULL, NULL, NULL); + assert(return_status == kHighsStatusOk); + assert(check_primal_feasibility_tolerance == primal_feasibility_tolerance); double default_primal_feasibility_tolerance; double min_primal_feasibility_tolerance; double max_primal_feasibility_tolerance; - return_status = Highs_getDoubleOptionValues(highs, "primal_feasibility_tolerance", - &check_primal_feasibility_tolerance, - &min_primal_feasibility_tolerance, - &max_primal_feasibility_tolerance, - &default_primal_feasibility_tolerance); - assert( min_primal_feasibility_tolerance == 1e-10 ); - assert( max_primal_feasibility_tolerance == kHighsInf ); - assert( default_primal_feasibility_tolerance == 1e-7 ); + return_status = Highs_getDoubleOptionValues( + highs, "primal_feasibility_tolerance", + &check_primal_feasibility_tolerance, &min_primal_feasibility_tolerance, + &max_primal_feasibility_tolerance, &default_primal_feasibility_tolerance); + assert(min_primal_feasibility_tolerance == 1e-10); + assert(max_primal_feasibility_tolerance == kHighsInf); + assert(default_primal_feasibility_tolerance == 1e-7); Highs_setStringOptionValue(highs, "presolve", "off"); return_status = Highs_getStringOptionValues(highs, "pre-solve", NULL, NULL); - assert( return_status == kHighsStatusError ); + assert(return_status == kHighsStatusError); // char check_presolve_value[kHighsMaximumStringLength]; char check_presolve_value[512]; - return_status = Highs_getStringOptionValues(highs, "presolve", check_presolve_value, NULL); - assert( return_status == kHighsStatusOk ); + return_status = Highs_getStringOptionValues(highs, "presolve", + check_presolve_value, NULL); + assert(return_status == kHighsStatusOk); // const HighsInt output_flag = 1; - // return_status = Highs_setBoolOptionValue(highs, "output_flag", output_flag); + // return_status = Highs_setBoolOptionValue(highs, "output_flag", + // output_flag); return_status = Highs_setBoolOptionValue(highs, "output_flag", 1); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); HighsInt check_output_flag, default_output_flag; return_status = Highs_getBoolOptionValues(highs, "output_flag", NULL, NULL); - assert( return_status == kHighsStatusOk ); - return_status = Highs_getBoolOptionValues(highs, "output_flag", - &check_output_flag, NULL); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); + return_status = + Highs_getBoolOptionValues(highs, "output_flag", &check_output_flag, NULL); + assert(return_status == kHighsStatusOk); // assert( check_output_flag == output_flag ); - assert( check_output_flag == 1 ); - return_status = Highs_getBoolOptionValues(highs, "output_flag", - &check_output_flag, - &default_output_flag); - assert( return_status == kHighsStatusOk ); + assert(check_output_flag == 1); + return_status = Highs_getBoolOptionValues( + highs, "output_flag", &check_output_flag, &default_output_flag); + assert(return_status == kHighsStatusOk); // assert( default_output_flag == output_flag ); - assert( default_output_flag == 1 ); - + assert(default_output_flag == 1); + HighsInt num_string_option = 0; char* option = NULL; HighsInt type; HighsInt num_options = Highs_getNumOptions(highs); char current_string_value[512]; - - if (dev_run) - printf("\nString options are:\n"); + + if (dev_run) printf("\nString options are:\n"); for (HighsInt index = 0; index < num_options; index++) { Highs_getOptionName(highs, index, &option); Highs_getOptionType(highs, option, &type); @@ -788,13 +833,12 @@ void full_api_options() { Highs_getStringOptionValues(highs, option, current_string_value, NULL); num_string_option++; if (dev_run) - printf("%"HIGHSINT_FORMAT": %-24s \"%s\"\n", - num_string_option, option, current_string_value); + printf("%" HIGHSINT_FORMAT ": %-24s \"%s\"\n", num_string_option, option, + current_string_value); free(option); } Highs_destroy(highs); - } void full_api_lp() { @@ -836,34 +880,35 @@ void full_api_lp() { HighsInt* row_basis_status = (HighsInt*)malloc(sizeof(HighsInt) * num_row); // Add two columns to the empty LP - assert( Highs_addCols(highs, num_col, col_cost, col_lower, col_upper, 0, NULL, NULL, NULL) == 0); + assert(Highs_addCols(highs, num_col, col_cost, col_lower, col_upper, 0, NULL, + NULL, NULL) == 0); // Add three rows to the 2-column LP - assert( Highs_addRows(highs, num_row, row_lower, row_upper, num_nz, arstart, arindex, arvalue) == 0); + assert(Highs_addRows(highs, num_row, row_lower, row_upper, num_nz, arstart, + arindex, arvalue) == 0); HighsInt sense; HighsInt return_status; return_status = Highs_getObjectiveSense(highs, &sense); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); if (dev_run) - printf("LP problem has objective sense = %"HIGHSINT_FORMAT"\n", sense); - assert( sense == kHighsObjSenseMinimize ); + printf("LP problem has objective sense = %" HIGHSINT_FORMAT "\n", sense); + assert(sense == kHighsObjSenseMinimize); sense *= -1; return_status = Highs_changeObjectiveSense(highs, sense); - assert( return_status == kHighsStatusOk ); - assert( sense == kHighsObjSenseMaximize ); + assert(return_status == kHighsStatusOk); + assert(sense == kHighsObjSenseMaximize); sense *= -1; return_status = Highs_changeObjectiveSense(highs, sense); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); return_status = Highs_getObjectiveSense(highs, &sense); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); if (dev_run) - printf("LP problem has old objective sense = %"HIGHSINT_FORMAT"\n", sense); - assert( sense == kHighsObjSenseMinimize ); - - + printf("LP problem has old objective sense = %" HIGHSINT_FORMAT "\n", + sense); + assert(sense == kHighsObjSenseMinimize); // fetch column data (just first column) { @@ -875,18 +920,21 @@ void full_api_lp() { double* get_upper = (double*)malloc(sizeof(double) * num_get_col); HighsInt get_num_nz = 0; - return_status = Highs_getColsByRange(highs, get_col, get_col, - &get_num_col, get_costs, get_lower, get_upper, &get_num_nz, - NULL, NULL, NULL); - assert( return_status == kHighsStatusOk ); + return_status = Highs_getColsByRange(highs, get_col, get_col, &get_num_col, + get_costs, get_lower, get_upper, + &get_num_nz, NULL, NULL, NULL); + assert(return_status == kHighsStatusOk); assertIntValuesEqual("getCols get_num_col", get_num_col, num_get_col); - assertDoubleValuesEqual("getCols get_costs", get_costs[0], col_cost[get_col]); - assertDoubleValuesEqual("getCols get_lower", get_lower[0], col_lower[get_col]); - assertDoubleValuesEqual("getCols get_upper", get_upper[0], col_upper[get_col]); + assertDoubleValuesEqual("getCols get_costs", get_costs[0], + col_cost[get_col]); + assertDoubleValuesEqual("getCols get_lower", get_lower[0], + col_lower[get_col]); + assertDoubleValuesEqual("getCols get_upper", get_upper[0], + col_upper[get_col]); assertIntValuesEqual("getCols get_num_nz", get_num_nz, 2); - // could also check coefficients by calling again... + // could also check coefficients by calling again... free(get_upper); free(get_lower); @@ -904,72 +952,77 @@ void full_api_lp() { assertIntValuesEqual("getNumRows", Highs_getNumRows(highs), num_row); - return_status = Highs_getRowsByRange(highs, get_row, get_row, - &get_num_row, get_lower, get_upper, &get_num_nz, - NULL, NULL, NULL); - assert( return_status == kHighsStatusOk ); + return_status = + Highs_getRowsByRange(highs, get_row, get_row, &get_num_row, get_lower, + get_upper, &get_num_nz, NULL, NULL, NULL); + assert(return_status == kHighsStatusOk); assertIntValuesEqual("getRows get_num_row", get_num_row, num_get_row); - assertDoubleValuesEqual("getRows get_lower", get_lower[0], row_lower[get_row]); - assertDoubleValuesEqual("getRows get_upper", get_upper[0], row_upper[get_row]); + assertDoubleValuesEqual("getRows get_lower", get_lower[0], + row_lower[get_row]); + assertDoubleValuesEqual("getRows get_upper", get_upper[0], + row_upper[get_row]); assertIntValuesEqual("getRows get_num_nz", get_num_nz, 2); - // could also check coefficients by calling again... + // could also check coefficients by calling again... free(get_upper); free(get_lower); } - - - - return_status = Highs_setBoolOptionValue(highs, "output_flag", 0); - assert( return_status == kHighsStatusOk ); - if (dev_run) - printf("Running quietly...\n"); + assert(return_status == kHighsStatusOk); + if (dev_run) printf("Running quietly...\n"); return_status = Highs_run(highs); - assert( return_status == kHighsStatusOk ); - if (dev_run) - printf("Running loudly...\n"); + assert(return_status == kHighsStatusOk); + if (dev_run) printf("Running loudly...\n"); // Get the model status HighsInt model_status = Highs_getModelStatus(highs); if (dev_run) - printf("Run status = %"HIGHSINT_FORMAT"; Model status = %"HIGHSINT_FORMAT"\n", return_status, model_status); + printf("Run status = %" HIGHSINT_FORMAT "; Model status = %" HIGHSINT_FORMAT + "\n", + return_status, model_status); double objective_function_value; - return_status = Highs_getDoubleInfoValue(highs, "objective_function_value", &objective_function_value); - assert( return_status == kHighsStatusOk ); + return_status = Highs_getDoubleInfoValue(highs, "objective_function_value", + &objective_function_value); + assert(return_status == kHighsStatusOk); HighsInt simplex_iteration_count; - return_status = Highs_getIntInfoValue(highs, "simplex_iteration_count", &simplex_iteration_count); - assert( return_status == kHighsStatusOk ); + return_status = Highs_getIntInfoValue(highs, "simplex_iteration_count", + &simplex_iteration_count); + assert(return_status == kHighsStatusOk); HighsInt primal_solution_status; - return_status = Highs_getIntInfoValue(highs, "primal_solution_status", &primal_solution_status); - assert( return_status == kHighsStatusOk ); + return_status = Highs_getIntInfoValue(highs, "primal_solution_status", + &primal_solution_status); + assert(return_status == kHighsStatusOk); HighsInt dual_solution_status; - return_status = Highs_getIntInfoValue(highs, "dual_solution_status", &dual_solution_status); - assert( return_status == kHighsStatusOk ); + return_status = Highs_getIntInfoValue(highs, "dual_solution_status", + &dual_solution_status); + assert(return_status == kHighsStatusOk); if (dev_run) { - printf("Objective value = %g; Iteration count = %"HIGHSINT_FORMAT"\n", - objective_function_value, simplex_iteration_count); + printf("Objective value = %g; Iteration count = %" HIGHSINT_FORMAT "\n", + objective_function_value, simplex_iteration_count); if (model_status == kHighsModelStatusOptimal) { // Get the primal and dual solution - return_status = Highs_getSolution(highs, col_value, col_dual, row_value, row_dual); - assert( return_status == kHighsStatusOk ); + return_status = + Highs_getSolution(highs, col_value, col_dual, row_value, row_dual); + assert(return_status == kHighsStatusOk); // Get the basis return_status = Highs_getBasis(highs, col_basis_status, row_basis_status); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); // Report the column primal and dual values, and basis status for (HighsInt iCol = 0; iCol < num_col; iCol++) - printf("Col%"HIGHSINT_FORMAT" = %lf; dual = %lf; status = %"HIGHSINT_FORMAT"; \n", - iCol, col_value[iCol], col_dual[iCol], col_basis_status[iCol]); + printf("Col%" HIGHSINT_FORMAT + " = %lf; dual = %lf; status = %" HIGHSINT_FORMAT "; \n", + iCol, col_value[iCol], col_dual[iCol], col_basis_status[iCol]); // Report the row primal and dual values, and basis status for (HighsInt iRow = 0; iRow < num_row; iRow++) - printf("Row%"HIGHSINT_FORMAT" = %lf; dual = %lf; status = %"HIGHSINT_FORMAT"; \n", - iRow, row_value[iRow], row_dual[iRow], row_basis_status[iRow]); + printf("Row%" HIGHSINT_FORMAT + " = %lf; dual = %lf; status = %" HIGHSINT_FORMAT "; \n", + iRow, row_value[iRow], row_dual[iRow], row_basis_status[iRow]); } } free(col_value); @@ -990,17 +1043,18 @@ void full_api_lp() { double a_value[5] = {1.0, 2.0, 1.0, 2.0, 1.0}; highs = Highs_create(); if (!dev_run) Highs_setBoolOptionValue(highs, "output_flag", 0); - 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_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 ); + assert(return_status == kHighsStatusOk); model_status = Highs_getModelStatus(highs); - assert( model_status == kHighsModelStatusOptimal ); + assert(model_status == kHighsModelStatusOptimal); if (dev_run) - printf("Run status = %"HIGHSINT_FORMAT"; Model status = %"HIGHSINT_FORMAT"\n", return_status, model_status); + printf("Run status = %" HIGHSINT_FORMAT "; Model status = %" HIGHSINT_FORMAT + "\n", + return_status, model_status); Highs_destroy(highs); } @@ -1026,13 +1080,14 @@ void full_api_mip() { double col_upper[3] = {1.0e30, 1.0e30, 1.0}; // Define the row lower bounds and upper bounds double row_lower[2] = {-1.0e30, 12.0}; - double row_upper[2] = { 7.0, 12.0}; + double row_upper[2] = {7.0, 12.0}; // Define the constraint matrix column-wise HighsInt a_start[3] = {0, 2, 4}; HighsInt a_index[6] = {0, 1, 0, 1, 0, 1}; double a_value[6] = {1.0, 4.0, 1.0, 2.0, 1.0, 1.0}; - HighsInt integrality[3] = {kHighsVarTypeInteger, kHighsVarTypeInteger, kHighsVarTypeInteger}; + HighsInt integrality[3] = {kHighsVarTypeInteger, kHighsVarTypeInteger, + kHighsVarTypeInteger}; double* col_value = (double*)malloc(sizeof(double) * num_col); double* row_value = (double*)malloc(sizeof(double) * num_row); @@ -1042,10 +1097,10 @@ void full_api_mip() { void* highs = Highs_create(); if (!dev_run) Highs_setBoolOptionValue(highs, "output_flag", 0); - return_status = Highs_passMip(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, - integrality); + return_status = + Highs_passMip(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, integrality); assert(return_status == kHighsStatusOk); Highs_setStringOptionValue(highs, "presolve", "off"); return_status = Highs_run(highs); @@ -1056,30 +1111,31 @@ void full_api_mip() { #ifdef HIGHSINT64 required_return_status = kHighsStatusOk; #endif - return_status = Highs_getIntInfoValue(highs, "mip_node_count", &mip_node_count_int); + return_status = + Highs_getIntInfoValue(highs, "mip_node_count", &mip_node_count_int); assert(return_status == required_return_status); int64_t mip_node_count; - return_status = Highs_getInt64InfoValue(highs, "mip_node_count", &mip_node_count); - assert( return_status == kHighsStatusOk ); - assert( mip_node_count == 1 ); + return_status = + Highs_getInt64InfoValue(highs, "mip_node_count", &mip_node_count); + assert(return_status == kHighsStatusOk); + assert(mip_node_count == 1); // Test Highs_getColIntegrality HighsInt col_integrality; return_status = Highs_getColIntegrality(highs, -1, &col_integrality); - assert( return_status == kHighsStatusError ); + assert(return_status == kHighsStatusError); return_status = Highs_getColIntegrality(highs, num_col, &col_integrality); - assert( return_status == kHighsStatusError ); + assert(return_status == kHighsStatusError); for (HighsInt iCol = 0; iCol < num_col; iCol++) { return_status = Highs_getColIntegrality(highs, iCol, &col_integrality); - assert( return_status == kHighsStatusOk ); - assert( col_integrality == 1 ); + assert(return_status == kHighsStatusOk); + assert(col_integrality == 1); } Highs_destroy(highs); free(col_value); free(row_value); - } void full_api_qp() { @@ -1099,12 +1155,12 @@ void full_api_qp() { HighsInt num_col = 0; return_status = Highs_addCol(highs, 1.0, -inf, inf, 0, NULL, NULL); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); num_col++; double offset = 0.25; return_status = Highs_changeObjectiveOffset(highs, offset); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); HighsInt q_dim = 1; HighsInt q_num_nz = 1; @@ -1115,23 +1171,26 @@ void full_api_qp() { q_start[0] = 0; q_index[0] = 0; q_value[0] = 2.0; - return_status = Highs_passHessian(highs, q_dim, q_num_nz, q_format, q_start, q_index, q_value); - assert( return_status == kHighsStatusOk ); + return_status = Highs_passHessian(highs, q_dim, q_num_nz, q_format, q_start, + q_index, q_value); + assert(return_status == kHighsStatusOk); if (dev_run) Highs_writeModel(highs, ""); return_status = Highs_run(highs); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); model_status = Highs_getModelStatus(highs); - assertIntValuesEqual("Model status for 1-d QP", model_status, kHighsModelStatusOptimal); + assertIntValuesEqual("Model status for 1-d QP", model_status, + kHighsModelStatusOptimal); required_objective_function_value = 0; required_x0 = -0.5; objective_function_value = Highs_getObjectiveValue(highs); - assertDoubleValuesEqual("Objective", objective_function_value, required_objective_function_value); + assertDoubleValuesEqual("Objective", objective_function_value, + required_objective_function_value); double* col_solution = (double*)malloc(sizeof(double) * num_col); return_status = Highs_getSolution(highs, col_solution, NULL, NULL, NULL); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); assertDoubleValuesEqual("x0", col_solution[0], required_x0); if (dev_run) Highs_writeSolutionPretty(highs, ""); @@ -1139,15 +1198,17 @@ void full_api_qp() { // // Add the variable return_status = Highs_addCol(highs, -1.0, -inf, inf, 0, NULL, NULL); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); num_col++; // Can solve the model before the Hessian has been replaced return_status = Highs_run(highs); - assert( return_status == kHighsStatusOk ); - assertIntValuesEqual("Run status for 2-d QP with OK Hessian", return_status, 0); + assert(return_status == kHighsStatusOk); + assertIntValuesEqual("Run status for 2-d QP with OK Hessian", return_status, + 0); model_status = Highs_getModelStatus(highs); - assertIntValuesEqual("Model status for this 2-d QP with OK Hessian", model_status, kHighsModelStatusUnbounded); + assertIntValuesEqual("Model status for this 2-d QP with OK Hessian", + model_status, kHighsModelStatusUnbounded); free(q_start); free(q_index); @@ -1165,26 +1226,29 @@ void full_api_qp() { q_start[1] = 1; q_index[1] = 1; q_value[1] = 2.0; - return_status = Highs_passHessian(highs, q_dim, q_num_nz, q_format, q_start, q_index, q_value); - assert( return_status == kHighsStatusOk ); + return_status = Highs_passHessian(highs, q_dim, q_num_nz, q_format, q_start, + q_index, q_value); + assert(return_status == kHighsStatusOk); if (dev_run) Highs_writeModel(highs, ""); return_status = Highs_run(highs); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); model_status = Highs_getModelStatus(highs); - assertIntValuesEqual("Model status for 2-d QP", model_status, kHighsModelStatusOptimal); - + assertIntValuesEqual("Model status for 2-d QP", model_status, + kHighsModelStatusOptimal); + required_objective_function_value = -0.25; required_x1 = 0.5; objective_function_value = Highs_getObjectiveValue(highs); - assertDoubleValuesEqual("Objective", objective_function_value, required_objective_function_value); + assertDoubleValuesEqual("Objective", objective_function_value, + required_objective_function_value); free(col_solution); col_solution = (double*)malloc(sizeof(double) * num_col); return_status = Highs_getSolution(highs, col_solution, NULL, NULL, NULL); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); assertDoubleValuesEqual("x0", col_solution[0], required_x0); assertDoubleValuesEqual("x1", col_solution[1], required_x1); @@ -1194,59 +1258,63 @@ void full_api_qp() { double check_offset; return_status = Highs_getObjectiveOffset(highs, &check_offset); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); assertDoubleValuesEqual("Offset", check_offset, offset); double dl_offset = -objective_function_value; offset += dl_offset; return_status = Highs_changeObjectiveOffset(highs, offset); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); required_objective_function_value += dl_offset; objective_function_value = Highs_getObjectiveValue(highs); - assertDoubleValuesEqual("Objective with new offset", objective_function_value, required_objective_function_value); + assertDoubleValuesEqual("Objective with new offset", objective_function_value, + required_objective_function_value); // Add the constraint 0.5 <= x0 + x1 HighsInt a_index[2] = {0, 1}; double a_value[2] = {1, 1}; return_status = Highs_addRow(highs, 0.5, inf, 2, a_index, a_value); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); if (dev_run) Highs_writeModel(highs, ""); return_status = Highs_run(highs); - assert( return_status == kHighsStatusOk ); - assertIntValuesEqual("Run status for 2-d QP with constraint", return_status, kHighsStatusOk); - + assert(return_status == kHighsStatusOk); + assertIntValuesEqual("Run status for 2-d QP with constraint", return_status, + kHighsStatusOk); + model_status = Highs_getModelStatus(highs); - assertIntValuesEqual("Model status for 2-d QP with constraint", model_status, kHighsModelStatusOptimal); + assertIntValuesEqual("Model status for 2-d QP with constraint", model_status, + kHighsModelStatusOptimal); required_objective_function_value = 0.125; required_x0 = -0.25; required_x1 = 0.75; objective_function_value = Highs_getObjectiveValue(highs); - assertDoubleValuesEqual("Objective", objective_function_value, required_objective_function_value); + assertDoubleValuesEqual("Objective", objective_function_value, + required_objective_function_value); return_status = Highs_getSolution(highs, col_solution, NULL, NULL, NULL); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); assertDoubleValuesEqual("x0", col_solution[0], required_x0); assertDoubleValuesEqual("x1", col_solution[1], required_x1); // Add bounds to make the QP infeasible return_status = Highs_changeColBounds(highs, 0, -inf, 0); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); return_status = Highs_changeColBounds(highs, 1, -inf, 0); - assert( return_status == kHighsStatusOk ); - + assert(return_status == kHighsStatusOk); + if (dev_run) Highs_writeModel(highs, ""); return_status = Highs_run(highs); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); assertIntValuesEqual("Run status for infeasible 2-d QP", return_status, 0); - + model_status = Highs_getModelStatus(highs); assertIntValuesEqual("Model status for infeasible 2-d QP", model_status, 8); - assert( model_status == kHighsModelStatusInfeasible ); + assert(model_status == kHighsModelStatusInfeasible); Highs_destroy(highs); @@ -1254,7 +1322,6 @@ void full_api_qp() { free(q_index); free(q_value); free(col_solution); - } void pass_presolve_get_lp() { @@ -1271,7 +1338,7 @@ void pass_presolve_get_lp() { const double kHighsInf = Highs_getInfinity(highs); HighsInt model_status; HighsInt return_status; - + Highs_setBoolOptionValue(highs, "output_flag", dev_run); HighsInt a_format = kHighsMatrixFormatColwise; HighsInt sense = kHighsObjSenseMinimize; @@ -1292,72 +1359,81 @@ void pass_presolve_get_lp() { HighsInt a_index[5] = {1, 2, 0, 1, 2}; double a_value[5] = {1.0, 2.0, 1.0, 2.0, 1.0}; - 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_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_presolve(highs); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); for (HighsInt k = 0; k < 2; k++) { // Loop twice: once for col-wise; once for row-wise HighsInt presolved_num_col = Highs_getPresolvedNumCol(highs); HighsInt presolved_num_row = Highs_getPresolvedNumRow(highs); HighsInt presolved_num_nz = Highs_getPresolvedNumNz(highs); - HighsInt presolved_a_format = k == 0 ? kHighsMatrixFormatColwise : kHighsMatrixFormatRowwise; + HighsInt presolved_a_format = + k == 0 ? kHighsMatrixFormatColwise : kHighsMatrixFormatRowwise; HighsInt presolved_sense; double presolved_offset; - double* presolved_col_cost = (double*)malloc(sizeof(double) * presolved_num_col); - double* presolved_col_lower = (double*)malloc(sizeof(double) * presolved_num_col); - double* presolved_col_upper = (double*)malloc(sizeof(double) * presolved_num_col); - double* presolved_row_lower = (double*)malloc(sizeof(double) * presolved_num_row); - double* presolved_row_upper = (double*)malloc(sizeof(double) * presolved_num_row); - HighsInt* presolved_a_start = (HighsInt*)malloc(sizeof(HighsInt) * (presolved_num_col+1)); - HighsInt* presolved_a_index = (HighsInt*)malloc(sizeof(HighsInt) * presolved_num_nz); - double* presolved_a_value = (double*)malloc(sizeof(double) * presolved_num_nz); - - return_status = Highs_getPresolvedLp(highs, presolved_a_format, - &presolved_num_col, &presolved_num_row, &presolved_num_nz, - &presolved_sense, &presolved_offset, - presolved_col_cost, presolved_col_lower, presolved_col_upper, - presolved_row_lower, presolved_row_upper, - presolved_a_start, presolved_a_index, presolved_a_value, NULL); - assert( return_status == kHighsStatusOk ); + double* presolved_col_cost = + (double*)malloc(sizeof(double) * presolved_num_col); + double* presolved_col_lower = + (double*)malloc(sizeof(double) * presolved_num_col); + double* presolved_col_upper = + (double*)malloc(sizeof(double) * presolved_num_col); + double* presolved_row_lower = + (double*)malloc(sizeof(double) * presolved_num_row); + double* presolved_row_upper = + (double*)malloc(sizeof(double) * presolved_num_row); + HighsInt* presolved_a_start = + (HighsInt*)malloc(sizeof(HighsInt) * (presolved_num_col + 1)); + HighsInt* presolved_a_index = + (HighsInt*)malloc(sizeof(HighsInt) * presolved_num_nz); + double* presolved_a_value = + (double*)malloc(sizeof(double) * presolved_num_nz); + + return_status = Highs_getPresolvedLp( + highs, presolved_a_format, &presolved_num_col, &presolved_num_row, + &presolved_num_nz, &presolved_sense, &presolved_offset, + presolved_col_cost, presolved_col_lower, presolved_col_upper, + presolved_row_lower, presolved_row_upper, presolved_a_start, + presolved_a_index, presolved_a_value, NULL); + assert(return_status == kHighsStatusOk); // Solve the presolved LP within a local version of HiGHS void* local_highs; local_highs = Highs_create(); Highs_setBoolOptionValue(local_highs, "output_flag", dev_run); Highs_setStringOptionValue(local_highs, "presolve", "off"); - return_status = Highs_passLp(local_highs, - presolved_num_col, presolved_num_row, presolved_num_nz, - presolved_a_format, presolved_sense, presolved_offset, - presolved_col_cost, presolved_col_lower, presolved_col_upper, - presolved_row_lower, presolved_row_upper, - presolved_a_start, presolved_a_index, presolved_a_value); - assert( return_status == kHighsStatusOk ); + return_status = Highs_passLp( + local_highs, presolved_num_col, presolved_num_row, presolved_num_nz, + presolved_a_format, presolved_sense, presolved_offset, + presolved_col_cost, presolved_col_lower, presolved_col_upper, + presolved_row_lower, presolved_row_upper, presolved_a_start, + presolved_a_index, presolved_a_value); + assert(return_status == kHighsStatusOk); return_status = Highs_run(local_highs); - + double* col_value = (double*)malloc(sizeof(double) * num_col); double* col_dual = (double*)malloc(sizeof(double) * num_col); double* row_dual = (double*)malloc(sizeof(double) * num_row); - return_status = Highs_getSolution(local_highs, col_value, col_dual, NULL, row_dual); - assert( return_status == kHighsStatusOk ); + return_status = + Highs_getSolution(local_highs, col_value, col_dual, NULL, row_dual); + assert(return_status == kHighsStatusOk); return_status = Highs_postsolve(highs, col_value, col_dual, row_dual); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); model_status = Highs_getModelStatus(highs); - assert( model_status == kHighsModelStatusOptimal ); - + assert(model_status == kHighsModelStatusOptimal); + // With just the primal solution, optimality cannot be determined return_status = Highs_postsolve(highs, col_value, NULL, NULL); - assert( return_status == kHighsStatusWarning ); + assert(return_status == kHighsStatusWarning); model_status = Highs_getModelStatus(highs); - assert( model_status == kHighsModelStatusUnknown ); + assert(model_status == kHighsModelStatusUnknown); free(presolved_col_cost); free(presolved_col_lower); @@ -1370,8 +1446,6 @@ void pass_presolve_get_lp() { free(col_value); free(col_dual); free(row_dual); - - } } @@ -1382,20 +1456,22 @@ void options() { HighsInt simplex_scale_strategy; HighsInt return_status; return_status = Highs_setIntOptionValue(highs, "simplex_scale_strategy", 0); - assert( return_status == kHighsStatusOk ); - return_status = Highs_getIntOptionValue(highs, "simplex_scale_strategy", &simplex_scale_strategy); - assert( return_status == kHighsStatusOk ); - assert( simplex_scale_strategy == 0 ); + assert(return_status == kHighsStatusOk); + return_status = Highs_getIntOptionValue(highs, "simplex_scale_strategy", + &simplex_scale_strategy); + assert(return_status == kHighsStatusOk); + assert(simplex_scale_strategy == 0); double primal_feasibility_tolerance; - return_status = Highs_setDoubleOptionValue(highs, "primal_feasibility_tolerance", 2.0); - assert( return_status == kHighsStatusOk ); - return_status = Highs_getDoubleOptionValue(highs, "primal_feasibility_tolerance", &primal_feasibility_tolerance); - assert( return_status == kHighsStatusOk ); - assert( primal_feasibility_tolerance == 2.0 ); + return_status = + Highs_setDoubleOptionValue(highs, "primal_feasibility_tolerance", 2.0); + assert(return_status == kHighsStatusOk); + return_status = Highs_getDoubleOptionValue( + highs, "primal_feasibility_tolerance", &primal_feasibility_tolerance); + assert(return_status == kHighsStatusOk); + assert(primal_feasibility_tolerance == 2.0); Highs_destroy(highs); - } void test_getColsByRange() { @@ -1403,32 +1479,33 @@ void test_getColsByRange() { if (!dev_run) Highs_setBoolOptionValue(highs, "output_flag", 0); HighsInt return_status; return_status = Highs_addCol(highs, -1.0, 0.0, 1.0, 0, NULL, NULL); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); return_status = Highs_addCol(highs, -1.0, 0.0, 1.0, 0, NULL, NULL); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); HighsInt a_index[2] = {0, 1}; double a_value[2] = {1.0, -1.0}; return_status = Highs_addRow(highs, 0.0, 0.0, 2, a_index, a_value); - assert( return_status == kHighsStatusOk ); + assert(return_status == kHighsStatusOk); HighsInt num_cols; HighsInt num_nz; HighsInt matrix_start[2] = {-1, -1}; - return_status = Highs_getColsByRange(highs, 0, 1, &num_cols, NULL, NULL, NULL, &num_nz, - matrix_start, NULL, NULL); - assert( return_status == kHighsStatusOk ); - assert( num_cols == 2 ); - assert( num_nz == 2 ); - assert( matrix_start[0] == 0 ); - assert( matrix_start[1] == 1 ); + return_status = Highs_getColsByRange(highs, 0, 1, &num_cols, NULL, NULL, NULL, + &num_nz, matrix_start, NULL, NULL); + assert(return_status == kHighsStatusOk); + assert(num_cols == 2); + assert(num_nz == 2); + assert(matrix_start[0] == 0); + assert(matrix_start[1] == 1); HighsInt matrix_index[2] = {-1, -1}; double matrix_value[2] = {0.0, 0.0}; - return_status = Highs_getColsByRange(highs, 0, 1, &num_cols, NULL, NULL, NULL, &num_nz, - matrix_start, matrix_index, matrix_value); - assert( return_status == kHighsStatusOk ); - assert( matrix_index[0] == 0 ); - assert( matrix_index[1] == 0 ); - assert( matrix_value[0] == 1.0 ); - assert( matrix_value[1] == -1.0 ); + return_status = + Highs_getColsByRange(highs, 0, 1, &num_cols, NULL, NULL, NULL, &num_nz, + matrix_start, matrix_index, matrix_value); + assert(return_status == kHighsStatusOk); + assert(matrix_index[0] == 0); + assert(matrix_index[1] == 0); + assert(matrix_value[0] == 1.0); + assert(matrix_value[1] == -1.0); Highs_destroy(highs); } @@ -1449,12 +1526,14 @@ void test_passHessian() { const double optimal_objective_value = 1; const double primal = 1; const double dual = 0; - assertIntValuesEqual("Status", Highs_getModelStatus(highs), kHighsModelStatusOptimal); // kOptimal + assertIntValuesEqual("Status", Highs_getModelStatus(highs), + kHighsModelStatusOptimal); // kOptimal double col_value[1] = {-123.0}; double col_dual[1] = {0.0}; Highs_getSolution(highs, col_value, col_dual, NULL, NULL); double objective_value = Highs_getObjectiveValue(highs); - assertDoubleValuesEqual("Objective", objective_value, optimal_objective_value); + assertDoubleValuesEqual("Objective", objective_value, + optimal_objective_value); assertDoubleValuesEqual("Primal", col_value[0], primal); assertDoubleValuesEqual("Dual", col_dual[0], dual); @@ -1462,7 +1541,6 @@ void test_passHessian() { } void test_ranging() { - void* highs = Highs_create(); if (!dev_run) Highs_setBoolOptionValue(highs, "output_flag", 0); // @@ -1485,7 +1563,7 @@ void test_ranging() { // Cost ranging // c0 2 -1 1 0 // c1 0 0 inf inf - // + // // Bound ranging // Columns // c0 1 -inf inf 1 @@ -1520,43 +1598,57 @@ void test_ranging() { double* row_bound_dn_objective = (double*)malloc(sizeof(double) * num_row); HighsInt* row_bound_dn_in_var = (HighsInt*)malloc(sizeof(HighsInt) * num_row); HighsInt* row_bound_dn_ou_var = (HighsInt*)malloc(sizeof(HighsInt) * num_row); - HighsInt status = - Highs_getRanging(highs, - // - col_cost_up_value, col_cost_up_objective, col_cost_up_in_var, col_cost_up_ou_var, - col_cost_dn_value, col_cost_dn_objective, col_cost_dn_in_var, col_cost_dn_ou_var, - col_bound_up_value, col_bound_up_objective, col_bound_up_in_var, col_bound_up_ou_var, - col_bound_dn_value, col_bound_dn_objective, col_bound_dn_in_var, col_bound_dn_ou_var, - row_bound_up_value, row_bound_up_objective, row_bound_up_in_var, row_bound_up_ou_var, - row_bound_dn_value, row_bound_dn_objective, row_bound_dn_in_var, row_bound_dn_ou_var); + HighsInt status = Highs_getRanging( + highs, + // + col_cost_up_value, col_cost_up_objective, col_cost_up_in_var, + col_cost_up_ou_var, col_cost_dn_value, col_cost_dn_objective, + col_cost_dn_in_var, col_cost_dn_ou_var, col_bound_up_value, + col_bound_up_objective, col_bound_up_in_var, col_bound_up_ou_var, + col_bound_dn_value, col_bound_dn_objective, col_bound_dn_in_var, + col_bound_dn_ou_var, row_bound_up_value, row_bound_up_objective, + row_bound_up_in_var, row_bound_up_ou_var, row_bound_dn_value, + row_bound_dn_objective, row_bound_dn_in_var, row_bound_dn_ou_var); assert(status == kHighsStatusOk); - assertDoubleValuesEqual("col_cost_dn_objective[0]", col_cost_dn_objective[0], 2); + assertDoubleValuesEqual("col_cost_dn_objective[0]", col_cost_dn_objective[0], + 2); assertDoubleValuesEqual("col_cost_dn_value[0]", col_cost_dn_value[0], -1); assertDoubleValuesEqual("col_cost_up_value[0]", col_cost_up_value[0], 1); - assertDoubleValuesEqual("col_cost_up_objective[0]", col_cost_up_objective[0], 0); - assertDoubleValuesEqual("col_cost_dn_objective[1]", col_cost_dn_objective[1], 0); + assertDoubleValuesEqual("col_cost_up_objective[0]", col_cost_up_objective[0], + 0); + assertDoubleValuesEqual("col_cost_dn_objective[1]", col_cost_dn_objective[1], + 0); assertDoubleValuesEqual("col_cost_dn_value[1]", col_cost_dn_value[1], 0); assertDoubleValuesEqual("col_cost_up_value[1]", col_cost_up_value[1], inf); - assertDoubleValuesEqual("col_cost_up_objective[1]", col_cost_up_objective[1], inf); + assertDoubleValuesEqual("col_cost_up_objective[1]", col_cost_up_objective[1], + inf); - assertDoubleValuesEqual("col_bound_dn_objective[0]", col_bound_dn_objective[0], 1); + assertDoubleValuesEqual("col_bound_dn_objective[0]", + col_bound_dn_objective[0], 1); assertDoubleValuesEqual("col_bound_dn_value[0]", col_bound_dn_value[0], -inf); assertDoubleValuesEqual("col_bound_up_value[0]", col_bound_up_value[0], inf); - assertDoubleValuesEqual("col_bound_up_objective[0]", col_bound_up_objective[0], 1); - assertDoubleValuesEqual("col_bound_dn_objective[1]", col_bound_dn_objective[1], 1); + assertDoubleValuesEqual("col_bound_up_objective[0]", + col_bound_up_objective[0], 1); + assertDoubleValuesEqual("col_bound_dn_objective[1]", + col_bound_dn_objective[1], 1); assertDoubleValuesEqual("col_bound_dn_value[1]", col_bound_dn_value[1], 1); assertDoubleValuesEqual("col_bound_up_value[1]", col_bound_up_value[1], inf); - assertDoubleValuesEqual("col_bound_up_objective[1]", col_bound_up_objective[1], 1); + assertDoubleValuesEqual("col_bound_up_objective[1]", + col_bound_up_objective[1], 1); - assertDoubleValuesEqual("row_bound_dn_objective[0]", row_bound_dn_objective[0], -inf); + assertDoubleValuesEqual("row_bound_dn_objective[0]", + row_bound_dn_objective[0], -inf); assertDoubleValuesEqual("row_bound_dn_value[0]", row_bound_dn_value[0], -inf); assertDoubleValuesEqual("row_bound_up_value[0]", row_bound_up_value[0], inf); - assertDoubleValuesEqual("row_bound_up_objective[0]", row_bound_up_objective[0], inf); - assertDoubleValuesEqual("row_bound_dn_objective[1]", row_bound_dn_objective[1], -inf); + assertDoubleValuesEqual("row_bound_up_objective[0]", + row_bound_up_objective[0], inf); + assertDoubleValuesEqual("row_bound_dn_objective[1]", + row_bound_dn_objective[1], -inf); assertDoubleValuesEqual("row_bound_dn_value[1]", row_bound_dn_value[1], -inf); assertDoubleValuesEqual("row_bound_up_value[1]", row_bound_up_value[1], inf); - assertDoubleValuesEqual("row_bound_up_objective[1]", row_bound_up_objective[1], inf); + assertDoubleValuesEqual("row_bound_up_objective[1]", + row_bound_up_objective[1], inf); free(col_cost_up_value); free(col_cost_up_objective); @@ -1592,7 +1684,7 @@ void test_feasibilityRelaxation() { const double kHighsInf = Highs_getInfinity(highs); Highs_setBoolOptionValue(highs, "output_flag", dev_run); HighsInt return_status; - + HighsInt num_col = 2; HighsInt num_row = 3; HighsInt num_nz = 6; @@ -1610,23 +1702,24 @@ void test_feasibilityRelaxation() { HighsInt integrality[2] = {kHighsVarTypeInteger, kHighsVarTypeInteger}; Highs_passMip(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, - integrality); + col_cost, col_lower, col_upper, row_lower, row_upper, a_start, + a_index, a_value, integrality); Highs_feasibilityRelaxation(highs, 1, 1, 1, NULL, NULL, NULL); double objective_function_value; - Highs_getDoubleInfoValue(highs, "objective_function_value", &objective_function_value); + Highs_getDoubleInfoValue(highs, "objective_function_value", + &objective_function_value); double* col_value = (double*)malloc(sizeof(double) * num_col); double* col_dual = (double*)malloc(sizeof(double) * num_col); double* row_value = (double*)malloc(sizeof(double) * num_row); double* row_dual = (double*)malloc(sizeof(double) * num_row); - return_status = Highs_getSolution(highs, col_value, col_dual, row_value, row_dual); - assert( return_status == kHighsStatusOk ); - assertDoubleValuesEqual("objective_function_value", objective_function_value, 5); + return_status = + Highs_getSolution(highs, col_value, col_dual, row_value, row_dual); + assert(return_status == kHighsStatusOk); + assertDoubleValuesEqual("objective_function_value", objective_function_value, + 5); assertDoubleValuesEqual("solution_value[0]", col_value[0], 1); assertDoubleValuesEqual("solution_value[1]", col_value[1], 1); - + free(col_value); free(col_dual); free(row_value); @@ -1651,40 +1744,42 @@ void test_callback() { HighsInt a_index[7] = {0, 1, 2, 3, 4, 5, 6}; double a_value[7] = {9, 6, 7, 9, 7, 9, 9}; HighsInt integrality[7] = {kHighsVarTypeInteger, kHighsVarTypeInteger, - kHighsVarTypeInteger, kHighsVarTypeInteger, - kHighsVarTypeInteger, kHighsVarTypeInteger, - kHighsVarTypeInteger}; + kHighsVarTypeInteger, kHighsVarTypeInteger, + kHighsVarTypeInteger, kHighsVarTypeInteger, + kHighsVarTypeInteger}; void* highs; highs = Highs_create(); Highs_setBoolOptionValue(highs, "output_flag", dev_run); Highs_passMip(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, - integrality); - + col_cost, col_lower, col_upper, row_lower, row_upper, a_start, + a_index, a_value, integrality); + Highs_setCallback(highs, userCallback, NULL); Highs_startCallback(highs, kHighsCallbackLogging); Highs_startCallback(highs, kHighsCallbackMipInterrupt); Highs_run(highs); double objective_function_value; - Highs_getDoubleInfoValue(highs, "objective_function_value", &objective_function_value); + Highs_getDoubleInfoValue(highs, "objective_function_value", + &objective_function_value); double inf = Highs_getInfinity(highs); - assertDoubleValuesEqual("objective_function_value", objective_function_value, inf); + assertDoubleValuesEqual("objective_function_value", objective_function_value, + inf); Highs_stopCallback(highs, kHighsCallbackMipInterrupt); Highs_run(highs); - Highs_getDoubleInfoValue(highs, "objective_function_value", &objective_function_value); - assertDoubleValuesEqual("objective_function_value", objective_function_value, 17); + Highs_getDoubleInfoValue(highs, "objective_function_value", + &objective_function_value); + assertDoubleValuesEqual("objective_function_value", objective_function_value, + 17); double user_callback_data = inf; void* p_user_callback_data = (void*)(&user_callback_data); - + Highs_setCallback(highs, userCallback, p_user_callback_data); Highs_clearSolver(highs); Highs_startCallback(highs, kHighsCallbackMipImprovingSolution); Highs_run(highs); - + Highs_destroy(highs); } @@ -1693,7 +1788,7 @@ void test_getModel() { highs = Highs_create(); Highs_setBoolOptionValue(highs, "output_flag", dev_run); const double inf = Highs_getInfinity(highs); - + HighsInt num_col = 2; HighsInt num_row = 2; HighsInt num_nz = 4; @@ -1708,8 +1803,9 @@ void test_getModel() { double a_value[4] = {0.3, 0.5, 0.7, 0.5}; HighsInt a_start[2] = {0, 2}; Highs_addVars(highs, num_col, col_lower, col_upper); - Highs_changeColsCostByRange(highs, 0, num_col-1, col_cost); - Highs_addRows(highs, num_row, row_lower, row_upper, num_nz, a_start, a_index, a_value); + Highs_changeColsCostByRange(highs, 0, num_col - 1, col_cost); + Highs_addRows(highs, num_row, row_lower, row_upper, num_nz, a_start, a_index, + a_value); Highs_changeObjectiveSense(highs, sense); Highs_run(highs); @@ -1720,20 +1816,19 @@ void test_getModel() { double ck_offset; // Get the model dimensions by passing array pointers as NULL - Highs_getLp(highs, kHighsMatrixFormatRowwise, - &ck_num_col, &ck_num_row, &ck_num_nz, - &ck_sense, &ck_offset, NULL, - NULL, NULL, NULL, - NULL, NULL, NULL, - NULL, NULL); - - assert( ck_num_col == num_col ); - assert( ck_num_row == num_row ); - assert( ck_num_nz == num_nz ); - // Motivated by #1712, ensure that the correct sense is returned when maximizing - assert( ck_sense == sense ); - - double* ck_col_cost = (double*)malloc(sizeof(double) * ck_num_col);; + Highs_getLp(highs, kHighsMatrixFormatRowwise, &ck_num_col, &ck_num_row, + &ck_num_nz, &ck_sense, &ck_offset, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL); + + assert(ck_num_col == num_col); + assert(ck_num_row == num_row); + assert(ck_num_nz == num_nz); + // Motivated by #1712, ensure that the correct sense is returned when + // maximizing + assert(ck_sense == sense); + + double* ck_col_cost = (double*)malloc(sizeof(double) * ck_num_col); + ; double* ck_col_lower = (double*)malloc(sizeof(double) * ck_num_col); double* ck_col_upper = (double*)malloc(sizeof(double) * ck_num_col); double* ck_row_lower = (double*)malloc(sizeof(double) * ck_num_row); @@ -1741,23 +1836,21 @@ void test_getModel() { HighsInt* ck_a_start = (HighsInt*)malloc(sizeof(HighsInt) * ck_num_col); HighsInt* ck_a_index = (HighsInt*)malloc(sizeof(HighsInt) * ck_num_nz); double* ck_a_value = (double*)malloc(sizeof(double) * num_nz); - + // Get the arrays - Highs_getLp(highs, kHighsMatrixFormatRowwise, - &ck_num_col, &ck_num_row, &ck_num_nz, - &ck_sense, &ck_offset, ck_col_cost, - ck_col_lower, ck_col_upper, ck_row_lower, - ck_row_upper, ck_a_start, ck_a_index, - ck_a_value, NULL); - - assert( doubleArraysEqual(num_col, ck_col_cost, col_cost) ); - assert( doubleArraysEqual(num_col, ck_col_lower, col_lower) ); - assert( doubleArraysEqual(num_col, ck_col_upper, col_upper) ); - assert( doubleArraysEqual(num_row, ck_row_lower, row_lower) ); - assert( doubleArraysEqual(num_row, ck_row_upper, row_upper) ); - assert( highsIntArraysEqual(num_col, ck_a_start, a_start) ); - assert( highsIntArraysEqual(num_nz, ck_a_index, a_index) ); - assert( doubleArraysEqual(num_nz, ck_a_value, a_value) ); + Highs_getLp(highs, kHighsMatrixFormatRowwise, &ck_num_col, &ck_num_row, + &ck_num_nz, &ck_sense, &ck_offset, ck_col_cost, ck_col_lower, + ck_col_upper, ck_row_lower, ck_row_upper, ck_a_start, ck_a_index, + ck_a_value, NULL); + + assert(doubleArraysEqual(num_col, ck_col_cost, col_cost)); + assert(doubleArraysEqual(num_col, ck_col_lower, col_lower)); + assert(doubleArraysEqual(num_col, ck_col_upper, col_upper)); + assert(doubleArraysEqual(num_row, ck_row_lower, row_lower)); + assert(doubleArraysEqual(num_row, ck_row_upper, row_upper)); + assert(highsIntArraysEqual(num_col, ck_a_start, a_start)); + assert(highsIntArraysEqual(num_nz, ck_a_index, a_index)); + assert(doubleArraysEqual(num_nz, ck_a_value, a_value)); Highs_destroy(highs); } @@ -1781,7 +1874,7 @@ void test_setSolution() { HighsInt length = strlen(model_file0) + 1; char model_file[length]; strcpy(model_file, model_file0); - + if (dev_run) printf("\nSolving from scratch\n"); Highs_setBoolOptionValue(highs, "output_flag", dev_run); @@ -1801,9 +1894,9 @@ void test_setSolution() { HighsInt iteration_count1; Highs_getIntInfoValue(highs, "simplex_iteration_count", &iteration_count1); HighsInt logic = iteration_count0 > iteration_count1; - printf("Iteration counts are %d and %d\n", iteration_count0, iteration_count1); - assertLogical("Dual", logic); - + printf("Iteration counts are %d and %d\n", iteration_count0, +iteration_count1); assertLogical("Dual", logic); + Highs_destroy(highs); } */ @@ -1828,4 +1921,4 @@ int main() { test_getModel(); return 0; } - // test_setSolution(); +// test_setSolution(); diff --git a/check/matrix_multiplication.hpp b/check/matrix_multiplication.hpp index 5db3303f81..0d850b15e4 100644 --- a/check/matrix_multiplication.hpp +++ b/check/matrix_multiplication.hpp @@ -1,13 +1,13 @@ -#include +#include +#include #include -#include #include -#include +#include +#include #include -#include +#include #include -#include -#include +#include extern int N; extern double **a, **b, **c; @@ -20,7 +20,7 @@ inline void allocate_matrix() { a = static_cast(std::malloc(N * sizeof(double*))); b = static_cast(std::malloc(N * sizeof(double*))); c = static_cast(std::malloc(N * sizeof(double*))); - for(int i=0; i(std::malloc(N * sizeof(double))); b[i] = static_cast(std::malloc(N * sizeof(double))); c[i] = static_cast(std::malloc(N * sizeof(double))); @@ -28,7 +28,7 @@ inline void allocate_matrix() { } inline void deallocate_matrix() { - for(int i=0; i -#include #include #include +#include +#include + +#include "interfaces/highs_c_api.h" -// gcc call_highs_from_c.c -o highstest -I install_folder/include/ -L install_folder/lib/ -lhighs +// gcc call_highs_from_c.c -o highstest -I install_folder/include/ -L +// install_folder/lib/ -lhighs void minimal_api() { // This illustrates the use of Highs_lpCall, the simple C interface to @@ -96,75 +97,87 @@ void minimal_api() { HighsInt model_status; HighsInt run_status; - run_status = Highs_lpCall(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, - col_value, col_dual, row_value, row_dual, - col_basis_status, row_basis_status, - &model_status); + run_status = + Highs_lpCall(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, col_value, col_dual, row_value, row_dual, + col_basis_status, row_basis_status, &model_status); // The run must be successful, and the model status optimal assert(run_status == kHighsStatusOk); assert(model_status == kHighsModelStatusOptimal); - printf("\nRun status = %" HIGHSINT_FORMAT "; Model status = %" HIGHSINT_FORMAT "\n", run_status, model_status); + printf("\nRun status = %" HIGHSINT_FORMAT "; Model status = %" HIGHSINT_FORMAT + "\n", + run_status, model_status); objective_value = offset; // Report the column primal and dual values, and basis status for (HighsInt i = 0; i < num_col; i++) { - printf("Col%" HIGHSINT_FORMAT " = %lf; dual = %lf; status = %" HIGHSINT_FORMAT "\n", i, col_value[i], col_dual[i], col_basis_status[i]); - objective_value += col_value[i]*col_cost[i]; + printf("Col%" HIGHSINT_FORMAT + " = %lf; dual = %lf; status = %" HIGHSINT_FORMAT "\n", + i, col_value[i], col_dual[i], col_basis_status[i]); + objective_value += col_value[i] * col_cost[i]; } // Report the row primal and dual values, and basis status for (HighsInt i = 0; i < num_row; i++) { - printf("Row%" HIGHSINT_FORMAT " = %lf; dual = %lf; status = %" HIGHSINT_FORMAT "\n", i, row_value[i], row_dual[i], row_basis_status[i]); + printf("Row%" HIGHSINT_FORMAT + " = %lf; dual = %lf; status = %" HIGHSINT_FORMAT "\n", + i, row_value[i], row_dual[i], row_basis_status[i]); } printf("Optimal objective value = %g\n", objective_value); // Switch the sense to maximization and solve the LP again sense = kHighsObjSenseMaximize; - run_status = Highs_lpCall(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, - col_value, col_dual, row_value, row_dual, - col_basis_status, row_basis_status, - &model_status); + run_status = + Highs_lpCall(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, col_value, col_dual, row_value, row_dual, + col_basis_status, row_basis_status, &model_status); // The run must be successful, and the model status optimal assert(run_status == kHighsStatusOk); assert(model_status == kHighsModelStatusOptimal); - printf("\nRun status = %" HIGHSINT_FORMAT "; Model status = %" HIGHSINT_FORMAT "\n", run_status, model_status); + printf("\nRun status = %" HIGHSINT_FORMAT "; Model status = %" HIGHSINT_FORMAT + "\n", + run_status, model_status); // Compute the objective value objective_value = offset; - for (HighsInt i = 0; i < num_col; i++) objective_value += col_value[i]*col_cost[i]; + for (HighsInt i = 0; i < num_col; i++) + objective_value += col_value[i] * col_cost[i]; // Report the column primal and dual values, and basis status for (HighsInt i = 0; i < num_col; i++) { - printf("Col%" HIGHSINT_FORMAT " = %lf; dual = %lf; status = %" HIGHSINT_FORMAT "\n", i, col_value[i], col_dual[i], col_basis_status[i]); + printf("Col%" HIGHSINT_FORMAT + " = %lf; dual = %lf; status = %" HIGHSINT_FORMAT "\n", + i, col_value[i], col_dual[i], col_basis_status[i]); } // Report the row primal and dual values, and basis status for (HighsInt i = 0; i < num_row; i++) { - printf("Row%" HIGHSINT_FORMAT " = %lf; dual = %lf; status = %" HIGHSINT_FORMAT "\n", i, row_value[i], row_dual[i], row_basis_status[i]); + printf("Row%" HIGHSINT_FORMAT + " = %lf; dual = %lf; status = %" HIGHSINT_FORMAT "\n", + i, row_value[i], row_dual[i], row_basis_status[i]); } printf("Optimal objective value = %g\n", objective_value); - // + // // Indicate that the optimal solution for both columns must be // integer valued and solve the model as a MIP HighsInt integrality[2] = {1, 1}; - run_status = Highs_mipCall(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, - integrality, - col_value, row_value, - &model_status); + run_status = Highs_mipCall(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, integrality, + col_value, row_value, &model_status); // The run must be successful, and the model status optimal assert(run_status == kHighsStatusOk); assert(model_status == kHighsModelStatusOptimal); - printf("\nRun status = %" HIGHSINT_FORMAT "; Model status = %" HIGHSINT_FORMAT "\n", run_status, model_status); + printf("\nRun status = %" HIGHSINT_FORMAT "; Model status = %" HIGHSINT_FORMAT + "\n", + run_status, model_status); // Compute the objective value objective_value = offset; - for (HighsInt i = 0; i < num_col; i++) objective_value += col_value[i]*col_cost[i]; + for (HighsInt i = 0; i < num_col; i++) + objective_value += col_value[i] * col_cost[i]; // Report the column primal values for (HighsInt i = 0; i < num_col; i++) { printf("Col%" HIGHSINT_FORMAT " = %lf\n", i, col_value[i]); @@ -189,7 +202,7 @@ void minimal_api_qp() { // minimize -x_2 + (1/2)(2x_1^2 - 2x_1x_3 + 0.2x_2^2 + 2x_3^2) // // subject to x_1 + x_2 + x_3 >= 1; x>=0 - + const HighsInt num_col = 3; const HighsInt num_row = 1; const HighsInt num_nz = 3; @@ -228,45 +241,50 @@ void minimal_api_qp() { HighsInt model_status; HighsInt run_status; - run_status = Highs_qpCall(num_col, num_row, num_nz, q_num_nz, a_format, q_format, - sense, offset, col_cost, col_lower, col_upper, row_lower, row_upper, - a_start, a_index, a_value, - q_start, q_index, q_value, - col_value, col_dual, row_value, row_dual, - col_basis_status, row_basis_status, - &model_status); + run_status = Highs_qpCall( + num_col, num_row, num_nz, q_num_nz, a_format, q_format, sense, offset, + col_cost, col_lower, col_upper, row_lower, row_upper, a_start, a_index, + a_value, q_start, q_index, q_value, col_value, col_dual, row_value, + row_dual, col_basis_status, row_basis_status, &model_status); // The run must be successful, and the model status optimal assert(run_status == kHighsStatusOk); assert(model_status == kHighsModelStatusOptimal); - printf("\nRun status = %" HIGHSINT_FORMAT "; Model status = %" HIGHSINT_FORMAT "\n", run_status, model_status); + printf("\nRun status = %" HIGHSINT_FORMAT "; Model status = %" HIGHSINT_FORMAT + "\n", + run_status, model_status); // Compute the objective value objective_value = offset; - for (HighsInt i = 0; i < num_col; i++) objective_value += col_value[i]*col_cost[i]; + for (HighsInt i = 0; i < num_col; i++) + objective_value += col_value[i] * col_cost[i]; for (HighsInt i = 0; i < num_col; i++) { HighsInt from_el = q_start[i]; HighsInt to_el; - if (i+1 -#include #include #include +#include +#include + +#include "interfaces/highs_c_api.h" -// gcc call_highs_from_c.c -o highstest -I install_folder/include/ -L install_folder/lib/ -lhighs +// gcc call_highs_from_c.c -o highstest -I install_folder/include/ -L +// install_folder/lib/ -lhighs void minimal_api() { // This illustrates the use of Highs_lpCall, the simple C interface to @@ -96,75 +97,87 @@ void minimal_api() { HighsInt model_status; HighsInt run_status; - run_status = Highs_lpCall(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, - col_value, col_dual, row_value, row_dual, - col_basis_status, row_basis_status, - &model_status); + run_status = + Highs_lpCall(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, col_value, col_dual, row_value, row_dual, + col_basis_status, row_basis_status, &model_status); // The run must be successful, and the model status optimal assert(run_status == kHighsStatusOk); assert(model_status == kHighsModelStatusOptimal); - printf("\nRun status = %" HIGHSINT_FORMAT "; Model status = %" HIGHSINT_FORMAT "\n", run_status, model_status); + printf("\nRun status = %" HIGHSINT_FORMAT "; Model status = %" HIGHSINT_FORMAT + "\n", + run_status, model_status); objective_value = offset; // Report the column primal and dual values, and basis status for (HighsInt i = 0; i < num_col; i++) { - printf("Col%" HIGHSINT_FORMAT " = %lf; dual = %lf; status = %" HIGHSINT_FORMAT "\n", i, col_value[i], col_dual[i], col_basis_status[i]); - objective_value += col_value[i]*col_cost[i]; + printf("Col%" HIGHSINT_FORMAT + " = %lf; dual = %lf; status = %" HIGHSINT_FORMAT "\n", + i, col_value[i], col_dual[i], col_basis_status[i]); + objective_value += col_value[i] * col_cost[i]; } // Report the row primal and dual values, and basis status for (HighsInt i = 0; i < num_row; i++) { - printf("Row%" HIGHSINT_FORMAT " = %lf; dual = %lf; status = %" HIGHSINT_FORMAT "\n", i, row_value[i], row_dual[i], row_basis_status[i]); + printf("Row%" HIGHSINT_FORMAT + " = %lf; dual = %lf; status = %" HIGHSINT_FORMAT "\n", + i, row_value[i], row_dual[i], row_basis_status[i]); } printf("Optimal objective value = %g\n", objective_value); // Switch the sense to maximization and solve the LP again sense = kHighsObjSenseMaximize; - run_status = Highs_lpCall(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, - col_value, col_dual, row_value, row_dual, - col_basis_status, row_basis_status, - &model_status); + run_status = + Highs_lpCall(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, col_value, col_dual, row_value, row_dual, + col_basis_status, row_basis_status, &model_status); // The run must be successful, and the model status optimal assert(run_status == kHighsStatusOk); assert(model_status == kHighsModelStatusOptimal); - printf("\nRun status = %" HIGHSINT_FORMAT "; Model status = %" HIGHSINT_FORMAT "\n", run_status, model_status); + printf("\nRun status = %" HIGHSINT_FORMAT "; Model status = %" HIGHSINT_FORMAT + "\n", + run_status, model_status); // Compute the objective value objective_value = offset; - for (HighsInt i = 0; i < num_col; i++) objective_value += col_value[i]*col_cost[i]; + for (HighsInt i = 0; i < num_col; i++) + objective_value += col_value[i] * col_cost[i]; // Report the column primal and dual values, and basis status for (HighsInt i = 0; i < num_col; i++) { - printf("Col%" HIGHSINT_FORMAT " = %lf; dual = %lf; status = %" HIGHSINT_FORMAT "\n", i, col_value[i], col_dual[i], col_basis_status[i]); + printf("Col%" HIGHSINT_FORMAT + " = %lf; dual = %lf; status = %" HIGHSINT_FORMAT "\n", + i, col_value[i], col_dual[i], col_basis_status[i]); } // Report the row primal and dual values, and basis status for (HighsInt i = 0; i < num_row; i++) { - printf("Row%" HIGHSINT_FORMAT " = %lf; dual = %lf; status = %" HIGHSINT_FORMAT "\n", i, row_value[i], row_dual[i], row_basis_status[i]); + printf("Row%" HIGHSINT_FORMAT + " = %lf; dual = %lf; status = %" HIGHSINT_FORMAT "\n", + i, row_value[i], row_dual[i], row_basis_status[i]); } printf("Optimal objective value = %g\n", objective_value); - // + // // Indicate that the optimal solution for both columns must be // integer valued and solve the model as a MIP HighsInt integrality[2] = {1, 1}; - run_status = Highs_mipCall(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, - integrality, - col_value, row_value, - &model_status); + run_status = Highs_mipCall(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, integrality, + col_value, row_value, &model_status); // The run must be successful, and the model status optimal assert(run_status == kHighsStatusOk); assert(model_status == kHighsModelStatusOptimal); - printf("\nRun status = %" HIGHSINT_FORMAT "; Model status = %" HIGHSINT_FORMAT "\n", run_status, model_status); + printf("\nRun status = %" HIGHSINT_FORMAT "; Model status = %" HIGHSINT_FORMAT + "\n", + run_status, model_status); // Compute the objective value objective_value = offset; - for (HighsInt i = 0; i < num_col; i++) objective_value += col_value[i]*col_cost[i]; + for (HighsInt i = 0; i < num_col; i++) + objective_value += col_value[i] * col_cost[i]; // Report the column primal values for (HighsInt i = 0; i < num_col; i++) { printf("Col%" HIGHSINT_FORMAT " = %lf\n", i, col_value[i]); @@ -189,7 +202,7 @@ void minimal_api_qp() { // minimize -x_2 + (1/2)(2x_1^2 - 2x_1x_3 + 0.2x_2^2 + 2x_3^2) // // subject to x_1 + x_2 + x_3 >= 1; x>=0 - + const HighsInt num_col = 3; const HighsInt num_row = 1; const HighsInt num_nz = 3; @@ -228,45 +241,50 @@ void minimal_api_qp() { HighsInt model_status; HighsInt run_status; - run_status = Highs_qpCall(num_col, num_row, num_nz, q_num_nz, a_format, q_format, - sense, offset, col_cost, col_lower, col_upper, row_lower, row_upper, - a_start, a_index, a_value, - q_start, q_index, q_value, - col_value, col_dual, row_value, row_dual, - col_basis_status, row_basis_status, - &model_status); + run_status = Highs_qpCall( + num_col, num_row, num_nz, q_num_nz, a_format, q_format, sense, offset, + col_cost, col_lower, col_upper, row_lower, row_upper, a_start, a_index, + a_value, q_start, q_index, q_value, col_value, col_dual, row_value, + row_dual, col_basis_status, row_basis_status, &model_status); // The run must be successful, and the model status optimal assert(run_status == kHighsStatusOk); assert(model_status == kHighsModelStatusOptimal); - printf("\nRun status = %" HIGHSINT_FORMAT "; Model status = %" HIGHSINT_FORMAT "\n", run_status, model_status); + printf("\nRun status = %" HIGHSINT_FORMAT "; Model status = %" HIGHSINT_FORMAT + "\n", + run_status, model_status); // Compute the objective value objective_value = offset; - for (HighsInt i = 0; i < num_col; i++) objective_value += col_value[i]*col_cost[i]; + for (HighsInt i = 0; i < num_col; i++) + objective_value += col_value[i] * col_cost[i]; for (HighsInt i = 0; i < num_col; i++) { HighsInt from_el = q_start[i]; HighsInt to_el; - if (i+1 +#include "Highs.h" + using std::cout; using std::endl; @@ -94,7 +95,7 @@ int main() { // // Pass the model to HiGHS return_status = highs.passModel(model); - assert(return_status==HighsStatus::kOk); + assert(return_status == HighsStatus::kOk); // If a user passes a model with entries in // model.lp_.a_matrix_.value_ less than (the option) // small_matrix_value in magnitude, they will be ignored. A logging @@ -106,19 +107,21 @@ int main() { // // Solve the model return_status = highs.run(); - assert(return_status==HighsStatus::kOk); + assert(return_status == HighsStatus::kOk); // // Get the model status const HighsModelStatus& model_status = highs.getModelStatus(); - assert(model_status==HighsModelStatus::kOptimal); + assert(model_status == HighsModelStatus::kOptimal); cout << "Model status: " << highs.modelStatusToString(model_status) << endl; // // Get the solution information const HighsInfo& info = highs.getInfo(); cout << "Simplex iteration count: " << info.simplex_iteration_count << endl; cout << "Objective function value: " << info.objective_function_value << endl; - cout << "Primal solution status: " << highs.solutionStatusToString(info.primal_solution_status) << endl; - cout << "Dual solution status: " << highs.solutionStatusToString(info.dual_solution_status) << endl; + cout << "Primal solution status: " + << highs.solutionStatusToString(info.primal_solution_status) << endl; + cout << "Dual solution status: " + << highs.solutionStatusToString(info.dual_solution_status) << endl; cout << "Basis: " << highs.basisValidityToString(info.basis_validity) << endl; const bool has_values = info.primal_solution_status; const bool has_duals = info.dual_solution_status; @@ -129,39 +132,43 @@ int main() { const HighsBasis& basis = highs.getBasis(); // // Report the primal and solution values and basis - for (int col=0; col < lp.num_col_; col++) { + for (int col = 0; col < lp.num_col_; col++) { cout << "Column " << col; if (has_values) cout << "; value = " << solution.col_value[col]; if (has_duals) cout << "; dual = " << solution.col_dual[col]; - if (has_basis) cout << "; status: " << highs.basisStatusToString(basis.col_status[col]); + if (has_basis) + cout << "; status: " << highs.basisStatusToString(basis.col_status[col]); cout << endl; } - for (int row=0; row < lp.num_row_; row++) { + for (int row = 0; row < lp.num_row_; row++) { cout << "Row " << row; if (has_values) cout << "; value = " << solution.row_value[row]; if (has_duals) cout << "; dual = " << solution.row_dual[row]; - if (has_basis) cout << "; status: " << highs.basisStatusToString(basis.row_status[row]); + if (has_basis) + cout << "; status: " << highs.basisStatusToString(basis.row_status[row]); cout << endl; } // Now indicate that all the variables must take integer values model.lp_.integrality_.resize(lp.num_col_); - for (int col=0; col < lp.num_col_; col++) + for (int col = 0; col < lp.num_col_; col++) model.lp_.integrality_[col] = HighsVarType::kInteger; highs.passModel(model); // Solve the model return_status = highs.run(); - assert(return_status==HighsStatus::kOk); + assert(return_status == HighsStatus::kOk); // Report the primal solution values - for (int col=0; col < lp.num_col_; col++) { + for (int col = 0; col < lp.num_col_; col++) { cout << "Column " << col; - if (info.primal_solution_status) cout << "; value = " << solution.col_value[col]; + if (info.primal_solution_status) + cout << "; value = " << solution.col_value[col]; cout << endl; } - for (int row=0; row < lp.num_row_; row++) { + for (int row = 0; row < lp.num_row_; row++) { cout << "Row " << row; - if (info.primal_solution_status) cout << "; value = " << solution.row_value[row]; + if (info.primal_solution_status) + cout << "; value = " << solution.row_value[row]; cout << endl; } diff --git a/extern/catch.hpp b/extern/catch.hpp index db1fed3b98..b529505586 100644 --- a/extern/catch.hpp +++ b/extern/catch.hpp @@ -2,8 +2,8 @@ * Catch v2.13.8 * Generated: 2022-01-03 21:20:09.589503 * ---------------------------------------------------------- - * This file has been merged from multiple headers. Please don't edit it directly - * Copyright (c) 2022 Two Blue Cubes Ltd. All rights reserved. + * This file has been merged from multiple headers. Please don't edit it + * directly Copyright (c) 2022 Two Blue Cubes Ltd. All rights reserved. * * Distributed under the Boost Software License, Version 1.0. (See accompanying * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -12,55 +12,54 @@ #define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED // start catch.hpp - #define CATCH_VERSION_MAJOR 2 #define CATCH_VERSION_MINOR 13 #define CATCH_VERSION_PATCH 8 #ifdef __clang__ -# pragma clang system_header +#pragma clang system_header #elif defined __GNUC__ -# pragma GCC system_header +#pragma GCC system_header #endif // start catch_suppress_warnings.h #ifdef __clang__ -# ifdef __ICC // icpc defines the __clang__ macro -# pragma warning(push) -# pragma warning(disable: 161 1682) -# else // __ICC -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wpadded" -# pragma clang diagnostic ignored "-Wswitch-enum" -# pragma clang diagnostic ignored "-Wcovered-switch-default" -# endif +#ifdef __ICC // icpc defines the __clang__ macro +#pragma warning(push) +#pragma warning(disable : 161 1682) +#else // __ICC +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#pragma clang diagnostic ignored "-Wswitch-enum" +#pragma clang diagnostic ignored "-Wcovered-switch-default" +#endif #elif defined __GNUC__ - // Because REQUIREs trigger GCC's -Wparentheses, and because still - // supported version of g++ have only buggy support for _Pragmas, - // Wparentheses have to be suppressed globally. -# pragma GCC diagnostic ignored "-Wparentheses" // See #674 for details - -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wunused-variable" -# pragma GCC diagnostic ignored "-Wpadded" +// Because REQUIREs trigger GCC's -Wparentheses, and because still +// supported version of g++ have only buggy support for _Pragmas, +// Wparentheses have to be suppressed globally. +#pragma GCC diagnostic ignored "-Wparentheses" // See #674 for details + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" +#pragma GCC diagnostic ignored "-Wpadded" #endif // end catch_suppress_warnings.h #if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) -# define CATCH_IMPL -# define CATCH_CONFIG_ALL_PARTS +#define CATCH_IMPL +#define CATCH_CONFIG_ALL_PARTS #endif // In the impl file, we want to have access to all parts of the headers // Can also be used to sanely support PCHs #if defined(CATCH_CONFIG_ALL_PARTS) -# define CATCH_CONFIG_EXTERNAL_INTERFACES -# if defined(CATCH_CONFIG_DISABLE_MATCHERS) -# undef CATCH_CONFIG_DISABLE_MATCHERS -# endif -# if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) -# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER -# endif +#define CATCH_CONFIG_EXTERNAL_INTERFACES +#if defined(CATCH_CONFIG_DISABLE_MATCHERS) +#undef CATCH_CONFIG_DISABLE_MATCHERS +#endif +#if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +#endif #endif #if !defined(CATCH_CONFIG_IMPL_ONLY) @@ -69,34 +68,35 @@ // See e.g.: // https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html #ifdef __APPLE__ -# include -# if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \ - (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1) -# define CATCH_PLATFORM_MAC -# elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) -# define CATCH_PLATFORM_IPHONE -# endif +#include +#if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \ + (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1) +#define CATCH_PLATFORM_MAC +#elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) +#define CATCH_PLATFORM_IPHONE +#endif #elif defined(linux) || defined(__linux) || defined(__linux__) -# define CATCH_PLATFORM_LINUX +#define CATCH_PLATFORM_LINUX -#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) -# define CATCH_PLATFORM_WINDOWS +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || \ + defined(_MSC_VER) || defined(__MINGW32__) +#define CATCH_PLATFORM_WINDOWS #endif // end catch_platform.h #ifdef CATCH_IMPL -# ifndef CLARA_CONFIG_MAIN -# define CLARA_CONFIG_MAIN_NOT_DEFINED -# define CLARA_CONFIG_MAIN -# endif +#ifndef CLARA_CONFIG_MAIN +#define CLARA_CONFIG_MAIN_NOT_DEFINED +#define CLARA_CONFIG_MAIN +#endif #endif // start catch_user_interfaces.h namespace Catch { - unsigned int rngSeed(); +unsigned int rngSeed(); } // end catch_user_interfaces.h @@ -125,30 +125,33 @@ namespace Catch { #ifdef __cplusplus -# if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) -# define CATCH_CPP14_OR_GREATER -# endif +#if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) +#define CATCH_CPP14_OR_GREATER +#endif -# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) -# define CATCH_CPP17_OR_GREATER -# endif +#if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +#define CATCH_CPP17_OR_GREATER +#endif #endif // Only GCC compiler should be used in this block, so other compilers trying to // mask themselves as GCC should be ignored. -#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) -# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) -# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && \ + !defined(__CUDACC__) && !defined(__LCC__) +#define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma("GCC diagnostic push") +#define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma("GCC diagnostic pop") -# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) +#define CATCH_INTERNAL_IGNORE_BUT_WARN(...) \ + (void)__builtin_constant_p(__VA_ARGS__) #endif #if defined(__clang__) -# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) -# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) +#define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + _Pragma("clang diagnostic push") +#define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma("clang diagnostic pop") // As of this writing, IBM XL's implementation of __builtin_constant_p has a bug // which results in calls to destructors being emitted for each temporary, @@ -161,62 +164,66 @@ namespace Catch { // ``` // // Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented. -# if !defined(__ibmxl__) && !defined(__CUDACC__) -# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, hicpp-vararg) */ -# endif +#if !defined(__ibmxl__) && !defined(__CUDACC__) +#define CATCH_INTERNAL_IGNORE_BUT_WARN(...) \ + (void)__builtin_constant_p( \ + __VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, \ + hicpp-vararg) */ +#endif -# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ - _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") +#define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + _Pragma("clang diagnostic ignored \"-Wexit-time-destructors\"") \ + _Pragma("clang diagnostic ignored \"-Wglobal-constructors\"") -# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) +#define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma("clang diagnostic ignored \"-Wparentheses\"") -# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) +#define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + _Pragma("clang diagnostic ignored \"-Wunused-variable\"") -# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) +#define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + _Pragma("clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"") -# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) +#define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + _Pragma("clang diagnostic ignored \"-Wunused-template\"") -#endif // __clang__ +#endif // __clang__ //////////////////////////////////////////////////////////////////////////////// // Assume that non-Windows platforms support posix signals by default #if !defined(CATCH_PLATFORM_WINDOWS) - #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS +#define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS #endif //////////////////////////////////////////////////////////////////////////////// // We know some environments not to support full POSIX signals -#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) - #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || \ + defined(__DJGPP__) +#define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS #endif #ifdef __OS400__ -# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS -# define CATCH_CONFIG_COLOUR_NONE +#define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +#define CATCH_CONFIG_COLOUR_NONE #endif //////////////////////////////////////////////////////////////////////////////// // Android somehow still does not support std::to_string #if defined(__ANDROID__) -# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING -# define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE +#define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +#define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE #endif //////////////////////////////////////////////////////////////////////////////// // Not all Windows environments support SEH properly #if defined(__MINGW32__) -# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH +#define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH #endif //////////////////////////////////////////////////////////////////////////////// // PS4 #if defined(__ORBIS__) -# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE +#define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE #endif //////////////////////////////////////////////////////////////////////////////// @@ -224,17 +231,20 @@ namespace Catch { #ifdef __CYGWIN__ // Required for some versions of Cygwin to declare gettimeofday -// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin -# define _BSD_SOURCE -// some versions of cygwin (most) do not support std::to_string. Use the libstd check. -// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 -# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ - && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) - -# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +// see: +// http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin +#define _BSD_SOURCE +// some versions of cygwin (most) do not support std::to_string. Use the libstd +// check. +// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html +// line 2812-2813 +#if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) && \ + !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) + +#define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING -# endif -#endif // __CYGWIN__ +#endif +#endif // __CYGWIN__ //////////////////////////////////////////////////////////////////////////////// // Visual C++ @@ -242,49 +252,52 @@ namespace Catch { // Universal Windows platform does not support SEH // Or console colours (or console at all...) -# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) -# define CATCH_CONFIG_COLOUR_NONE -# else -# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH -# endif +#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +#define CATCH_CONFIG_COLOUR_NONE +#else +#define CATCH_INTERNAL_CONFIG_WINDOWS_SEH +#endif -# if !defined(__clang__) // Handle Clang masquerading for msvc +#if !defined(__clang__) // Handle Clang masquerading for msvc // MSVC traditional preprocessor needs some workaround for __VA_ARGS__ // _MSVC_TRADITIONAL == 0 means new conformant preprocessor // _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor -# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) -# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -# endif // MSVC_TRADITIONAL +#if !defined(_MSVC_TRADITIONAL) || \ + (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) +#define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#endif // MSVC_TRADITIONAL -// Only do this if we're not using clang on Windows, which uses `diagnostic push` & `diagnostic pop` -# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) ) -# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma( warning(pop) ) -# endif // __clang__ +// Only do this if we're not using clang on Windows, which uses `diagnostic +// push` & `diagnostic pop` +#define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma(warning(push)) +#define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma(warning(pop)) +#endif // __clang__ -#endif // _MSC_VER +#endif // _MSC_VER #if defined(_REENTRANT) || defined(_MSC_VER) -// Enable async processing, as -pthread is specified or no additional linking is required -# define CATCH_INTERNAL_CONFIG_USE_ASYNC -#endif // _MSC_VER +// Enable async processing, as -pthread is specified or no additional linking is +// required +#define CATCH_INTERNAL_CONFIG_USE_ASYNC +#endif // _MSC_VER //////////////////////////////////////////////////////////////////////////////// // Check if we are compiled with -fno-exceptions or equivalent #if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) -# define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED +#define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED #endif //////////////////////////////////////////////////////////////////////////////// // DJGPP #ifdef __DJGPP__ -# define CATCH_INTERNAL_CONFIG_NO_WCHAR -#endif // __DJGPP__ +#define CATCH_INTERNAL_CONFIG_NO_WCHAR +#endif // __DJGPP__ //////////////////////////////////////////////////////////////////////////////// // Embarcadero C++Build #if defined(__BORLANDC__) - #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN +#define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN #endif //////////////////////////////////////////////////////////////////////////////// @@ -294,8 +307,8 @@ namespace Catch { // handled by it. // Otherwise all supported compilers support COUNTER macro, // but user still might want to turn it off -#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) - #define CATCH_INTERNAL_CONFIG_COUNTER +#if (!defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L) +#define CATCH_INTERNAL_CONFIG_COUNTER #endif //////////////////////////////////////////////////////////////////////////////// @@ -304,9 +317,9 @@ namespace Catch { // This means that it is detected as Windows, but does not provide // the same set of capabilities as real Windows does. #if defined(UNDER_RTSS) || defined(RTX64_BUILD) - #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH - #define CATCH_INTERNAL_CONFIG_NO_ASYNC - #define CATCH_CONFIG_COLOUR_NONE +#define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH +#define CATCH_INTERNAL_CONFIG_NO_ASYNC +#define CATCH_CONFIG_COLOUR_NONE #endif #if !defined(_GLIBCXX_USE_C99_MATH_TR1) @@ -315,139 +328,173 @@ namespace Catch { // Various stdlib support checks that require __has_include #if defined(__has_include) - // Check if string_view is available and usable - #if __has_include() && defined(CATCH_CPP17_OR_GREATER) - # define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW - #endif - - // Check if optional is available and usable - # if __has_include() && defined(CATCH_CPP17_OR_GREATER) - # define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL - # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) - - // Check if byte is available and usable - # if __has_include() && defined(CATCH_CPP17_OR_GREATER) - # include - # if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0) - # define CATCH_INTERNAL_CONFIG_CPP17_BYTE - # endif - # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) - - // Check if variant is available and usable - # if __has_include() && defined(CATCH_CPP17_OR_GREATER) - # if defined(__clang__) && (__clang_major__ < 8) - // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 - // fix should be in clang 8, workaround in libstdc++ 8.2 - # include - # if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) - # define CATCH_CONFIG_NO_CPP17_VARIANT - # else - # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT - # endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) - # else - # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT - # endif // defined(__clang__) && (__clang_major__ < 8) - # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) -#endif // defined(__has_include) - -#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) -# define CATCH_CONFIG_COUNTER +// Check if string_view is available and usable +#if __has_include() && defined(CATCH_CPP17_OR_GREATER) +#define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW +#endif + +// Check if optional is available and usable +#if __has_include() && defined(CATCH_CPP17_OR_GREATER) +#define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL +#endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + +// Check if byte is available and usable +#if __has_include() && defined(CATCH_CPP17_OR_GREATER) +#include +#if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0) +#define CATCH_INTERNAL_CONFIG_CPP17_BYTE +#endif +#endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + +// Check if variant is available and usable +#if __has_include() && defined(CATCH_CPP17_OR_GREATER) +#if defined(__clang__) && (__clang_major__ < 8) +// work around clang bug with libstdc++ +// https://bugs.llvm.org/show_bug.cgi?id=31852 fix should be in clang 8, +// workaround in libstdc++ 8.2 +#include +#if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) +#define CATCH_CONFIG_NO_CPP17_VARIANT +#else +#define CATCH_INTERNAL_CONFIG_CPP17_VARIANT +#endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && + // (_GLIBCXX_RELEASE < 9) +#else +#define CATCH_INTERNAL_CONFIG_CPP17_VARIANT +#endif // defined(__clang__) && (__clang_major__ < 8) +#endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) +#endif // defined(__has_include) + +#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && \ + !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) +#define CATCH_CONFIG_COUNTER #endif -#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) -# define CATCH_CONFIG_WINDOWS_SEH +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && \ + !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && \ + !defined(CATCH_CONFIG_WINDOWS_SEH) && \ + !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) +#define CATCH_CONFIG_WINDOWS_SEH #endif -// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. -#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) -# define CATCH_CONFIG_POSIX_SIGNALS +// This is set by default, because we assume that unix compilers are +// posix-signal-compatible by default. +#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && \ + !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && \ + !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && \ + !defined(CATCH_CONFIG_POSIX_SIGNALS) +#define CATCH_CONFIG_POSIX_SIGNALS #endif -// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. -#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) -# define CATCH_CONFIG_WCHAR +// This is set by default, because we assume that compilers with no wchar_t +// support are just rare exceptions. +#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && \ + !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) +#define CATCH_CONFIG_WCHAR #endif -#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) -# define CATCH_CONFIG_CPP11_TO_STRING +#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && \ + !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && \ + !defined(CATCH_CONFIG_CPP11_TO_STRING) +#define CATCH_CONFIG_CPP11_TO_STRING #endif -#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) -# define CATCH_CONFIG_CPP17_OPTIONAL +#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && \ + !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && \ + !defined(CATCH_CONFIG_CPP17_OPTIONAL) +#define CATCH_CONFIG_CPP17_OPTIONAL #endif -#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) -# define CATCH_CONFIG_CPP17_STRING_VIEW +#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && \ + !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && \ + !defined(CATCH_CONFIG_CPP17_STRING_VIEW) +#define CATCH_CONFIG_CPP17_STRING_VIEW #endif -#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) -# define CATCH_CONFIG_CPP17_VARIANT +#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && \ + !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && \ + !defined(CATCH_CONFIG_CPP17_VARIANT) +#define CATCH_CONFIG_CPP17_VARIANT #endif -#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) -# define CATCH_CONFIG_CPP17_BYTE +#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && \ + !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) +#define CATCH_CONFIG_CPP17_BYTE #endif #if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) -# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE +#define CATCH_INTERNAL_CONFIG_NEW_CAPTURE #endif -#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) -# define CATCH_CONFIG_NEW_CAPTURE +#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && \ + !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && \ + !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && \ + !defined(CATCH_CONFIG_NEW_CAPTURE) +#define CATCH_CONFIG_NEW_CAPTURE #endif -#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) -# define CATCH_CONFIG_DISABLE_EXCEPTIONS +#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && \ + !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +#define CATCH_CONFIG_DISABLE_EXCEPTIONS #endif -#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) -# define CATCH_CONFIG_POLYFILL_ISNAN +#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && \ + !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && \ + !defined(CATCH_CONFIG_POLYFILL_ISNAN) +#define CATCH_CONFIG_POLYFILL_ISNAN #endif -#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) -# define CATCH_CONFIG_USE_ASYNC +#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && \ + !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && \ + !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) +#define CATCH_CONFIG_USE_ASYNC #endif -#if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_ANDROID_LOGWRITE) -# define CATCH_CONFIG_ANDROID_LOGWRITE +#if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && \ + !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) && \ + !defined(CATCH_CONFIG_ANDROID_LOGWRITE) +#define CATCH_CONFIG_ANDROID_LOGWRITE #endif -#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) -# define CATCH_CONFIG_GLOBAL_NEXTAFTER +#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && \ + !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && \ + !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) +#define CATCH_CONFIG_GLOBAL_NEXTAFTER #endif // Even if we do not think the compiler has that warning, we still have // to provide a macro that can be used by the code. #if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) -# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION +#define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION #endif #if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) -# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION +#define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION #endif #if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +#define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS #endif #if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +#define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS #endif #if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS +#define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS #endif #if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS +#define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS #endif // The goal of this macro is to avoid evaluation of the arguments, but // still have the compiler warn on problems inside... #if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN) -# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) +#define CATCH_INTERNAL_IGNORE_BUT_WARN(...) #endif -#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) -# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#if defined(__APPLE__) && defined(__apple_build_version__) && \ + (__clang_major__ < 10) +#undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS #elif defined(__clang__) && (__clang_major__ < 5) -# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS #endif #if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS #endif #if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) @@ -460,103 +507,109 @@ namespace Catch { #define CATCH_CATCH_ANON(type) catch (type) #endif -#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) +#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && \ + !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && \ + !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) #define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #endif // end catch_compiler_capabilities.h -#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line -#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2(name, line) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE(name, line) \ + INTERNAL_CATCH_UNIQUE_NAME_LINE2(name, line) #ifdef CATCH_CONFIG_COUNTER -# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#define INTERNAL_CATCH_UNIQUE_NAME(name) \ + INTERNAL_CATCH_UNIQUE_NAME_LINE(name, __COUNTER__) #else -# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#define INTERNAL_CATCH_UNIQUE_NAME(name) \ + INTERNAL_CATCH_UNIQUE_NAME_LINE(name, __LINE__) #endif +#include #include #include -#include -// We need a dummy global operator<< so we can bring it into Catch namespace later +// We need a dummy global operator<< so we can bring it into Catch namespace +// later struct Catch_global_namespace_dummy {}; std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); namespace Catch { - struct CaseSensitive { enum Choice { - Yes, - No - }; }; - - class NonCopyable { - NonCopyable( NonCopyable const& ) = delete; - NonCopyable( NonCopyable && ) = delete; - NonCopyable& operator = ( NonCopyable const& ) = delete; - NonCopyable& operator = ( NonCopyable && ) = delete; +struct CaseSensitive { + enum Choice { Yes, No }; +}; - protected: - NonCopyable(); - virtual ~NonCopyable(); - }; +class NonCopyable { + NonCopyable(NonCopyable const&) = delete; + NonCopyable(NonCopyable&&) = delete; + NonCopyable& operator=(NonCopyable const&) = delete; + NonCopyable& operator=(NonCopyable&&) = delete; - struct SourceLineInfo { + protected: + NonCopyable(); + virtual ~NonCopyable(); +}; - SourceLineInfo() = delete; - SourceLineInfo( char const* _file, std::size_t _line ) noexcept - : file( _file ), - line( _line ) - {} +struct SourceLineInfo { + SourceLineInfo() = delete; + SourceLineInfo(char const* _file, std::size_t _line) noexcept + : file(_file), line(_line) {} - SourceLineInfo( SourceLineInfo const& other ) = default; - SourceLineInfo& operator = ( SourceLineInfo const& ) = default; - SourceLineInfo( SourceLineInfo&& ) noexcept = default; - SourceLineInfo& operator = ( SourceLineInfo&& ) noexcept = default; + SourceLineInfo(SourceLineInfo const& other) = default; + SourceLineInfo& operator=(SourceLineInfo const&) = default; + SourceLineInfo(SourceLineInfo&&) noexcept = default; + SourceLineInfo& operator=(SourceLineInfo&&) noexcept = default; - bool empty() const noexcept { return file[0] == '\0'; } - bool operator == ( SourceLineInfo const& other ) const noexcept; - bool operator < ( SourceLineInfo const& other ) const noexcept; + bool empty() const noexcept { return file[0] == '\0'; } + bool operator==(SourceLineInfo const& other) const noexcept; + bool operator<(SourceLineInfo const& other) const noexcept; - char const* file; - std::size_t line; - }; + char const* file; + std::size_t line; +}; - std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); +std::ostream& operator<<(std::ostream& os, SourceLineInfo const& info); - // Bring in operator<< from global namespace into Catch namespace - // This is necessary because the overload of operator<< above makes - // lookup stop at namespace Catch - using ::operator<<; +// Bring in operator<< from global namespace into Catch namespace +// This is necessary because the overload of operator<< above makes +// lookup stop at namespace Catch +using ::operator<<; - // Use this in variadic streaming macros to allow - // >> +StreamEndStop - // as well as - // >> stuff +StreamEndStop - struct StreamEndStop { - std::string operator+() const; - }; - template - T const& operator + ( T const& value, StreamEndStop ) { - return value; - } +// Use this in variadic streaming macros to allow +// >> +StreamEndStop +// as well as +// >> stuff +StreamEndStop +struct StreamEndStop { + std::string operator+() const; +}; +template +T const& operator+(T const& value, StreamEndStop) { + return value; } +} // namespace Catch #define CATCH_INTERNAL_LINEINFO \ - ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) + ::Catch::SourceLineInfo(__FILE__, static_cast(__LINE__)) // end catch_common.h namespace Catch { - struct RegistrarForTagAliases { - RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); - }; +struct RegistrarForTagAliases { + RegistrarForTagAliases(char const* alias, char const* tag, + SourceLineInfo const& lineInfo); +}; -} // end namespace Catch +} // end namespace Catch -#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION +#define CATCH_REGISTER_TAG_ALIAS(alias, spec) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace { \ + Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( \ + AutoRegisterTagAlias)(alias, spec, CATCH_INTERNAL_LINEINFO); \ + } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION // end catch_tag_alias_autoregistrar.h // start catch_test_registry.h @@ -567,143 +620,151 @@ namespace Catch { namespace Catch { - class TestSpec; +class TestSpec; - struct ITestInvoker { - virtual void invoke () const = 0; - virtual ~ITestInvoker(); - }; +struct ITestInvoker { + virtual void invoke() const = 0; + virtual ~ITestInvoker(); +}; - class TestCase; - struct IConfig; +class TestCase; +struct IConfig; - struct ITestCaseRegistry { - virtual ~ITestCaseRegistry(); - virtual std::vector const& getAllTests() const = 0; - virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; - }; +struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector const& getAllTests() const = 0; + virtual std::vector const& getAllTestsSorted( + IConfig const& config) const = 0; +}; - bool isThrowSafe( TestCase const& testCase, IConfig const& config ); - bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); - std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); - std::vector const& getAllTestCasesSorted( IConfig const& config ); +bool isThrowSafe(TestCase const& testCase, IConfig const& config); +bool matchTest(TestCase const& testCase, TestSpec const& testSpec, + IConfig const& config); +std::vector filterTests(std::vector const& testCases, + TestSpec const& testSpec, + IConfig const& config); +std::vector const& getAllTestCasesSorted(IConfig const& config); -} +} // namespace Catch // end catch_interfaces_testcase.h // start catch_stringref.h +#include #include -#include #include -#include +#include namespace Catch { - /// A non-owning string class (similar to the forthcoming std::string_view) - /// Note that, because a StringRef may be a substring of another string, - /// it may not be null terminated. - class StringRef { - public: - using size_type = std::size_t; - using const_iterator = const char*; +/// A non-owning string class (similar to the forthcoming std::string_view) +/// Note that, because a StringRef may be a substring of another string, +/// it may not be null terminated. +class StringRef { + public: + using size_type = std::size_t; + using const_iterator = const char*; - private: - static constexpr char const* const s_empty = ""; + private: + static constexpr char const* const s_empty = ""; - char const* m_start = s_empty; - size_type m_size = 0; + char const* m_start = s_empty; + size_type m_size = 0; - public: // construction - constexpr StringRef() noexcept = default; + public: // construction + constexpr StringRef() noexcept = default; - StringRef( char const* rawChars ) noexcept; + StringRef(char const* rawChars) noexcept; - constexpr StringRef( char const* rawChars, size_type size ) noexcept - : m_start( rawChars ), - m_size( size ) - {} + constexpr StringRef(char const* rawChars, size_type size) noexcept + : m_start(rawChars), m_size(size) {} - StringRef( std::string const& stdString ) noexcept - : m_start( stdString.c_str() ), - m_size( stdString.size() ) - {} + StringRef(std::string const& stdString) noexcept + : m_start(stdString.c_str()), m_size(stdString.size()) {} - explicit operator std::string() const { - return std::string(m_start, m_size); - } + explicit operator std::string() const { return std::string(m_start, m_size); } - public: // operators - auto operator == ( StringRef const& other ) const noexcept -> bool; - auto operator != (StringRef const& other) const noexcept -> bool { - return !(*this == other); - } + public: // operators + auto operator==(StringRef const& other) const noexcept -> bool; + auto operator!=(StringRef const& other) const noexcept -> bool { + return !(*this == other); + } - auto operator[] ( size_type index ) const noexcept -> char { - assert(index < m_size); - return m_start[index]; - } + auto operator[](size_type index) const noexcept -> char { + assert(index < m_size); + return m_start[index]; + } - public: // named queries - constexpr auto empty() const noexcept -> bool { - return m_size == 0; - } - constexpr auto size() const noexcept -> size_type { - return m_size; - } + public: // named queries + constexpr auto empty() const noexcept -> bool { return m_size == 0; } + constexpr auto size() const noexcept -> size_type { return m_size; } - // Returns the current start pointer. If the StringRef is not - // null-terminated, throws std::domain_exception - auto c_str() const -> char const*; + // Returns the current start pointer. If the StringRef is not + // null-terminated, throws std::domain_exception + auto c_str() const -> char const*; - public: // substrings and searches - // Returns a substring of [start, start + length). - // If start + length > size(), then the substring is [start, size()). - // If start > size(), then the substring is empty. - auto substr( size_type start, size_type length ) const noexcept -> StringRef; + public: // substrings and searches + // Returns a substring of [start, start + length). + // If start + length > size(), then the substring is [start, size()). + // If start > size(), then the substring is empty. + auto substr(size_type start, size_type length) const noexcept -> StringRef; - // Returns the current start pointer. May not be null-terminated. - auto data() const noexcept -> char const*; + // Returns the current start pointer. May not be null-terminated. + auto data() const noexcept -> char const*; - constexpr auto isNullTerminated() const noexcept -> bool { - return m_start[m_size] == '\0'; - } + constexpr auto isNullTerminated() const noexcept -> bool { + return m_start[m_size] == '\0'; + } - public: // iterators - constexpr const_iterator begin() const { return m_start; } - constexpr const_iterator end() const { return m_start + m_size; } - }; + public: // iterators + constexpr const_iterator begin() const { return m_start; } + constexpr const_iterator end() const { return m_start + m_size; } +}; - auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; - auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; +auto operator+=(std::string& lhs, StringRef const& sr) -> std::string&; +auto operator<<(std::ostream& os, StringRef const& sr) -> std::ostream&; - constexpr auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { - return StringRef( rawChars, size ); - } -} // namespace Catch +constexpr auto operator"" _sr(char const* rawChars, + std::size_t size) noexcept -> StringRef { + return StringRef(rawChars, size); +} +} // namespace Catch -constexpr auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { - return Catch::StringRef( rawChars, size ); +constexpr auto operator"" _catch_sr( + char const* rawChars, std::size_t size) noexcept -> Catch::StringRef { + return Catch::StringRef(rawChars, size); } // end catch_stringref.h // start catch_preprocessor.hpp - #define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ -#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL1(...) \ + CATCH_RECURSION_LEVEL0( \ + CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL2(...) \ + CATCH_RECURSION_LEVEL1( \ + CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL3(...) \ + CATCH_RECURSION_LEVEL2( \ + CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL4(...) \ + CATCH_RECURSION_LEVEL3( \ + CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL5(...) \ + CATCH_RECURSION_LEVEL4( \ + CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) #ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ // MSVC needs more evaluations -#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) -#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) +#define CATCH_RECURSION_LEVEL6(...) \ + CATCH_RECURSION_LEVEL5( \ + CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) +#define CATCH_RECURSE(...) \ + CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) #else -#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) #endif #define CATCH_REC_END(...) @@ -716,627 +777,1109 @@ constexpr auto operator "" _catch_sr( char const* rawChars, std::size_t size ) n #define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 #define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 #define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT -#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) -#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) - -#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) -#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) -#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) - -#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) -#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) -#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) - -// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, -// and passes userdata as the first parameter to each invocation, -// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) -#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) - -#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) +#define CATCH_REC_NEXT1(test, next) CATCH_DEFER(CATCH_REC_NEXT0)(test, next, 0) +#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) + +#define CATCH_REC_LIST0(f, x, peek, ...) \ + , f(x) CATCH_DEFER(CATCH_REC_NEXT(peek, CATCH_REC_LIST1))(f, peek, \ + __VA_ARGS__) +#define CATCH_REC_LIST1(f, x, peek, ...) \ + , f(x) CATCH_DEFER(CATCH_REC_NEXT(peek, CATCH_REC_LIST0))(f, peek, \ + __VA_ARGS__) +#define CATCH_REC_LIST2(f, x, peek, ...) \ + f(x) CATCH_DEFER(CATCH_REC_NEXT(peek, CATCH_REC_LIST1))(f, peek, __VA_ARGS__) + +#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) \ + , f(userdata, x) CATCH_DEFER(CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD))( \ + f, userdata, peek, __VA_ARGS__) +#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) \ + , f(userdata, x) CATCH_DEFER(CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD))( \ + f, userdata, peek, __VA_ARGS__) +#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) \ + f(userdata, x) CATCH_DEFER(CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD))( \ + f, userdata, peek, __VA_ARGS__) + +// Applies the function macro `f` to each of the remaining parameters, inserts +// commas between the results, and passes userdata as the first parameter to +// each invocation, e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), +// f(x, b), f(x, c) +#define CATCH_REC_LIST_UD(f, userdata, ...) \ + CATCH_RECURSE( \ + CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define CATCH_REC_LIST(f, ...) \ + CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) #define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) -#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ +#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO##__VA_ARGS__ #define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ #define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF #define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ -#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) \ + INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) #else -// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +// MSVC is adding extra space and needs another indirection to expand +// INTERNAL_CATCH_NOINTERNAL_CATCH_DEF #define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) #define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ -#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) \ + (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) #endif #define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ #define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) -#define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) +#define INTERNAL_CATCH_REMOVE_PARENS(...) \ + INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) -#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) \ + decltype(get_wrapper()) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) \ + INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) #else -#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) -#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) \ + INTERNAL_CATCH_EXPAND_VARGS( \ + decltype(get_wrapper())) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) \ + INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2( \ + INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) #endif -#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ - CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) +#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...) \ + CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST, __VA_ARGS__) #define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) -#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) -#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) -#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) -#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) -#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) -#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6) -#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) -#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) -#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) -#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) - -#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N - -#define INTERNAL_CATCH_TYPE_GEN\ - template struct TypeList {};\ - template\ - constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ - template class...> struct TemplateTypeList{};\ - template class...Cs>\ - constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\ - template\ - struct append;\ - template\ - struct rewrap;\ - template class, typename...>\ - struct create;\ - template class, typename>\ - struct convert;\ - \ - template \ - struct append { using type = T; };\ - template< template class L1, typename...E1, template class L2, typename...E2, typename...Rest>\ - struct append, L2, Rest...> { using type = typename append, Rest...>::type; };\ - template< template class L1, typename...E1, typename...Rest>\ - struct append, TypeList, Rest...> { using type = L1; };\ - \ - template< template class Container, template class List, typename...elems>\ - struct rewrap, List> { using type = TypeList>; };\ - template< template class Container, template class List, class...Elems, typename...Elements>\ - struct rewrap, List, Elements...> { using type = typename append>, typename rewrap, Elements...>::type>::type; };\ - \ - template