From 1d17f81acabe02201b419f1e98185b12996584fa Mon Sep 17 00:00:00 2001 From: Julian Hall Date: Thu, 24 Oct 2024 17:19:15 +0100 Subject: [PATCH 01/11] WIP --- src/Highs.h | 16 ++++++++- src/lp_data/Highs.cpp | 34 ++++++++++++++++++ src/lp_data/HighsInterface.cpp | 66 ++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 1 deletion(-) diff --git a/src/Highs.h b/src/Highs.h index 22e29c52cc..bd1cdcbd4e 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -400,6 +400,12 @@ class Highs { * Methods for model output */ + /** + * @brief Identify and the standard form of the HighsLp instance in + * HiGHS + */ + HighsStatus getStandardFormLp(HighsLp& standard_form_lp); + /** * @brief Return a const reference to the presolved HighsLp instance in HiGHS */ @@ -1378,6 +1384,10 @@ class Highs { HighsPresolveStatus::kNotPresolved; HighsModelStatus model_status_ = HighsModelStatus::kNotset; + std::vector standard_form_cost_; + std::vector standard_form_rhs_; + HighsSparseMatrix standard_form_matrix_; + HEkk ekk_instance_; HighsPresolveLog presolve_log_; @@ -1429,7 +1439,10 @@ class Highs { // // Clears the presolved model and its status void clearPresolve(); - // + // + // Clears the standard form LP + void clearStandardFormLp(); + // // Methods to clear solver data for users in Highs class members // before (possibly) updating them with data from trying to solve // the incumbent model. @@ -1473,6 +1486,7 @@ class Highs { void underDevelopmentLogMessage(const std::string& method_name); // Interface methods + HighsStatus formStandardFormLp(); HighsStatus basisForSolution(); HighsStatus addColsInterface( HighsInt ext_num_new_col, const double* ext_col_cost, diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index c1f1f48b72..441ed65d1b 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -65,6 +65,7 @@ HighsStatus Highs::clearModel() { HighsStatus Highs::clearSolver() { HighsStatus return_status = HighsStatus::kOk; clearPresolve(); + clearStandardFormLp(); invalidateUserSolverData(); return returnFromHighs(return_status); } @@ -1637,6 +1638,13 @@ HighsStatus Highs::run() { return returnFromRun(return_status, undo_mods); } +HighsStatus Highs::getStandardFormLp(HighsLp& standard_form_lp) { + HighsStatus status = formStandardFormLp(); + if (status != HighsStatus::kOk) return status; + standard_form_lp = this->standard_form_lp_; + return HighsStatus::kOk; +} + HighsStatus Highs::getDualRay(bool& has_dual_ray, double* dual_ray_value) { // Can't get a ray without an INVERT, but absence is only an error // when solving an LP #1350 @@ -2338,6 +2346,7 @@ HighsStatus Highs::addCols(const HighsInt num_new_col, const double* costs, this->logHeader(); HighsStatus return_status = HighsStatus::kOk; clearPresolve(); + clearStandardFormLp(); return_status = interpretCallStatus( options_.log_options, addColsInterface(num_new_col, costs, lower_bounds, upper_bounds, @@ -2376,6 +2385,7 @@ HighsStatus Highs::addRows(const HighsInt num_new_row, this->logHeader(); HighsStatus return_status = HighsStatus::kOk; clearPresolve(); + clearStandardFormLp(); return_status = interpretCallStatus( options_.log_options, addRowsInterface(num_new_row, lower_bounds, upper_bounds, num_new_nz, @@ -2391,6 +2401,7 @@ HighsStatus Highs::changeObjectiveSense(const ObjSense sense) { model_.lp_.sense_ = sense; // Nontrivial change clearPresolve(); + clearStandardFormLp(); invalidateModelStatusSolutionAndInfo(); } return returnFromHighs(HighsStatus::kOk); @@ -2520,6 +2531,7 @@ HighsStatus Highs::changeColCost(const HighsInt col, const double cost) { HighsStatus Highs::changeColsCost(const HighsInt from_col, const HighsInt to_col, const double* cost) { clearPresolve(); + clearStandardFormLp(); HighsIndexCollection index_collection; const HighsInt create_error = create(index_collection, from_col, to_col, model_.lp_.num_col_); @@ -2546,6 +2558,7 @@ HighsStatus Highs::changeColsCost(const HighsInt num_set_entries, if (doubleUserDataNotNull(options_.log_options, cost, "column costs")) return HighsStatus::kError; clearPresolve(); + clearStandardFormLp(); // Ensure that the set and data are in ascending order std::vector local_cost{cost, cost + num_set_entries}; std::vector local_set{set, set + num_set_entries}; @@ -2569,6 +2582,7 @@ HighsStatus Highs::changeColsCost(const HighsInt num_set_entries, HighsStatus Highs::changeColsCost(const HighsInt* mask, const double* cost) { clearPresolve(); + clearStandardFormLp(); HighsIndexCollection index_collection; const bool create_error = create(index_collection, mask, model_.lp_.num_col_); assert(!create_error); @@ -2590,6 +2604,7 @@ HighsStatus Highs::changeColsBounds(const HighsInt from_col, const HighsInt to_col, const double* lower, const double* upper) { clearPresolve(); + clearStandardFormLp(); HighsIndexCollection index_collection; const HighsInt create_error = create(index_collection, from_col, to_col, model_.lp_.num_col_); @@ -2624,6 +2639,7 @@ HighsStatus Highs::changeColsBounds(const HighsInt num_set_entries, null_data; if (null_data) return HighsStatus::kError; clearPresolve(); + clearStandardFormLp(); // Ensure that the set and data are in ascending order std::vector local_lower{lower, lower + num_set_entries}; std::vector local_upper{upper, upper + num_set_entries}; @@ -2649,6 +2665,7 @@ HighsStatus Highs::changeColsBounds(const HighsInt num_set_entries, HighsStatus Highs::changeColsBounds(const HighsInt* mask, const double* lower, const double* upper) { clearPresolve(); + clearStandardFormLp(); HighsIndexCollection index_collection; const bool create_error = create(index_collection, mask, model_.lp_.num_col_); assert(!create_error); @@ -2671,6 +2688,7 @@ HighsStatus Highs::changeRowsBounds(const HighsInt from_row, const HighsInt to_row, const double* lower, const double* upper) { clearPresolve(); + clearStandardFormLp(); HighsIndexCollection index_collection; const HighsInt create_error = create(index_collection, from_row, to_row, model_.lp_.num_row_); @@ -2705,6 +2723,7 @@ HighsStatus Highs::changeRowsBounds(const HighsInt num_set_entries, null_data; if (null_data) return HighsStatus::kError; clearPresolve(); + clearStandardFormLp(); // Ensure that the set and data are in ascending order std::vector local_lower{lower, lower + num_set_entries}; std::vector local_upper{upper, upper + num_set_entries}; @@ -2730,6 +2749,7 @@ HighsStatus Highs::changeRowsBounds(const HighsInt num_set_entries, HighsStatus Highs::changeRowsBounds(const HighsInt* mask, const double* lower, const double* upper) { clearPresolve(); + clearStandardFormLp(); HighsIndexCollection index_collection; const bool create_error = create(index_collection, mask, model_.lp_.num_row_); assert(!create_error); @@ -3028,6 +3048,7 @@ HighsStatus Highs::getCoeff(const HighsInt row, const HighsInt col, HighsStatus Highs::deleteCols(const HighsInt from_col, const HighsInt to_col) { clearPresolve(); + clearStandardFormLp(); HighsIndexCollection index_collection; const HighsInt create_error = create(index_collection, from_col, to_col, model_.lp_.num_col_); @@ -3046,6 +3067,7 @@ HighsStatus Highs::deleteCols(const HighsInt num_set_entries, const HighsInt* set) { if (num_set_entries == 0) return HighsStatus::kOk; clearPresolve(); + clearStandardFormLp(); HighsIndexCollection index_collection; const HighsInt create_error = create(index_collection, num_set_entries, set, model_.lp_.num_col_); @@ -3059,6 +3081,7 @@ HighsStatus Highs::deleteCols(const HighsInt num_set_entries, HighsStatus Highs::deleteCols(HighsInt* mask) { clearPresolve(); + clearStandardFormLp(); const HighsInt original_num_col = model_.lp_.num_col_; HighsIndexCollection index_collection; const bool create_error = create(index_collection, mask, original_num_col); @@ -3072,6 +3095,7 @@ HighsStatus Highs::deleteCols(HighsInt* mask) { HighsStatus Highs::deleteRows(const HighsInt from_row, const HighsInt to_row) { clearPresolve(); + clearStandardFormLp(); HighsIndexCollection index_collection; const HighsInt create_error = create(index_collection, from_row, to_row, model_.lp_.num_row_); @@ -3090,6 +3114,7 @@ HighsStatus Highs::deleteRows(const HighsInt num_set_entries, const HighsInt* set) { if (num_set_entries == 0) return HighsStatus::kOk; clearPresolve(); + clearStandardFormLp(); HighsIndexCollection index_collection; const HighsInt create_error = create(index_collection, num_set_entries, set, model_.lp_.num_row_); @@ -3103,6 +3128,7 @@ HighsStatus Highs::deleteRows(const HighsInt num_set_entries, HighsStatus Highs::deleteRows(HighsInt* mask) { clearPresolve(); + clearStandardFormLp(); const HighsInt original_num_row = model_.lp_.num_row_; HighsIndexCollection index_collection; const bool create_error = create(index_collection, mask, original_num_row); @@ -3117,6 +3143,7 @@ HighsStatus Highs::deleteRows(HighsInt* mask) { HighsStatus Highs::scaleCol(const HighsInt col, const double scale_value) { HighsStatus return_status = HighsStatus::kOk; clearPresolve(); + clearStandardFormLp(); HighsStatus call_status = scaleColInterface(col, scale_value); return_status = interpretCallStatus(options_.log_options, call_status, return_status, "scaleCol"); @@ -3127,6 +3154,7 @@ HighsStatus Highs::scaleCol(const HighsInt col, const double scale_value) { HighsStatus Highs::scaleRow(const HighsInt row, const double scale_value) { HighsStatus return_status = HighsStatus::kOk; clearPresolve(); + clearStandardFormLp(); HighsStatus call_status = scaleRowInterface(row, scale_value); return_status = interpretCallStatus(options_.log_options, call_status, return_status, "scaleRow"); @@ -3422,6 +3450,12 @@ void Highs::clearPresolve() { presolve_.clear(); } +void Highs::clearStandardFormLp() { + standard_form_cost_.clear(); + standard_form_rhs_.clear(); + standard_form_matrix_.clear(); +} + void Highs::invalidateUserSolverData() { invalidateModelStatus(); invalidateSolution(); diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 1366b7c927..8f51bac344 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -21,6 +21,72 @@ #include "util/HighsMatrixUtils.h" #include "util/HighsSort.h" +HighsStatus Highs::formStandardFormLp() { + this->clearStandardFormLp(); + HighsLp& lp = this->model_.lp_; + // Check for free variables + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + if (lp.col_lower_[iCol] <= -kHighsInf && + lp.col_upper_[iCol] >= kHighsInf) { + // Free col + highsLogUser(options_.log_options, HighsLogType::kError, + "Cannot generate standard form LP for problems with free variables\n"); + return HighsStatus::kError; + } + } + HighsSparseMatrix& matrix = lp.a_matrix_; + // Ensure that the incumbent LP is rowwise + matrix.ensureRowwise(); + HighsSparseMatrix local_row; + std::vector& index = local_row.index_; + std::vector value = local_row.value_; + local_row.num_row_ = 1; + local_row.num_col_ = lp.num_col_; + index.resize(lp.num_col_); + value.resize(lp.num_col_); + local_row.start_.resize(2); + HighsInt& num_nz = local_row.start_[1]; + local_row.start_[0] = 0; + HighsInt num_free_row = 0; + std::vectorslack_ix; + HighsInt slack_k = 1; + for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { + if (lp.row_lower_[iRow] <= -kHighsInf && + lp.row_upper_[iRow] >= kHighsInf) { + assert(0 == 1); + // Free row + num_free_row++; + continue; + } + if (lp.row_lower_[iRow] == lp.row_upper_[iRow]) { + assert(0 == 2); + // Equality + matrix.getRow(iRow, num_nz, index.data(), value.data()); + this->standard_form_matrix_.addRows(local_row); + this->standard_form_rhs_.push_back(lp.row_upper_[iRow]); + continue; + } else if (lp.row_lower_[iRow] <= -kHighsInf) { + assert(0 == 3); + // Upper bounded, so record the slack + assert(lp.row_upper_[iRow] < kHighsInf); + slack_ix.push_back(HighsInt(standard_form_rhs_.size())); + matrix.getRow(iRow, num_nz, index.data(), value.data()); + this->standard_form_matrix_.addRows(local_row); + this->standard_form_rhs_.push_back(lp.row_upper_[iRow]); + } else if (lp.row_upper_[iRow] >= kHighsInf) { + assert(0 == 4); + // Lower bounded, so record the slack + assert(lp.row_lower_[iRow] > -kHighsInf); + slack_ix.push_back(HighsInt(standard_form_rhs_.size())); + matrix.getRow(iRow, num_nz, index.data(), value.data()); + this->standard_form_matrix_.addRows(local_row); + this->standard_form_rhs_.push_back(lp.row_upper_[iRow]); + + } + + return HighsStatus::kError; +} + HighsStatus Highs::basisForSolution() { HighsLp& lp = model_.lp_; assert(!lp.isMip() || options_.solve_relaxation); From 9a14344b53cf0870b6242e5edf78e1eb9b039c19 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 25 Oct 2024 13:00:39 +0100 Subject: [PATCH 02/11] Standard form of avgas formed correctly --- check/TestLpSolvers.cpp | 65 +++++++++- src/Highs.h | 13 +- src/lp_data/Highs.cpp | 36 +++++- src/lp_data/HighsInterface.cpp | 204 ++++++++++++++++++++++++++------ src/mip/HighsLpRelaxation.cpp | 9 +- src/qpsolver/dantzigpricing.hpp | 2 +- src/qpsolver/devexpricing.hpp | 2 +- 7 files changed, 276 insertions(+), 55 deletions(-) diff --git a/check/TestLpSolvers.cpp b/check/TestLpSolvers.cpp index 572a478cf2..b40577bf0e 100644 --- a/check/TestLpSolvers.cpp +++ b/check/TestLpSolvers.cpp @@ -283,7 +283,7 @@ TEST_CASE("LP-solver", "[highs_lp_solver]") { const HighsInfo& info = highs.getInfo(); REQUIRE(info.num_dual_infeasibilities == 0); - REQUIRE(info.simplex_iteration_count == 472); // 476); // 444); + REQUIRE(info.simplex_iteration_count == 472); HighsModelStatus model_status = highs.getModelStatus(); REQUIRE(model_status == HighsModelStatus::kOptimal); @@ -296,7 +296,7 @@ TEST_CASE("LP-solver", "[highs_lp_solver]") { return_status = highs.run(); REQUIRE(return_status == HighsStatus::kOk); - REQUIRE(info.simplex_iteration_count == 592); // 621); // 584); // + REQUIRE(info.simplex_iteration_count == 592); } TEST_CASE("mip-with-lp-solver", "[highs_lp_solver]") { @@ -470,3 +470,64 @@ TEST_CASE("blending-lp-ipm", "[highs_lp_solver]") { printf("Sum dual infeasibilities = %g\n", info.sum_dual_infeasibilities); } } + +TEST_CASE("standard-form-lp", "[highs_lp_solver]") { + std::string model; + std::string model_file; + Highs highs; + highs.setOptionValue("output_flag", dev_run); + model = "avgas"; + model_file = std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; + highs.readModel(model_file); + highs.run(); + double required_objective_function_value = + highs.getInfo().objective_function_value; + // + HighsInt num_col; + HighsInt num_row; + HighsInt num_nz; + double offset; + REQUIRE(highs.getStandardFormLp(num_col, num_row, num_nz, offset) == + HighsStatus::kOk); + + std::vector cost(num_col); + std::vector rhs(num_row); + std::vector start(num_col + 1); + std::vector index(num_nz); + std::vector value(num_nz); + REQUIRE(highs.getStandardFormLp(num_col, num_row, num_nz, offset, cost.data(), + rhs.data(), start.data(), index.data(), + value.data()) == HighsStatus::kOk); + + HighsLp standard_form_lp; + standard_form_lp.num_col_ = num_col; + standard_form_lp.num_row_ = num_row; + standard_form_lp.col_cost_ = cost; + standard_form_lp.col_lower_.assign(num_col, 0); + standard_form_lp.col_upper_.assign(num_col, kHighsInf); + standard_form_lp.row_lower_ = rhs; + standard_form_lp.row_upper_ = rhs; + standard_form_lp.a_matrix_.start_ = start; + standard_form_lp.a_matrix_.index_ = index; + standard_form_lp.a_matrix_.value_ = value; + REQUIRE(highs.passModel(standard_form_lp) == HighsStatus::kOk); + if (dev_run) highs.writeModel(""); + REQUIRE(highs.run() == HighsStatus::kOk); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal); + double objective_function_value = highs.getInfo().objective_function_value; + double objective_difference = + std::fabs(objective_function_value - required_objective_function_value) / + std::max(1.0, std::fabs(required_objective_function_value)); + REQUIRE(objective_difference < 1e-10); + const bool look_at_presolved_lp = false; + if (look_at_presolved_lp) { + // Strange that presolve doesn't convert the constraints + // + // Ax-s = b; s >= 0 into Ax >= b + REQUIRE(highs.passModel(standard_form_lp) == HighsStatus::kOk); + REQUIRE(highs.presolve() == HighsStatus::kOk); + HighsLp presolved_lp = highs.getPresolvedLp(); + REQUIRE(highs.passModel(presolved_lp) == HighsStatus::kOk); + highs.writeModel(""); + } +} diff --git a/src/Highs.h b/src/Highs.h index bd1cdcbd4e..a7024cc403 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -404,7 +404,12 @@ class Highs { * @brief Identify and the standard form of the HighsLp instance in * HiGHS */ - HighsStatus getStandardFormLp(HighsLp& standard_form_lp); + HighsStatus getStandardFormLp(HighsInt& num_col, HighsInt& num_row, + HighsInt& num_nz, double offset, + double* cost = nullptr, double* rhs = nullptr, + HighsInt* start = nullptr, + HighsInt* index = nullptr, + double* value = nullptr); /** * @brief Return a const reference to the presolved HighsLp instance in HiGHS @@ -1384,6 +1389,8 @@ class Highs { HighsPresolveStatus::kNotPresolved; HighsModelStatus model_status_ = HighsModelStatus::kNotset; + bool standard_form_valid_; + double standard_form_offset_; std::vector standard_form_cost_; std::vector standard_form_rhs_; HighsSparseMatrix standard_form_matrix_; @@ -1439,10 +1446,10 @@ class Highs { // // Clears the presolved model and its status void clearPresolve(); - // + // // Clears the standard form LP void clearStandardFormLp(); - // + // // Methods to clear solver data for users in Highs class members // before (possibly) updating them with data from trying to solve // the incumbent model. diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index 441ed65d1b..7277647b78 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -1638,10 +1638,34 @@ HighsStatus Highs::run() { return returnFromRun(return_status, undo_mods); } -HighsStatus Highs::getStandardFormLp(HighsLp& standard_form_lp) { - HighsStatus status = formStandardFormLp(); - if (status != HighsStatus::kOk) return status; - standard_form_lp = this->standard_form_lp_; +HighsStatus Highs::getStandardFormLp(HighsInt& num_col, HighsInt& num_row, + HighsInt& num_nz, double offset, + double* cost, double* rhs, HighsInt* start, + HighsInt* index, double* value) { + if (!this->standard_form_valid_) { + HighsStatus status = formStandardFormLp(); + if (status != HighsStatus::kOk) return status; + } + num_col = this->standard_form_cost_.size(); + num_row = this->standard_form_rhs_.size(); + num_nz = this->standard_form_matrix_.start_[num_col]; + offset = this->standard_form_offset_; + for (HighsInt iCol = 0; iCol < num_col; iCol++) { + if (cost) cost[iCol] = this->standard_form_cost_[iCol]; + if (start) start[iCol] = this->standard_form_matrix_.start_[iCol]; + if (index || value) { + for (HighsInt iEl = this->standard_form_matrix_.start_[iCol]; + iEl < this->standard_form_matrix_.start_[iCol + 1]; iEl++) { + if (index) index[iEl] = this->standard_form_matrix_.index_[iEl]; + if (value) value[iEl] = this->standard_form_matrix_.value_[iEl]; + } + } + } + if (start) start[num_col] = this->standard_form_matrix_.start_[num_col]; + if (rhs) { + for (HighsInt iRow = 0; iRow < num_row; iRow++) + rhs[iRow] = this->standard_form_rhs_[iRow]; + } return HighsStatus::kOk; } @@ -2401,7 +2425,7 @@ HighsStatus Highs::changeObjectiveSense(const ObjSense sense) { model_.lp_.sense_ = sense; // Nontrivial change clearPresolve(); - clearStandardFormLp(); + clearStandardFormLp(); invalidateModelStatusSolutionAndInfo(); } return returnFromHighs(HighsStatus::kOk); @@ -3451,6 +3475,8 @@ void Highs::clearPresolve() { } void Highs::clearStandardFormLp() { + standard_form_valid_ = false; + standard_form_offset_ = 0; standard_form_cost_.clear(); standard_form_rhs_.clear(); standard_form_matrix_.clear(); diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 8f51bac344..df008d4f6c 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -24,67 +24,193 @@ HighsStatus Highs::formStandardFormLp() { this->clearStandardFormLp(); HighsLp& lp = this->model_.lp_; - // Check for free variables - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { - if (lp.col_lower_[iCol] <= -kHighsInf && - lp.col_upper_[iCol] >= kHighsInf) { - // Free col - highsLogUser(options_.log_options, HighsLogType::kError, - "Cannot generate standard form LP for problems with free variables\n"); - return HighsStatus::kError; - } - } HighsSparseMatrix& matrix = lp.a_matrix_; - // Ensure that the incumbent LP is rowwise + // Ensure that the incumbent LP and standard form LP matrices are rowwise matrix.ensureRowwise(); + // Original rows are processed before columns, so that any original + // boxed rows can be transformed to pairs of one-sided rows, + // requiring the standard form matrix to be row-wise. The original + // columns are assumed to come before any new columns, so their + // costs must be defined befor costs of new columns. + this->standard_form_cost_ = lp.col_cost_; + // Whilst debugging, work with local data structure + double standard_form_offset; + std::vector standard_form_cost = lp.col_cost_; + std::vector standard_form_rhs; + HighsSparseMatrix standard_form_matrix; + standard_form_matrix.format_ = MatrixFormat::kRowwise; + standard_form_matrix.num_col_ = lp.num_col_; + // Create a HighsSparseMatrix instance to store rows extracted from + // the original constraint matrix + HighsInt local_row_min_nnz = std::max(lp.num_col_, 2); HighsSparseMatrix local_row; - std::vector& index = local_row.index_; - std::vector value = local_row.value_; + local_row.ensureRowwise(); local_row.num_row_ = 1; local_row.num_col_ = lp.num_col_; - index.resize(lp.num_col_); - value.resize(lp.num_col_); + local_row.index_.resize(local_row_min_nnz); + local_row.value_.resize(local_row_min_nnz); local_row.start_.resize(2); HighsInt& num_nz = local_row.start_[1]; local_row.start_[0] = 0; HighsInt num_free_row = 0; - std::vectorslack_ix; + std::vector slack_ix; HighsInt slack_k = 1; for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { - if (lp.row_lower_[iRow] <= -kHighsInf && - lp.row_upper_[iRow] >= kHighsInf) { + double lower = lp.row_lower_[iRow]; + double upper = lp.row_upper_[iRow]; + if (lower <= -kHighsInf && upper >= kHighsInf) { assert(0 == 1); // Free row num_free_row++; continue; } - if (lp.row_lower_[iRow] == lp.row_upper_[iRow]) { + if (lower == upper) { assert(0 == 2); // Equality - matrix.getRow(iRow, num_nz, index.data(), value.data()); - this->standard_form_matrix_.addRows(local_row); - this->standard_form_rhs_.push_back(lp.row_upper_[iRow]); + matrix.getRow(iRow, num_nz, local_row.index_.data(), + local_row.value_.data()); + standard_form_matrix.addRows(local_row); + standard_form_rhs.push_back(upper); continue; - } else if (lp.row_lower_[iRow] <= -kHighsInf) { + } else if (lower <= -kHighsInf) { assert(0 == 3); // Upper bounded, so record the slack - assert(lp.row_upper_[iRow] < kHighsInf); - slack_ix.push_back(HighsInt(standard_form_rhs_.size())); - matrix.getRow(iRow, num_nz, index.data(), value.data()); - this->standard_form_matrix_.addRows(local_row); - this->standard_form_rhs_.push_back(lp.row_upper_[iRow]); - } else if (lp.row_upper_[iRow] >= kHighsInf) { - assert(0 == 4); + assert(upper < kHighsInf); + slack_ix.push_back(++slack_k); + matrix.getRow(iRow, num_nz, local_row.index_.data(), + local_row.value_.data()); + standard_form_matrix.addRows(local_row); + standard_form_rhs.push_back(upper); + } else if (upper >= kHighsInf) { // Lower bounded, so record the slack - assert(lp.row_lower_[iRow] > -kHighsInf); - slack_ix.push_back(HighsInt(standard_form_rhs_.size())); - matrix.getRow(iRow, num_nz, index.data(), value.data()); - this->standard_form_matrix_.addRows(local_row); - this->standard_form_rhs_.push_back(lp.row_upper_[iRow]); - - } - - return HighsStatus::kError; + assert(lower > -kHighsInf); + slack_ix.push_back(-(++slack_k)); + matrix.getRow(iRow, num_nz, local_row.index_.data(), + local_row.value_.data()); + standard_form_matrix.addRows(local_row); + standard_form_rhs.push_back(lower); + } else { + assert(0 == 5); + // Boxed, so record the lower slack + assert(lower > -kHighsInf); + assert(upper < kHighsInf); + slack_ix.push_back(-(++slack_k)); + matrix.getRow(iRow, num_nz, local_row.index_.data(), + local_row.value_.data()); + standard_form_matrix.addRows(local_row); + standard_form_rhs.push_back(lower); + // .. and upper slack + slack_ix.push_back(++slack_k); + standard_form_matrix.addRows(local_row); + standard_form_rhs.push_back(upper); + } + } + // Add rows corresponding to boxed columns + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + double lower = lp.col_lower_[iCol]; + double upper = lp.col_upper_[iCol]; + if (lower > -kHighsInf && upper < kHighsInf) { + // Boxed column + // + // x will be replaced by x = l + X (below) with X >= 0 + // + // Introduce variable s >= 0 so that + // + // l + X = u - s => X + s = u - l + standard_form_cost.push_back(0); + standard_form_matrix.num_col_++; + local_row.num_col_++; + local_row.index_[0] = iCol; + local_row.index_[1] = standard_form_matrix.num_col_ - 1; + local_row.value_[0] = 1; + local_row.value_[1] = 1; + local_row.start_[1] = 2; + standard_form_matrix.addRows(local_row); + standard_form_rhs.push_back(upper - lower); + } + } + // Finished with both matrices, row-wise, so ensure that the + // incumbent matrix leaves col-wise, and that the standard form + // matrix is col-wise so RHS shifts can be applied and more columns + // can be added + matrix.ensureColwise(); + standard_form_matrix.ensureColwise(); + // Determine the objective scaling, and apply it to any offset + double objective_mu = double(lp.sense_); + standard_form_offset = objective_mu * lp.offset_; + // Work through the columns, ensuring that all have non-negativity bounds + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + double cost = objective_mu * lp.col_cost_[iCol]; + double lower = lp.col_lower_[iCol]; + double upper = lp.col_upper_[iCol]; + if (lower > -kHighsInf) { + // Finite lower bound + if (lower != 0) { + assert(1 == 2); + // x >= l, so shift x-l = X >= 0, giving x = X + l + // + // Cost contribution c(X+l) = cX + cl + standard_form_offset += cost * lower; + // Constraint contribution a(X+l) = aX + al + for (HighsInt iEl = standard_form_matrix.start_[iCol]; + iEl < standard_form_matrix.start_[iCol + 1]; iEl++) + standard_form_rhs[standard_form_matrix.index_[iEl]] -= + standard_form_matrix.value_[iEl] * lower; + } + } else if (upper < kHighsInf) { + // Finite upper bound + // + // Have to operate even if u=0, since cost and column values are negated + // + // x <= u, so shift u-x = X >= 0, giving x = u - X + // + // Cost contribution c(u-X) = cu - cX + standard_form_offset += cost * upper; + standard_form_cost[iCol] = -cost; + // Constraint contribution a(u-X) = au - aX + for (HighsInt iEl = standard_form_matrix.start_[iCol]; + iEl < standard_form_matrix.start_[iCol + 1]; iEl++) { + standard_form_rhs[standard_form_matrix.index_[iEl]] += + standard_form_matrix.value_[iEl] * upper; + standard_form_matrix.value_[iEl] = -standard_form_matrix.value_[iEl]; + } + } else { + assert(1 == 4); + // Free variable + // + // Represent as x = x+ - x-, where original column is now x+ >= + // 0 and x- >= 0 has negation of its cost and matrix column + standard_form_cost.push_back(-cost); + for (HighsInt iEl = standard_form_matrix.start_[iCol]; + iEl < standard_form_matrix.start_[iCol + 1]; iEl++) { + standard_form_matrix.index_.push_back(standard_form_matrix.index_[iEl]); + standard_form_matrix.value_.push_back( + -standard_form_matrix.value_[iEl]); + } + standard_form_matrix.start_.push_back( + HighsInt(standard_form_matrix.index_.size())); + } + } + // Now add the slack variables + for (HighsInt iX = 0; iX < HighsInt(slack_ix.size()); iX++) { + HighsInt iRow = slack_ix[iX]; + standard_form_cost.push_back(0); + if (iRow > 0) { + standard_form_matrix.index_.push_back(iRow - 1); + standard_form_matrix.value_.push_back(1); + } else { + standard_form_matrix.index_.push_back(-iRow - 1); + standard_form_matrix.value_.push_back(-1); + } + standard_form_matrix.start_.push_back( + HighsInt(standard_form_matrix.index_.size())); + } + this->standard_form_valid_ = true; + this->standard_form_offset_ = standard_form_offset; + this->standard_form_cost_ = standard_form_cost; + this->standard_form_rhs_ = standard_form_rhs; + this->standard_form_matrix_ = standard_form_matrix; + return HighsStatus::kOk; } HighsStatus Highs::basisForSolution() { diff --git a/src/mip/HighsLpRelaxation.cpp b/src/mip/HighsLpRelaxation.cpp index db6edef910..8a034fbd70 100644 --- a/src/mip/HighsLpRelaxation.cpp +++ b/src/mip/HighsLpRelaxation.cpp @@ -1148,10 +1148,11 @@ HighsLpRelaxation::Status HighsLpRelaxation::run(bool resolve_on_error) { // Status::kError as a result yielded #1962, where the root node // is unbounded. if (info.basis_validity == kBasisValidityInvalid) { - highsLogUser(mipsolver.options_mip_->log_options, - HighsLogType::kWarning, - "HighsLpRelaxation::run LP is unbounded with no basis, " - "but not returning Status::kError\n"); + printf( + // highsLogUser(mipsolver.options_mip_->log_options, + // HighsLogType::kWarning, + "HighsLpRelaxation::run LP is unbounded with no basis, " + "but not returning Status::kError\n"); // return Status::kError; } diff --git a/src/qpsolver/dantzigpricing.hpp b/src/qpsolver/dantzigpricing.hpp index 89b109e5c1..44d06542f8 100644 --- a/src/qpsolver/dantzigpricing.hpp +++ b/src/qpsolver/dantzigpricing.hpp @@ -52,7 +52,7 @@ class DantzigPricing : public Pricing { public: DantzigPricing(Runtime& rt, Basis& bas, ReducedCosts& rc) - : runtime(rt), basis(bas), redcosts(rc) {}; + : runtime(rt), basis(bas), redcosts(rc){}; HighsInt price(const QpVector& x, const QpVector& gradient) { HighsInt minidx = chooseconstrainttodrop(redcosts.getReducedCosts()); diff --git a/src/qpsolver/devexpricing.hpp b/src/qpsolver/devexpricing.hpp index a88f3ecea5..ba34ac04e5 100644 --- a/src/qpsolver/devexpricing.hpp +++ b/src/qpsolver/devexpricing.hpp @@ -58,7 +58,7 @@ class DevexPricing : public Pricing { : runtime(rt), basis(bas), redcosts(rc), - weights(std::vector(rt.instance.num_var, 1.0)) {}; + weights(std::vector(rt.instance.num_var, 1.0)){}; // B lambda = g // lambda = inv(B)g From bcadf18dc2e6c2578e04e8d9e9248f1d3709b4e9 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 25 Oct 2024 13:35:04 +0100 Subject: [PATCH 03/11] Fixed line 45 in HighsInterface.cpp --- src/lp_data/HighsInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index df008d4f6c..c8035afc5f 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -42,7 +42,7 @@ HighsStatus Highs::formStandardFormLp() { standard_form_matrix.num_col_ = lp.num_col_; // Create a HighsSparseMatrix instance to store rows extracted from // the original constraint matrix - HighsInt local_row_min_nnz = std::max(lp.num_col_, 2); + HighsInt local_row_min_nnz = std::max(lp.num_col_, HighsInt(2)); HighsSparseMatrix local_row; local_row.ensureRowwise(); local_row.num_row_ = 1; From ee8e5e70f981d40fd2d7226a91998909befc8d6d Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 25 Oct 2024 13:37:39 +0100 Subject: [PATCH 04/11] Fixed line 43 of HighsIis.cpp --- src/lp_data/HighsIis.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lp_data/HighsIis.cpp b/src/lp_data/HighsIis.cpp index 9f244415ca..06dffcabb2 100644 --- a/src/lp_data/HighsIis.cpp +++ b/src/lp_data/HighsIis.cpp @@ -40,7 +40,7 @@ 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 ", int(iCol)); printf("\nStatus: "); for (HighsInt iCol = 0; iCol < num_iis_col; iCol++) printf("%9s ", iisBoundStatusToString(this->col_bound_[iCol]).c_str()); From b625ac03c584754267adce44d1f8b1cb1b7386b8 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 25 Oct 2024 18:00:38 +0100 Subject: [PATCH 05/11] Now handles multi-case LP --- check/TestLpSolvers.cpp | 32 ++++++++++++--- src/Highs.h | 2 +- src/lp_data/Highs.cpp | 4 +- src/lp_data/HighsInterface.cpp | 74 ++++++++++++++++++++++++---------- 4 files changed, 83 insertions(+), 29 deletions(-) diff --git a/check/TestLpSolvers.cpp b/check/TestLpSolvers.cpp index b40577bf0e..ed312e616d 100644 --- a/check/TestLpSolvers.cpp +++ b/check/TestLpSolvers.cpp @@ -475,11 +475,30 @@ TEST_CASE("standard-form-lp", "[highs_lp_solver]") { std::string model; std::string model_file; Highs highs; - highs.setOptionValue("output_flag", dev_run); - model = "avgas"; - model_file = std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; - highs.readModel(model_file); + // highs.setOptionValue("output_flag", dev_run); + const bool test_mps = false; + if (test_mps) { + model = "avgas"; + model_file = std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; + highs.readModel(model_file); + } else { + HighsLp lp; + lp.offset_ = 0.5; + lp.num_col_ = 4; + lp.num_row_ = 3; + lp.col_cost_ = {1, 1, 1, -1}; + lp.col_lower_ = {1, -kHighsInf, -kHighsInf, -1}; + lp.col_upper_ = {kHighsInf, kHighsInf, 2, 3}; + lp.row_lower_ = {0, 1, -kHighsInf}; + lp.row_upper_ = {4, kHighsInf, 4}; + lp.a_matrix_.start_ = {0, 2, 4, 6, 8}; + lp.a_matrix_.index_ = {0, 2, 0, 1, 1, 2, 0, 2}; + lp.a_matrix_.value_ = {1, 1, 1, 1, 1, 1, 1, 1}; + highs.passModel(lp); + highs.writeModel(""); + } highs.run(); + highs.writeSolution("", kSolutionStylePretty); double required_objective_function_value = highs.getInfo().objective_function_value; // @@ -502,6 +521,7 @@ TEST_CASE("standard-form-lp", "[highs_lp_solver]") { HighsLp standard_form_lp; standard_form_lp.num_col_ = num_col; standard_form_lp.num_row_ = num_row; + standard_form_lp.offset_ = offset; standard_form_lp.col_cost_ = cost; standard_form_lp.col_lower_.assign(num_col, 0); standard_form_lp.col_upper_.assign(num_col, kHighsInf); @@ -511,9 +531,11 @@ TEST_CASE("standard-form-lp", "[highs_lp_solver]") { standard_form_lp.a_matrix_.index_ = index; standard_form_lp.a_matrix_.value_ = value; REQUIRE(highs.passModel(standard_form_lp) == HighsStatus::kOk); - if (dev_run) highs.writeModel(""); + // if (dev_run) + highs.writeModel(""); REQUIRE(highs.run() == HighsStatus::kOk); REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal); + highs.writeSolution("", kSolutionStylePretty); double objective_function_value = highs.getInfo().objective_function_value; double objective_difference = std::fabs(objective_function_value - required_objective_function_value) / diff --git a/src/Highs.h b/src/Highs.h index a7024cc403..724a177375 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -405,7 +405,7 @@ class Highs { * HiGHS */ HighsStatus getStandardFormLp(HighsInt& num_col, HighsInt& num_row, - HighsInt& num_nz, double offset, + HighsInt& num_nz, double& offset, double* cost = nullptr, double* rhs = nullptr, HighsInt* start = nullptr, HighsInt* index = nullptr, diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index 7277647b78..43769ef474 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -1639,12 +1639,12 @@ HighsStatus Highs::run() { } HighsStatus Highs::getStandardFormLp(HighsInt& num_col, HighsInt& num_row, - HighsInt& num_nz, double offset, + HighsInt& num_nz, double& offset, double* cost, double* rhs, HighsInt* start, HighsInt* index, double* value) { if (!this->standard_form_valid_) { HighsStatus status = formStandardFormLp(); - if (status != HighsStatus::kOk) return status; + assert(status == HighsStatus::kOk); } num_col = this->standard_form_cost_.size(); num_row = this->standard_form_rhs_.size(); diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index c8035afc5f..4dff777d08 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -52,9 +52,18 @@ HighsStatus Highs::formStandardFormLp() { local_row.start_.resize(2); HighsInt& num_nz = local_row.start_[1]; local_row.start_[0] = 0; + HighsInt num_fixed_row = 0; + HighsInt num_boxed_row = 0; + HighsInt num_lower_row = 0; + HighsInt num_upper_row = 0; HighsInt num_free_row = 0; + HighsInt num_fixed_col = 0; + HighsInt num_boxed_col = 0; + HighsInt num_lower_col = 0; + HighsInt num_upper_col = 0; + HighsInt num_free_col = 0; std::vector slack_ix; - HighsInt slack_k = 1; + HighsInt slack_k = 0; for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { double lower = lp.row_lower_[iRow]; double upper = lp.row_upper_[iRow]; @@ -65,16 +74,16 @@ HighsStatus Highs::formStandardFormLp() { continue; } if (lower == upper) { - assert(0 == 2); - // Equality + // Equality row + num_fixed_row++; matrix.getRow(iRow, num_nz, local_row.index_.data(), local_row.value_.data()); standard_form_matrix.addRows(local_row); standard_form_rhs.push_back(upper); continue; } else if (lower <= -kHighsInf) { - assert(0 == 3); - // Upper bounded, so record the slack + // Upper bounded row, so record the slack + num_upper_row++; assert(upper < kHighsInf); slack_ix.push_back(++slack_k); matrix.getRow(iRow, num_nz, local_row.index_.data(), @@ -82,7 +91,8 @@ HighsStatus Highs::formStandardFormLp() { standard_form_matrix.addRows(local_row); standard_form_rhs.push_back(upper); } else if (upper >= kHighsInf) { - // Lower bounded, so record the slack + // Lower bounded row, so record the slack + num_lower_row++; assert(lower > -kHighsInf); slack_ix.push_back(-(++slack_k)); matrix.getRow(iRow, num_nz, local_row.index_.data(), @@ -90,16 +100,16 @@ HighsStatus Highs::formStandardFormLp() { standard_form_matrix.addRows(local_row); standard_form_rhs.push_back(lower); } else { - assert(0 == 5); - // Boxed, so record the lower slack + // Boxed row, so record the lower slack assert(lower > -kHighsInf); assert(upper < kHighsInf); + num_boxed_row++; slack_ix.push_back(-(++slack_k)); matrix.getRow(iRow, num_nz, local_row.index_.data(), local_row.value_.data()); standard_form_matrix.addRows(local_row); standard_form_rhs.push_back(lower); - // .. and upper slack + // .. and upper slack, adding a copy of the row slack_ix.push_back(++slack_k); standard_form_matrix.addRows(local_row); standard_form_rhs.push_back(upper); @@ -114,9 +124,9 @@ HighsStatus Highs::formStandardFormLp() { // // x will be replaced by x = l + X (below) with X >= 0 // - // Introduce variable s >= 0 so that + // Introduce variable s >= 0 so that (with x >= l still) // - // l + X = u - s => X + s = u - l + // x = u - s => x + s = u standard_form_cost.push_back(0); standard_form_matrix.num_col_++; local_row.num_col_++; @@ -126,7 +136,7 @@ HighsStatus Highs::formStandardFormLp() { local_row.value_[1] = 1; local_row.start_[1] = 2; standard_form_matrix.addRows(local_row); - standard_form_rhs.push_back(upper - lower); + standard_form_rhs.push_back(upper); } } // Finished with both matrices, row-wise, so ensure that the @@ -145,8 +155,19 @@ HighsStatus Highs::formStandardFormLp() { double upper = lp.col_upper_[iCol]; if (lower > -kHighsInf) { // Finite lower bound + if (upper < kHighsInf) { + if (lower == upper) { + // Fixed column + num_fixed_col++; + } else { + // Boxed column + num_boxed_col++; + } + } else { + // Lower column + num_lower_col++; + } if (lower != 0) { - assert(1 == 2); // x >= l, so shift x-l = X >= 0, giving x = X + l // // Cost contribution c(X+l) = cX + cl @@ -158,8 +179,8 @@ HighsStatus Highs::formStandardFormLp() { standard_form_matrix.value_[iEl] * lower; } } else if (upper < kHighsInf) { - // Finite upper bound - // + // Upper column + num_upper_col++; // Have to operate even if u=0, since cost and column values are negated // // x <= u, so shift u-x = X >= 0, giving x = u - X @@ -167,19 +188,21 @@ HighsStatus Highs::formStandardFormLp() { // Cost contribution c(u-X) = cu - cX standard_form_offset += cost * upper; standard_form_cost[iCol] = -cost; - // Constraint contribution a(u-X) = au - aX + // Constraint contribution a(u-X) = -aX + au for (HighsInt iEl = standard_form_matrix.start_[iCol]; iEl < standard_form_matrix.start_[iCol + 1]; iEl++) { - standard_form_rhs[standard_form_matrix.index_[iEl]] += + standard_form_rhs[standard_form_matrix.index_[iEl]] -= standard_form_matrix.value_[iEl] * upper; standard_form_matrix.value_[iEl] = -standard_form_matrix.value_[iEl]; } } else { - assert(1 == 4); - // Free variable + // Free column + num_free_col++; + // Represent as x = x+ - x- + // + // where original column is now x+ >= 0 // - // Represent as x = x+ - x-, where original column is now x+ >= - // 0 and x- >= 0 has negation of its cost and matrix column + // and x- >= 0 has negation of its cost and matrix column standard_form_cost.push_back(-cost); for (HighsInt iEl = standard_form_matrix.start_[iCol]; iEl < standard_form_matrix.start_[iCol + 1]; iEl++) { @@ -210,6 +233,15 @@ HighsStatus Highs::formStandardFormLp() { this->standard_form_cost_ = standard_form_cost; this->standard_form_rhs_ = standard_form_rhs; this->standard_form_matrix_ = standard_form_matrix; + highsLogUser(options_.log_options, HighsLogType::kInfo, + "Standard form LP obtained for LP with (free / lower / upper / " + "boxed / fixed) variables" + " (%d / %d / %d / %d / %d) and constraints" + " (%d / %d / %d / %d / %d) \n", + int(num_free_col), int(num_lower_col), int(num_upper_col), + int(num_boxed_col), int(num_fixed_col), int(num_free_row), + int(num_lower_row), int(num_upper_row), int(num_boxed_row), + int(num_fixed_row)); return HighsStatus::kOk; } From ef13944b737cd8cd9f9a155caaece2434c05ca68 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 25 Oct 2024 22:53:59 +0100 Subject: [PATCH 06/11] Added clang-format off/on for two lines in QP solver --- src/mip/HighsLpRelaxation.cpp | 6 ++---- src/qpsolver/dantzigpricing.hpp | 4 +++- src/qpsolver/devexpricing.hpp | 4 +++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/mip/HighsLpRelaxation.cpp b/src/mip/HighsLpRelaxation.cpp index 8a034fbd70..16963c85bb 100644 --- a/src/mip/HighsLpRelaxation.cpp +++ b/src/mip/HighsLpRelaxation.cpp @@ -1148,12 +1148,10 @@ HighsLpRelaxation::Status HighsLpRelaxation::run(bool resolve_on_error) { // Status::kError as a result yielded #1962, where the root node // is unbounded. if (info.basis_validity == kBasisValidityInvalid) { - printf( - // highsLogUser(mipsolver.options_mip_->log_options, - // HighsLogType::kWarning, + highsLogUser(mipsolver.options_mip_->log_options, + HighsLogType::kWarning, "HighsLpRelaxation::run LP is unbounded with no basis, " "but not returning Status::kError\n"); - // return Status::kError; } if (info.primal_solution_status == kSolutionStatusFeasible) diff --git a/src/qpsolver/dantzigpricing.hpp b/src/qpsolver/dantzigpricing.hpp index 44d06542f8..08ac363418 100644 --- a/src/qpsolver/dantzigpricing.hpp +++ b/src/qpsolver/dantzigpricing.hpp @@ -52,7 +52,9 @@ class DantzigPricing : public Pricing { public: DantzigPricing(Runtime& rt, Basis& bas, ReducedCosts& rc) - : runtime(rt), basis(bas), redcosts(rc){}; + //clang-format off + : runtime(rt), basis(bas), redcosts(rc) {}; + //clang-format on HighsInt price(const QpVector& x, const QpVector& gradient) { HighsInt minidx = chooseconstrainttodrop(redcosts.getReducedCosts()); diff --git a/src/qpsolver/devexpricing.hpp b/src/qpsolver/devexpricing.hpp index ba34ac04e5..9966de07ba 100644 --- a/src/qpsolver/devexpricing.hpp +++ b/src/qpsolver/devexpricing.hpp @@ -58,7 +58,9 @@ class DevexPricing : public Pricing { : runtime(rt), basis(bas), redcosts(rc), - weights(std::vector(rt.instance.num_var, 1.0)){}; + //clang-format off + weights(std::vector(rt.instance.num_var, 1.0)) {}; + //clang-format on // B lambda = g // lambda = inv(B)g From 56eeacbace24cdadcdf937d548709f3159719076 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Fri, 25 Oct 2024 22:58:48 +0100 Subject: [PATCH 07/11] More format frigging --- src/mip/HighsLpRelaxation.cpp | 12 +++++------- src/qpsolver/dantzigpricing.hpp | 2 +- src/qpsolver/devexpricing.hpp | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/mip/HighsLpRelaxation.cpp b/src/mip/HighsLpRelaxation.cpp index 16963c85bb..3242248cef 100644 --- a/src/mip/HighsLpRelaxation.cpp +++ b/src/mip/HighsLpRelaxation.cpp @@ -1147,13 +1147,11 @@ HighsLpRelaxation::Status HighsLpRelaxation::run(bool resolve_on_error) { // postsolve cannot be run, so there is no basis. Returning // Status::kError as a result yielded #1962, where the root node // is unbounded. - if (info.basis_validity == kBasisValidityInvalid) { - highsLogUser(mipsolver.options_mip_->log_options, - HighsLogType::kWarning, - "HighsLpRelaxation::run LP is unbounded with no basis, " - "but not returning Status::kError\n"); - } - + if (info.basis_validity == kBasisValidityInvalid) + highsLogUser(mipsolver.options_mip_->log_options, + HighsLogType::kWarning, + "HighsLpRelaxation::run LP is unbounded with no basis, " + "but not returning Status::kError\n"); if (info.primal_solution_status == kSolutionStatusFeasible) mipsolver.mipdata_->trySolution(lpsolver.getSolution().col_value, 'T'); diff --git a/src/qpsolver/dantzigpricing.hpp b/src/qpsolver/dantzigpricing.hpp index 08ac363418..81a9e2a9cb 100644 --- a/src/qpsolver/dantzigpricing.hpp +++ b/src/qpsolver/dantzigpricing.hpp @@ -52,7 +52,7 @@ class DantzigPricing : public Pricing { public: DantzigPricing(Runtime& rt, Basis& bas, ReducedCosts& rc) - //clang-format off + //clang-format off : runtime(rt), basis(bas), redcosts(rc) {}; //clang-format on diff --git a/src/qpsolver/devexpricing.hpp b/src/qpsolver/devexpricing.hpp index 9966de07ba..1009f91055 100644 --- a/src/qpsolver/devexpricing.hpp +++ b/src/qpsolver/devexpricing.hpp @@ -58,7 +58,7 @@ class DevexPricing : public Pricing { : runtime(rt), basis(bas), redcosts(rc), - //clang-format off + //clang-format off weights(std::vector(rt.instance.num_var, 1.0)) {}; //clang-format on From c2a9729a3dddca1cbd35117982de6fed1762f252 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sat, 26 Oct 2024 11:28:58 +0100 Subject: [PATCH 08/11] Refactored unit tests for standard form --- check/TestLpSolvers.cpp | 94 ++++++++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 34 deletions(-) diff --git a/check/TestLpSolvers.cpp b/check/TestLpSolvers.cpp index ed312e616d..8b1efbc4ee 100644 --- a/check/TestLpSolvers.cpp +++ b/check/TestLpSolvers.cpp @@ -471,43 +471,21 @@ TEST_CASE("blending-lp-ipm", "[highs_lp_solver]") { } } -TEST_CASE("standard-form-lp", "[highs_lp_solver]") { - std::string model; - std::string model_file; +void testStandardForm(const HighsLp& lp) { Highs highs; - // highs.setOptionValue("output_flag", dev_run); - const bool test_mps = false; - if (test_mps) { - model = "avgas"; - model_file = std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; - highs.readModel(model_file); - } else { - HighsLp lp; - lp.offset_ = 0.5; - lp.num_col_ = 4; - lp.num_row_ = 3; - lp.col_cost_ = {1, 1, 1, -1}; - lp.col_lower_ = {1, -kHighsInf, -kHighsInf, -1}; - lp.col_upper_ = {kHighsInf, kHighsInf, 2, 3}; - lp.row_lower_ = {0, 1, -kHighsInf}; - lp.row_upper_ = {4, kHighsInf, 4}; - lp.a_matrix_.start_ = {0, 2, 4, 6, 8}; - lp.a_matrix_.index_ = {0, 2, 0, 1, 1, 2, 0, 2}; - lp.a_matrix_.value_ = {1, 1, 1, 1, 1, 1, 1, 1}; - highs.passModel(lp); - highs.writeModel(""); - } + highs.setOptionValue("output_flag", dev_run); + highs.passModel(lp); highs.run(); highs.writeSolution("", kSolutionStylePretty); double required_objective_function_value = - highs.getInfo().objective_function_value; - // + highs.getInfo().objective_function_value; + HighsInt num_col; HighsInt num_row; HighsInt num_nz; double offset; REQUIRE(highs.getStandardFormLp(num_col, num_row, num_nz, offset) == - HighsStatus::kOk); + HighsStatus::kOk); std::vector cost(num_col); std::vector rhs(num_row); @@ -515,8 +493,8 @@ TEST_CASE("standard-form-lp", "[highs_lp_solver]") { std::vector index(num_nz); std::vector value(num_nz); REQUIRE(highs.getStandardFormLp(num_col, num_row, num_nz, offset, cost.data(), - rhs.data(), start.data(), index.data(), - value.data()) == HighsStatus::kOk); + rhs.data(), start.data(), index.data(), + value.data()) == HighsStatus::kOk); HighsLp standard_form_lp; standard_form_lp.num_col_ = num_col; @@ -531,15 +509,15 @@ TEST_CASE("standard-form-lp", "[highs_lp_solver]") { standard_form_lp.a_matrix_.index_ = index; standard_form_lp.a_matrix_.value_ = value; REQUIRE(highs.passModel(standard_form_lp) == HighsStatus::kOk); - // if (dev_run) - highs.writeModel(""); + if (dev_run) + highs.writeModel(""); REQUIRE(highs.run() == HighsStatus::kOk); REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal); highs.writeSolution("", kSolutionStylePretty); double objective_function_value = highs.getInfo().objective_function_value; double objective_difference = - std::fabs(objective_function_value - required_objective_function_value) / - std::max(1.0, std::fabs(required_objective_function_value)); + std::fabs(objective_function_value - required_objective_function_value) / + std::max(1.0, std::fabs(required_objective_function_value)); REQUIRE(objective_difference < 1e-10); const bool look_at_presolved_lp = false; if (look_at_presolved_lp) { @@ -553,3 +531,51 @@ TEST_CASE("standard-form-lp", "[highs_lp_solver]") { highs.writeModel(""); } } + +void testStandardFormModel(const std::string model) { + const std::string model_file = std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps";; + Highs highs; + highs.setOptionValue("output_flag", dev_run); + highs.readModel(model_file); + HighsLp lp = highs.getLp(); + testStandardForm(lp); +} + +TEST_CASE("standard-form-mps", "[highs_lp_solver]") { + testStandardFormModel("avgas"); +} + +TEST_CASE("standard-form-lp", "[highs_lp_solver]") { + HighsLp lp; + lp.offset_ = -0.5; + lp.num_col_ = 4; + lp.num_row_ = 3; + lp.col_cost_ = {1, 1, 1, -1}; + lp.col_lower_ = {1, -kHighsInf, -kHighsInf, -1}; + lp.col_upper_ = {kHighsInf, kHighsInf, 2, 3}; + lp.row_lower_ = {0, 1, -kHighsInf}; + lp.row_upper_ = {4, kHighsInf, 4}; + lp.a_matrix_.start_ = {0, 2, 4, 6, 8}; + lp.a_matrix_.index_ = {0, 2, 0, 1, 1, 2, 0, 2}; + lp.a_matrix_.value_ = {1, 1, 1, 1, 1, 1, 1, 1}; + + testStandardForm(lp); + Highs highs; + highs.setOptionValue("output_flag", dev_run); + + std::vector index; + std::vector value; + // Add a fixed column and a fixed row + highs.passModel(lp); + index = {0, 1, 2}; + value = {-1, 1, -1}; + REQUIRE(highs.addCol(-2.0, 1.0, 1.0, 3, index.data(), value.data()) == HighsStatus::kOk); + index = {0, 1, 2, 3}; + value = {-2, -1, 1, 3}; + REQUIRE(highs.addRow(1.0, 1.0, 4, index.data(), value.data()) == HighsStatus::kOk); + + testStandardForm(highs.getLp()); + + + +} From 18dffedc429306f94e704b756ee969fb934018c4 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sat, 26 Oct 2024 12:27:59 +0100 Subject: [PATCH 09/11] Standard form conversion works on 100 LPs --- check/TestLpSolvers.cpp | 6 +++--- src/lp_data/HighsInterface.cpp | 13 ++++++++----- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/check/TestLpSolvers.cpp b/check/TestLpSolvers.cpp index 8b1efbc4ee..b28380ded9 100644 --- a/check/TestLpSolvers.cpp +++ b/check/TestLpSolvers.cpp @@ -476,7 +476,7 @@ void testStandardForm(const HighsLp& lp) { highs.setOptionValue("output_flag", dev_run); highs.passModel(lp); highs.run(); - highs.writeSolution("", kSolutionStylePretty); + // highs.writeSolution("", kSolutionStylePretty); double required_objective_function_value = highs.getInfo().objective_function_value; @@ -509,8 +509,7 @@ void testStandardForm(const HighsLp& lp) { standard_form_lp.a_matrix_.index_ = index; standard_form_lp.a_matrix_.value_ = value; REQUIRE(highs.passModel(standard_form_lp) == HighsStatus::kOk); - if (dev_run) - highs.writeModel(""); + // highs.writeModel(""); REQUIRE(highs.run() == HighsStatus::kOk); REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal); highs.writeSolution("", kSolutionStylePretty); @@ -543,6 +542,7 @@ void testStandardFormModel(const std::string model) { TEST_CASE("standard-form-mps", "[highs_lp_solver]") { testStandardFormModel("avgas"); + testStandardFormModel("afiro"); } TEST_CASE("standard-form-lp", "[highs_lp_solver]") { diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 4dff777d08..98affbd699 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -63,7 +63,6 @@ HighsStatus Highs::formStandardFormLp() { HighsInt num_upper_col = 0; HighsInt num_free_col = 0; std::vector slack_ix; - HighsInt slack_k = 0; for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { double lower = lp.row_lower_[iRow]; double upper = lp.row_upper_[iRow]; @@ -85,7 +84,8 @@ HighsStatus Highs::formStandardFormLp() { // Upper bounded row, so record the slack num_upper_row++; assert(upper < kHighsInf); - slack_ix.push_back(++slack_k); + HighsInt standard_form_row = standard_form_rhs.size(); + slack_ix.push_back(standard_form_row+1); matrix.getRow(iRow, num_nz, local_row.index_.data(), local_row.value_.data()); standard_form_matrix.addRows(local_row); @@ -94,7 +94,8 @@ HighsStatus Highs::formStandardFormLp() { // Lower bounded row, so record the slack num_lower_row++; assert(lower > -kHighsInf); - slack_ix.push_back(-(++slack_k)); + HighsInt standard_form_row = standard_form_rhs.size(); + slack_ix.push_back(-(standard_form_row+1)); matrix.getRow(iRow, num_nz, local_row.index_.data(), local_row.value_.data()); standard_form_matrix.addRows(local_row); @@ -104,13 +105,15 @@ HighsStatus Highs::formStandardFormLp() { assert(lower > -kHighsInf); assert(upper < kHighsInf); num_boxed_row++; - slack_ix.push_back(-(++slack_k)); + HighsInt standard_form_row = standard_form_rhs.size(); + slack_ix.push_back(-(standard_form_row+1)); matrix.getRow(iRow, num_nz, local_row.index_.data(), local_row.value_.data()); standard_form_matrix.addRows(local_row); standard_form_rhs.push_back(lower); // .. and upper slack, adding a copy of the row - slack_ix.push_back(++slack_k); + standard_form_row = standard_form_rhs.size(); + slack_ix.push_back(standard_form_row+1); standard_form_matrix.addRows(local_row); standard_form_rhs.push_back(upper); } From afe2f9a3bca1f3f5e3ad9a7f81e4f6cc00193dd5 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sat, 26 Oct 2024 12:44:00 +0100 Subject: [PATCH 10/11] Formation of standard form works for minimizations --- check/TestLpSolvers.cpp | 27 ++++---- src/lp_data/HighsInterface.cpp | 119 +++++++++++++++----------------- src/qpsolver/dantzigpricing.hpp | 4 +- src/qpsolver/devexpricing.hpp | 4 +- 4 files changed, 74 insertions(+), 80 deletions(-) diff --git a/check/TestLpSolvers.cpp b/check/TestLpSolvers.cpp index b28380ded9..a9dca8131e 100644 --- a/check/TestLpSolvers.cpp +++ b/check/TestLpSolvers.cpp @@ -478,14 +478,14 @@ void testStandardForm(const HighsLp& lp) { highs.run(); // highs.writeSolution("", kSolutionStylePretty); double required_objective_function_value = - highs.getInfo().objective_function_value; + highs.getInfo().objective_function_value; HighsInt num_col; HighsInt num_row; HighsInt num_nz; double offset; REQUIRE(highs.getStandardFormLp(num_col, num_row, num_nz, offset) == - HighsStatus::kOk); + HighsStatus::kOk); std::vector cost(num_col); std::vector rhs(num_row); @@ -493,8 +493,8 @@ void testStandardForm(const HighsLp& lp) { std::vector index(num_nz); std::vector value(num_nz); REQUIRE(highs.getStandardFormLp(num_col, num_row, num_nz, offset, cost.data(), - rhs.data(), start.data(), index.data(), - value.data()) == HighsStatus::kOk); + rhs.data(), start.data(), index.data(), + value.data()) == HighsStatus::kOk); HighsLp standard_form_lp; standard_form_lp.num_col_ = num_col; @@ -515,8 +515,8 @@ void testStandardForm(const HighsLp& lp) { highs.writeSolution("", kSolutionStylePretty); double objective_function_value = highs.getInfo().objective_function_value; double objective_difference = - std::fabs(objective_function_value - required_objective_function_value) / - std::max(1.0, std::fabs(required_objective_function_value)); + std::fabs(objective_function_value - required_objective_function_value) / + std::max(1.0, std::fabs(required_objective_function_value)); REQUIRE(objective_difference < 1e-10); const bool look_at_presolved_lp = false; if (look_at_presolved_lp) { @@ -532,7 +532,9 @@ void testStandardForm(const HighsLp& lp) { } void testStandardFormModel(const std::string model) { - const std::string model_file = std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps";; + const std::string model_file = + std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps"; + ; Highs highs; highs.setOptionValue("output_flag", dev_run); highs.readModel(model_file); @@ -564,18 +566,17 @@ TEST_CASE("standard-form-lp", "[highs_lp_solver]") { highs.setOptionValue("output_flag", dev_run); std::vector index; - std::vector value; + std::vector value; // Add a fixed column and a fixed row highs.passModel(lp); index = {0, 1, 2}; value = {-1, 1, -1}; - REQUIRE(highs.addCol(-2.0, 1.0, 1.0, 3, index.data(), value.data()) == HighsStatus::kOk); + REQUIRE(highs.addCol(-2.0, 1.0, 1.0, 3, index.data(), value.data()) == + HighsStatus::kOk); index = {0, 1, 2, 3}; value = {-2, -1, 1, 3}; - REQUIRE(highs.addRow(1.0, 1.0, 4, index.data(), value.data()) == HighsStatus::kOk); + REQUIRE(highs.addRow(1.0, 1.0, 4, index.data(), value.data()) == + HighsStatus::kOk); testStandardForm(highs.getLp()); - - - } diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 98affbd699..bd247abb78 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -33,13 +33,8 @@ HighsStatus Highs::formStandardFormLp() { // columns are assumed to come before any new columns, so their // costs must be defined befor costs of new columns. this->standard_form_cost_ = lp.col_cost_; - // Whilst debugging, work with local data structure - double standard_form_offset; - std::vector standard_form_cost = lp.col_cost_; - std::vector standard_form_rhs; - HighsSparseMatrix standard_form_matrix; - standard_form_matrix.format_ = MatrixFormat::kRowwise; - standard_form_matrix.num_col_ = lp.num_col_; + this->standard_form_matrix_.format_ = MatrixFormat::kRowwise; + this->standard_form_matrix_.num_col_ = lp.num_col_; // Create a HighsSparseMatrix instance to store rows extracted from // the original constraint matrix HighsInt local_row_min_nnz = std::max(lp.num_col_, HighsInt(2)); @@ -77,45 +72,45 @@ HighsStatus Highs::formStandardFormLp() { num_fixed_row++; matrix.getRow(iRow, num_nz, local_row.index_.data(), local_row.value_.data()); - standard_form_matrix.addRows(local_row); - standard_form_rhs.push_back(upper); + this->standard_form_matrix_.addRows(local_row); + this->standard_form_rhs_.push_back(upper); continue; } else if (lower <= -kHighsInf) { // Upper bounded row, so record the slack num_upper_row++; assert(upper < kHighsInf); - HighsInt standard_form_row = standard_form_rhs.size(); - slack_ix.push_back(standard_form_row+1); + HighsInt standard_form_row = this->standard_form_rhs_.size(); + slack_ix.push_back(standard_form_row + 1); matrix.getRow(iRow, num_nz, local_row.index_.data(), local_row.value_.data()); - standard_form_matrix.addRows(local_row); - standard_form_rhs.push_back(upper); + this->standard_form_matrix_.addRows(local_row); + this->standard_form_rhs_.push_back(upper); } else if (upper >= kHighsInf) { // Lower bounded row, so record the slack num_lower_row++; assert(lower > -kHighsInf); - HighsInt standard_form_row = standard_form_rhs.size(); - slack_ix.push_back(-(standard_form_row+1)); + HighsInt standard_form_row = this->standard_form_rhs_.size(); + slack_ix.push_back(-(standard_form_row + 1)); matrix.getRow(iRow, num_nz, local_row.index_.data(), local_row.value_.data()); - standard_form_matrix.addRows(local_row); - standard_form_rhs.push_back(lower); + this->standard_form_matrix_.addRows(local_row); + this->standard_form_rhs_.push_back(lower); } else { // Boxed row, so record the lower slack assert(lower > -kHighsInf); assert(upper < kHighsInf); num_boxed_row++; - HighsInt standard_form_row = standard_form_rhs.size(); - slack_ix.push_back(-(standard_form_row+1)); + HighsInt standard_form_row = this->standard_form_rhs_.size(); + slack_ix.push_back(-(standard_form_row + 1)); matrix.getRow(iRow, num_nz, local_row.index_.data(), local_row.value_.data()); - standard_form_matrix.addRows(local_row); - standard_form_rhs.push_back(lower); + this->standard_form_matrix_.addRows(local_row); + this->standard_form_rhs_.push_back(lower); // .. and upper slack, adding a copy of the row - standard_form_row = standard_form_rhs.size(); - slack_ix.push_back(standard_form_row+1); - standard_form_matrix.addRows(local_row); - standard_form_rhs.push_back(upper); + standard_form_row = this->standard_form_rhs_.size(); + slack_ix.push_back(standard_form_row + 1); + this->standard_form_matrix_.addRows(local_row); + this->standard_form_rhs_.push_back(upper); } } // Add rows corresponding to boxed columns @@ -130,16 +125,16 @@ HighsStatus Highs::formStandardFormLp() { // Introduce variable s >= 0 so that (with x >= l still) // // x = u - s => x + s = u - standard_form_cost.push_back(0); - standard_form_matrix.num_col_++; + this->standard_form_cost_.push_back(0); + this->standard_form_matrix_.num_col_++; local_row.num_col_++; local_row.index_[0] = iCol; - local_row.index_[1] = standard_form_matrix.num_col_ - 1; + local_row.index_[1] = this->standard_form_matrix_.num_col_ - 1; local_row.value_[0] = 1; local_row.value_[1] = 1; local_row.start_[1] = 2; - standard_form_matrix.addRows(local_row); - standard_form_rhs.push_back(upper); + this->standard_form_matrix_.addRows(local_row); + this->standard_form_rhs_.push_back(upper); } } // Finished with both matrices, row-wise, so ensure that the @@ -147,10 +142,10 @@ HighsStatus Highs::formStandardFormLp() { // matrix is col-wise so RHS shifts can be applied and more columns // can be added matrix.ensureColwise(); - standard_form_matrix.ensureColwise(); + this->standard_form_matrix_.ensureColwise(); // Determine the objective scaling, and apply it to any offset double objective_mu = double(lp.sense_); - standard_form_offset = objective_mu * lp.offset_; + this->standard_form_offset_ = objective_mu * lp.offset_; // Work through the columns, ensuring that all have non-negativity bounds for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { double cost = objective_mu * lp.col_cost_[iCol]; @@ -174,12 +169,12 @@ HighsStatus Highs::formStandardFormLp() { // x >= l, so shift x-l = X >= 0, giving x = X + l // // Cost contribution c(X+l) = cX + cl - standard_form_offset += cost * lower; + this->standard_form_offset_ += cost * lower; // Constraint contribution a(X+l) = aX + al - for (HighsInt iEl = standard_form_matrix.start_[iCol]; - iEl < standard_form_matrix.start_[iCol + 1]; iEl++) - standard_form_rhs[standard_form_matrix.index_[iEl]] -= - standard_form_matrix.value_[iEl] * lower; + for (HighsInt iEl = this->standard_form_matrix_.start_[iCol]; + iEl < this->standard_form_matrix_.start_[iCol + 1]; iEl++) + this->standard_form_rhs_[this->standard_form_matrix_.index_[iEl]] -= + this->standard_form_matrix_.value_[iEl] * lower; } } else if (upper < kHighsInf) { // Upper column @@ -189,14 +184,15 @@ HighsStatus Highs::formStandardFormLp() { // x <= u, so shift u-x = X >= 0, giving x = u - X // // Cost contribution c(u-X) = cu - cX - standard_form_offset += cost * upper; - standard_form_cost[iCol] = -cost; + this->standard_form_offset_ += cost * upper; + this->standard_form_cost_[iCol] = -cost; // Constraint contribution a(u-X) = -aX + au - for (HighsInt iEl = standard_form_matrix.start_[iCol]; - iEl < standard_form_matrix.start_[iCol + 1]; iEl++) { - standard_form_rhs[standard_form_matrix.index_[iEl]] -= - standard_form_matrix.value_[iEl] * upper; - standard_form_matrix.value_[iEl] = -standard_form_matrix.value_[iEl]; + for (HighsInt iEl = this->standard_form_matrix_.start_[iCol]; + iEl < this->standard_form_matrix_.start_[iCol + 1]; iEl++) { + this->standard_form_rhs_[this->standard_form_matrix_.index_[iEl]] -= + this->standard_form_matrix_.value_[iEl] * upper; + this->standard_form_matrix_.value_[iEl] = + -this->standard_form_matrix_.value_[iEl]; } } else { // Free column @@ -206,36 +202,33 @@ HighsStatus Highs::formStandardFormLp() { // where original column is now x+ >= 0 // // and x- >= 0 has negation of its cost and matrix column - standard_form_cost.push_back(-cost); - for (HighsInt iEl = standard_form_matrix.start_[iCol]; - iEl < standard_form_matrix.start_[iCol + 1]; iEl++) { - standard_form_matrix.index_.push_back(standard_form_matrix.index_[iEl]); - standard_form_matrix.value_.push_back( - -standard_form_matrix.value_[iEl]); + this->standard_form_cost_.push_back(-cost); + for (HighsInt iEl = this->standard_form_matrix_.start_[iCol]; + iEl < this->standard_form_matrix_.start_[iCol + 1]; iEl++) { + this->standard_form_matrix_.index_.push_back( + this->standard_form_matrix_.index_[iEl]); + this->standard_form_matrix_.value_.push_back( + -this->standard_form_matrix_.value_[iEl]); } - standard_form_matrix.start_.push_back( - HighsInt(standard_form_matrix.index_.size())); + this->standard_form_matrix_.start_.push_back( + HighsInt(this->standard_form_matrix_.index_.size())); } } // Now add the slack variables for (HighsInt iX = 0; iX < HighsInt(slack_ix.size()); iX++) { HighsInt iRow = slack_ix[iX]; - standard_form_cost.push_back(0); + this->standard_form_cost_.push_back(0); if (iRow > 0) { - standard_form_matrix.index_.push_back(iRow - 1); - standard_form_matrix.value_.push_back(1); + this->standard_form_matrix_.index_.push_back(iRow - 1); + this->standard_form_matrix_.value_.push_back(1); } else { - standard_form_matrix.index_.push_back(-iRow - 1); - standard_form_matrix.value_.push_back(-1); + this->standard_form_matrix_.index_.push_back(-iRow - 1); + this->standard_form_matrix_.value_.push_back(-1); } - standard_form_matrix.start_.push_back( - HighsInt(standard_form_matrix.index_.size())); + this->standard_form_matrix_.start_.push_back( + HighsInt(this->standard_form_matrix_.index_.size())); } this->standard_form_valid_ = true; - this->standard_form_offset_ = standard_form_offset; - this->standard_form_cost_ = standard_form_cost; - this->standard_form_rhs_ = standard_form_rhs; - this->standard_form_matrix_ = standard_form_matrix; highsLogUser(options_.log_options, HighsLogType::kInfo, "Standard form LP obtained for LP with (free / lower / upper / " "boxed / fixed) variables" diff --git a/src/qpsolver/dantzigpricing.hpp b/src/qpsolver/dantzigpricing.hpp index 81a9e2a9cb..05b0d9c00c 100644 --- a/src/qpsolver/dantzigpricing.hpp +++ b/src/qpsolver/dantzigpricing.hpp @@ -52,9 +52,9 @@ class DantzigPricing : public Pricing { public: DantzigPricing(Runtime& rt, Basis& bas, ReducedCosts& rc) - //clang-format off + // clang-format off : runtime(rt), basis(bas), redcosts(rc) {}; - //clang-format on + // clang-format on HighsInt price(const QpVector& x, const QpVector& gradient) { HighsInt minidx = chooseconstrainttodrop(redcosts.getReducedCosts()); diff --git a/src/qpsolver/devexpricing.hpp b/src/qpsolver/devexpricing.hpp index 1009f91055..6664a98611 100644 --- a/src/qpsolver/devexpricing.hpp +++ b/src/qpsolver/devexpricing.hpp @@ -58,9 +58,9 @@ class DevexPricing : public Pricing { : runtime(rt), basis(bas), redcosts(rc), - //clang-format off + // clang-format off weights(std::vector(rt.instance.num_var, 1.0)) {}; - //clang-format on + // clang-format on // B lambda = g // lambda = inv(B)g From ed462edf07e908f389a01360df1b97a46fd7aed1 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sat, 26 Oct 2024 13:04:45 +0100 Subject: [PATCH 11/11] Standard form conversion now works for maximizations --- check/TestLpSolvers.cpp | 12 ++++++++---- src/lp_data/HighsInterface.cpp | 14 ++++++++------ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/check/TestLpSolvers.cpp b/check/TestLpSolvers.cpp index a9dca8131e..6ef4fb4692 100644 --- a/check/TestLpSolvers.cpp +++ b/check/TestLpSolvers.cpp @@ -2,7 +2,7 @@ #include "Highs.h" #include "catch.hpp" -const bool dev_run = false; +const bool dev_run = true; // false; struct IterationCount { HighsInt simplex; @@ -474,6 +474,7 @@ TEST_CASE("blending-lp-ipm", "[highs_lp_solver]") { void testStandardForm(const HighsLp& lp) { Highs highs; highs.setOptionValue("output_flag", dev_run); + HighsInt sense = HighsInt(lp.sense_); highs.passModel(lp); highs.run(); // highs.writeSolution("", kSolutionStylePretty); @@ -513,7 +514,8 @@ void testStandardForm(const HighsLp& lp) { REQUIRE(highs.run() == HighsStatus::kOk); REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal); highs.writeSolution("", kSolutionStylePretty); - double objective_function_value = highs.getInfo().objective_function_value; + double objective_function_value = + sense * highs.getInfo().objective_function_value; double objective_difference = std::fabs(objective_function_value - required_objective_function_value) / std::max(1.0, std::fabs(required_objective_function_value)); @@ -567,7 +569,7 @@ TEST_CASE("standard-form-lp", "[highs_lp_solver]") { std::vector index; std::vector value; - // Add a fixed column and a fixed row + // Add a fixed column and a fixed row, and maximize highs.passModel(lp); index = {0, 1, 2}; value = {-1, 1, -1}; @@ -577,6 +579,8 @@ TEST_CASE("standard-form-lp", "[highs_lp_solver]") { value = {-2, -1, 1, 3}; REQUIRE(highs.addRow(1.0, 1.0, 4, index.data(), value.data()) == HighsStatus::kOk); - + REQUIRE(highs.changeObjectiveSense(ObjSense::kMaximize) == HighsStatus::kOk); + printf( + "\nNow test by adding a fixed column and a fixed row, and maximizing\n"); testStandardForm(highs.getLp()); } diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index bd247abb78..a669339dc4 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -31,8 +31,13 @@ HighsStatus Highs::formStandardFormLp() { // boxed rows can be transformed to pairs of one-sided rows, // requiring the standard form matrix to be row-wise. The original // columns are assumed to come before any new columns, so their - // costs must be defined befor costs of new columns. - this->standard_form_cost_ = lp.col_cost_; + // costs (as a minimization) must be defined befor costs of new + // columns. + // Determine the objective scaling, and apply it to any offset + HighsInt sense = HighsInt(lp.sense_); + this->standard_form_offset_ = sense * lp.offset_; + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + this->standard_form_cost_.push_back(sense * lp.col_cost_[iCol]); this->standard_form_matrix_.format_ = MatrixFormat::kRowwise; this->standard_form_matrix_.num_col_ = lp.num_col_; // Create a HighsSparseMatrix instance to store rows extracted from @@ -143,12 +148,9 @@ HighsStatus Highs::formStandardFormLp() { // can be added matrix.ensureColwise(); this->standard_form_matrix_.ensureColwise(); - // Determine the objective scaling, and apply it to any offset - double objective_mu = double(lp.sense_); - this->standard_form_offset_ = objective_mu * lp.offset_; // Work through the columns, ensuring that all have non-negativity bounds for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { - double cost = objective_mu * lp.col_cost_[iCol]; + double cost = sense * lp.col_cost_[iCol]; double lower = lp.col_lower_[iCol]; double upper = lp.col_upper_[iCol]; if (lower > -kHighsInf) {