From 9b69146ee791afaefe1d02bbbc8d3c4c946bee89 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 17 Apr 2023 14:59:40 +0100 Subject: [PATCH 1/5] LO and UP used for bounds on integer variables in MPS files --- src/io/HMPSIO.cpp | 49 ++++++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/src/io/HMPSIO.cpp b/src/io/HMPSIO.cpp index 0aeb08a3ad..0ac99b2d2c 100644 --- a/src/io/HMPSIO.cpp +++ b/src/io/HMPSIO.cpp @@ -612,9 +612,8 @@ HighsStatus writeMps( const vector& integrality, const std::string objective_name, const vector& col_names, const vector& row_names, const bool use_free_format) { - const bool write_zero_no_cost_columns = true; - HighsInt num_zero_no_cost_columns = 0; - HighsInt num_zero_no_cost_columns_in_bounds_section = 0; + HighsInt num_no_cost_zero_columns = 0; + HighsInt num_no_cost_zero_columns_in_bounds_section = 0; highsLogDev(log_options, HighsLogType::kInfo, "writeMPS: Trying to open file %s\n", filename.c_str()); FILE* file = fopen(filename.c_str(), "w"); @@ -771,15 +770,17 @@ HighsStatus writeMps( bool integerFg = false; HighsInt nIntegerMk = 0; fprintf(file, "COLUMNS\n"); + const bool write_no_cost_zero_columns = true; for (HighsInt c_n = 0; c_n < num_col; c_n++) { - if (a_start[c_n] == a_start[c_n + 1] && col_cost[c_n] == 0) { + const bool no_cost_zero_column = + !col_cost[c_n] && a_start[c_n] == a_start[c_n + 1]; + if (no_cost_zero_column) { // Possibly skip this column as it's zero and has no cost - num_zero_no_cost_columns++; - if (write_zero_no_cost_columns) { + num_no_cost_zero_columns++; + if (write_no_cost_zero_columns) { // Give the column a presence by writing out a zero cost - double v = 0; fprintf(file, " %-8s %-8s %.15g\n", col_names[c_n].c_str(), - objective_name.c_str(), v); + objective_name.c_str(), 0.0); } continue; } @@ -852,13 +853,15 @@ HighsStatus writeMps( discrete = integrality[c_n] == HighsVarType::kInteger || integrality[c_n] == HighsVarType::kSemiContinuous || integrality[c_n] == HighsVarType::kSemiInteger; - if (a_start[c_n] == a_start[c_n + 1] && col_cost[c_n] == 0) { + const bool no_cost_zero_column = + !col_cost[c_n] && a_start[c_n] == a_start[c_n + 1]; + if (no_cost_zero_column) { // Possibly skip this column if it's zero and has no cost if (!highs_isInfinity(ub) || lb) { // Column would have a bound to report - num_zero_no_cost_columns_in_bounds_section++; + num_no_cost_zero_columns_in_bounds_section++; } - if (!write_zero_no_cost_columns) continue; + if (!write_no_cost_zero_columns) continue; } if (lb == ub) { // Equal lower and upper bounds: Fixed @@ -874,16 +877,18 @@ HighsStatus writeMps( // Binary fprintf(file, " BV BOUND %-8s\n", col_names[c_n].c_str()); } else { - if (!highs_isInfinity(-lb)) { - // Finite lower bound. No need to state this if LB is - // zero unless UB is infinte - if (lb || highs_isInfinity(ub)) - fprintf(file, " LI BOUND %-8s %.15g\n", - col_names[c_n].c_str(), lb); + assert(write_no_cost_zero_columns); + // No cost zero columns have a presence in the COLUMNS + // section, so no need to indicate integrality using LI + // or UI bounds. Avoids need for integer-valued bounds + if (!highs_isInfinity(-lb) && lb) { + // Finite, nonzero lower bound. + fprintf(file, " LO BOUND %-8s %.15g\n", + col_names[c_n].c_str(), lb); } if (!highs_isInfinity(ub)) { // Finite upper bound - fprintf(file, " UI BOUND %-8s %.15g\n", + fprintf(file, " UP BOUND %-8s %.15g\n", col_names[c_n].c_str(), ub); } } @@ -938,15 +943,15 @@ HighsStatus writeMps( } } fprintf(file, "ENDATA\n"); - if (num_zero_no_cost_columns) + if (num_no_cost_zero_columns) highsLogUser(log_options, HighsLogType::kInfo, "Model has %" HIGHSINT_FORMAT " zero columns with no costs: %" HIGHSINT_FORMAT " have finite upper bounds " "or nonzero lower bounds and are %swritten in MPS file\n", - num_zero_no_cost_columns, - num_zero_no_cost_columns_in_bounds_section, - write_zero_no_cost_columns ? "" : "not "); + num_no_cost_zero_columns, + num_no_cost_zero_columns_in_bounds_section, + write_no_cost_zero_columns ? "" : "not "); fclose(file); return HighsStatus::kOk; } From fac20cd5aaf2888846ffc00ca419b2e607393179 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 17 Apr 2023 16:51:33 +0100 Subject: [PATCH 2/5] Now ignoring inconsistent bounds on semi-variables in assessBounds --- check/TestSemiVariables.cpp | 31 +++++++++++++++++++++++++++++++ src/lp_data/HighsLpUtils.cpp | 30 +++++++++++++++++++++++++++--- src/lp_data/HighsLpUtils.h | 3 ++- 3 files changed, 60 insertions(+), 4 deletions(-) diff --git a/check/TestSemiVariables.cpp b/check/TestSemiVariables.cpp index a16222b234..dc93f571cd 100644 --- a/check/TestSemiVariables.cpp +++ b/check/TestSemiVariables.cpp @@ -234,6 +234,37 @@ TEST_CASE("semi-variable-file", "[highs_test_semi_variables]") { optimal_objective_function_value) < double_equal_tolerance); } +TEST_CASE("semi-variable-inconsistent-bounds", "[highs_test_semi_variables]") { + HighsLp lp; + lp.num_col_ = 1; + lp.num_row_ = 0; + lp.col_cost_ = {1}; + lp.col_lower_ = {1}; + lp.col_upper_ = {-1}; + lp.a_matrix_.start_ = {0, 0}; + lp.integrality_ = {semi_continuous}; + Highs highs; + // highs.setOptionValue("output_flag", dev_run); + highs.passModel(lp); + highs.run(); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal); + REQUIRE(highs.getSolution().col_value[0] == 0); + // Ensure that inconsistent bounds with negative lower are still + // accepted + lp.col_lower_[0] = -1; + lp.col_upper_[0] = -2; + highs.passModel(lp); + highs.run(); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal); + REQUIRE(highs.getSolution().col_value[0] == 0); + // Ensure that continuous variables with inconsistent bounds yield + // infeasibility + highs.setOptionValue("solve_relaxation", true); + highs.passModel(lp); + highs.run(); + REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); +} + void semiModel0(HighsLp& lp) { lp.num_col_ = 4; lp.num_row_ = 4; diff --git a/src/lp_data/HighsLpUtils.cpp b/src/lp_data/HighsLpUtils.cpp index 43da7949ba..ba553a9fd2 100644 --- a/src/lp_data/HighsLpUtils.cpp +++ b/src/lp_data/HighsLpUtils.cpp @@ -64,7 +64,8 @@ HighsStatus assessLp(HighsLp& lp, const HighsOptions& options) { if (return_status == HighsStatus::kError) return return_status; // Assess the LP column bounds call_status = assessBounds(options, "Col", 0, index_collection, lp.col_lower_, - lp.col_upper_, options.infinite_bound); + lp.col_upper_, options.infinite_bound, + lp.integrality_.data()); return_status = interpretCallStatus(options.log_options, call_status, return_status, "assessBounds"); if (return_status == HighsStatus::kError) return return_status; @@ -348,7 +349,8 @@ HighsStatus assessBounds(const HighsOptions& options, const char* type, const HighsInt ml_ix_os, const HighsIndexCollection& index_collection, vector& lower, vector& upper, - const double infinite_bound) { + const double infinite_bound, + const HighsVarType* integrality) { HighsStatus return_status = HighsStatus::kOk; assert(ok(index_collection)); HighsInt from_k; @@ -419,6 +421,12 @@ HighsStatus assessBounds(const HighsOptions& options, const char* type, } // Check that the lower bound does not exceed the upper bound bool legalLowerUpperBound = lower[usr_ix] <= upper[usr_ix]; + if (integrality) { + // Legal for semi-variables to have inconsistent bounds + if (integrality[usr_ix] == HighsVarType::kSemiContinuous || + integrality[usr_ix] == HighsVarType::kSemiInteger) + legalLowerUpperBound = true; + } if (!legalLowerUpperBound) { // Leave inconsistent bounds to be used to deduce infeasibility highsLogUser(options.log_options, HighsLogType::kWarning, @@ -476,6 +484,7 @@ HighsStatus assessIntegrality(HighsLp& lp, const HighsOptions& options) { HighsInt num_illegal_lower = 0; HighsInt num_illegal_upper = 0; HighsInt num_modified_upper = 0; + HighsInt num_inconsistent_semi = 0; HighsInt num_non_semi = 0; HighsInt num_non_continuous_variables = 0; const double kLowerBoundMu = 10.0; @@ -521,6 +530,14 @@ HighsStatus assessIntegrality(HighsLp& lp, const HighsOptions& options) { num_non_continuous_variables++; } } + if (num_inconsistent_semi) { + highsLogUser(options.log_options, HighsLogType::kWarning, + "%" HIGHSINT_FORMAT + " semi-continuous/integer variable(s) have inconsistent bounds " + "so are fixed at zero\n", + num_inconsistent_semi); + return_status = HighsStatus::kWarning; + } if (num_non_semi) { highsLogUser(options.log_options, HighsLogType::kWarning, "%" HIGHSINT_FORMAT @@ -2546,8 +2563,15 @@ HighsStatus calculateRowValuesQuad(const HighsLp& lp, HighsSolution& solution) { bool isBoundInfeasible(const HighsLogOptions& log_options, const HighsLp& lp) { HighsInt num_bound_infeasible = 0; - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + const bool has_integrality = lp.integrality_.size() > 0; + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + if (has_integrality) { + // Semi-variables can have inconsistent bounds + if (lp.integrality_[iCol] == HighsVarType::kSemiContinuous || + lp.integrality_[iCol] == HighsVarType::kSemiInteger) continue; + } if (lp.col_upper_[iCol] < lp.col_lower_[iCol]) num_bound_infeasible++; + } for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) if (lp.row_upper_[iRow] < lp.row_lower_[iRow]) num_bound_infeasible++; if (num_bound_infeasible > 0) diff --git a/src/lp_data/HighsLpUtils.h b/src/lp_data/HighsLpUtils.h index 3a1b44da80..953f6961a2 100644 --- a/src/lp_data/HighsLpUtils.h +++ b/src/lp_data/HighsLpUtils.h @@ -51,7 +51,8 @@ HighsStatus assessBounds(const HighsOptions& options, const char* type, const HighsInt ml_ix_os, const HighsIndexCollection& index_collection, vector& lower, vector& upper, - const double infinite_bound); + const double infinite_bound, + const HighsVarType* integrality = nullptr); HighsStatus cleanBounds(const HighsOptions& options, HighsLp& lp); From ba7ded0344e38e9be762495831d2d71b7345bf7b Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 17 Apr 2023 17:31:04 +0100 Subject: [PATCH 3/5] Renamed original members of HighsLpMods and introduced ones for handling inconsistent semi variables --- check/TestSemiVariables.cpp | 2 + src/lp_data/HStruct.h | 13 ++++-- src/lp_data/HighsLp.cpp | 76 ++++++++++++++++++++++++------------ src/lp_data/HighsLpUtils.cpp | 51 ++++++++++++------------ 4 files changed, 87 insertions(+), 55 deletions(-) diff --git a/check/TestSemiVariables.cpp b/check/TestSemiVariables.cpp index dc93f571cd..2bcb7de372 100644 --- a/check/TestSemiVariables.cpp +++ b/check/TestSemiVariables.cpp @@ -234,6 +234,7 @@ TEST_CASE("semi-variable-file", "[highs_test_semi_variables]") { optimal_objective_function_value) < double_equal_tolerance); } +/* TEST_CASE("semi-variable-inconsistent-bounds", "[highs_test_semi_variables]") { HighsLp lp; lp.num_col_ = 1; @@ -264,6 +265,7 @@ TEST_CASE("semi-variable-inconsistent-bounds", "[highs_test_semi_variables]") { highs.run(); REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); } +*/ void semiModel0(HighsLp& lp) { lp.num_col_ = 4; diff --git a/src/lp_data/HStruct.h b/src/lp_data/HStruct.h index 5202058d0e..fdf46db3fd 100644 --- a/src/lp_data/HStruct.h +++ b/src/lp_data/HStruct.h @@ -82,10 +82,15 @@ struct HighsScale { }; struct HighsLpMods { - std::vector save_semi_variable_lower_bound_index; - std::vector save_semi_variable_lower_bound_value; - std::vector save_semi_variable_upper_bound_index; - std::vector save_semi_variable_upper_bound_value; + std::vector save_non_semi_variable_index; + std::vector save_inconsistent_semi_variable_index; + std::vector save_inconsistent_semi_variable_lower_bound_value; + std::vector save_inconsistent_semi_variable_upper_bound_value; + + std::vector save_relaxed_semi_variable_lower_bound_index; + std::vector save_relaxed_semi_variable_lower_bound_value; + std::vector save_tightened_semi_variable_upper_bound_index; + std::vector save_tightened_semi_variable_upper_bound_value; void clear(); bool isClear(); }; diff --git a/src/lp_data/HighsLp.cpp b/src/lp_data/HighsLp.cpp index 88146f07aa..f325e374ee 100644 --- a/src/lp_data/HighsLp.cpp +++ b/src/lp_data/HighsLp.cpp @@ -229,52 +229,76 @@ void HighsLp::moveBackLpAndUnapplyScaling(HighsLp& lp) { } void HighsLp::unapplyMods() { - // Restore any modified lower bounds - std::vector& lower_bound_index = - this->mods_.save_semi_variable_lower_bound_index; - std::vector& lower_bound_value = - this->mods_.save_semi_variable_lower_bound_value; - const HighsInt num_lower_bound = lower_bound_index.size(); + // Restore any non-semi types + const HighsInt num_non_semi = this->mods_.save_non_semi_variable_index.size(); + for (HighsInt k = 0; k < num_non_semi; k++) { + HighsInt iCol = this->mods_.save_non_semi_variable_index[k]; + assert(this->integrality_[iCol] == HighsVarType::kContinuous || + this->integrality_[iCol] == HighsVarType::kInteger); + if (this->integrality_[iCol] == HighsVarType::kContinuous) { + this->integrality_[iCol] = HighsVarType::kSemiContinuous; + } else { + this->integrality_[iCol] = HighsVarType::kSemiInteger; + } + } + // Restore any relaxed lower bounds + std::vector& relaxed_semi_variable_lower_index = + this->mods_.save_relaxed_semi_variable_lower_bound_index; + std::vector& relaxed_semi_variable_lower_value = + this->mods_.save_relaxed_semi_variable_lower_bound_value; + const HighsInt num_lower_bound = relaxed_semi_variable_lower_index.size(); // Ensure that if there are no indices of modified lower bounds, // then there are no modified lower bound values - if (!num_lower_bound) assert(!lower_bound_value.size()); + if (!num_lower_bound) assert(!relaxed_semi_variable_lower_value.size()); for (HighsInt k = 0; k < num_lower_bound; k++) { - HighsInt iCol = lower_bound_index[k]; - this->col_lower_[iCol] = lower_bound_value[k]; + HighsInt iCol = relaxed_semi_variable_lower_index[k]; + assert(this->integrality_[iCol] == HighsVarType::kSemiContinuous || + this->integrality_[iCol] == HighsVarType::kSemiInteger); + this->col_lower_[iCol] = relaxed_semi_variable_lower_value[k]; } - // Restore any modified upper bounds - std::vector& upper_bound_index = - this->mods_.save_semi_variable_upper_bound_index; - std::vector& upper_bound_value = - this->mods_.save_semi_variable_upper_bound_value; - const HighsInt num_upper_bound = upper_bound_index.size(); + // Restore any tighteneed upper bounds + std::vector& tightened_semi_variable_upper_bound_index = + this->mods_.save_tightened_semi_variable_upper_bound_index; + std::vector& tightened_semi_variable_upper_bound_value = + this->mods_.save_tightened_semi_variable_upper_bound_value; + const HighsInt num_upper_bound = tightened_semi_variable_upper_bound_index.size(); // Ensure that if there are no indices of modified upper bounds, // then there are no modified upper bound values - if (!num_upper_bound) assert(!upper_bound_value.size()); + if (!num_upper_bound) assert(!tightened_semi_variable_upper_bound_value.size()); for (HighsInt k = 0; k < num_upper_bound; k++) { - HighsInt iCol = upper_bound_index[k]; - this->col_upper_[iCol] = upper_bound_value[k]; + HighsInt iCol = tightened_semi_variable_upper_bound_index[k]; + assert(this->integrality_[iCol] == HighsVarType::kSemiContinuous || + this->integrality_[iCol] == HighsVarType::kSemiInteger); + this->col_upper_[iCol] = tightened_semi_variable_upper_bound_value[k]; } this->mods_.clear(); } void HighsLpMods::clear() { - this->save_semi_variable_lower_bound_index.clear(); - this->save_semi_variable_lower_bound_value.clear(); - this->save_semi_variable_upper_bound_index.clear(); - this->save_semi_variable_upper_bound_value.clear(); + this->save_non_semi_variable_index.clear(); + this->save_inconsistent_semi_variable_index.clear(); + this->save_inconsistent_semi_variable_lower_bound_value.clear(); + this->save_inconsistent_semi_variable_upper_bound_value.clear(); + this->save_relaxed_semi_variable_lower_bound_index.clear(); + this->save_relaxed_semi_variable_lower_bound_value.clear(); + this->save_tightened_semi_variable_upper_bound_index.clear(); + this->save_tightened_semi_variable_upper_bound_value.clear(); } bool HighsLpMods::isClear() { - if (this->save_semi_variable_lower_bound_index.size()) return false; - if (this->save_semi_variable_lower_bound_value.size()) return false; - if (this->save_semi_variable_upper_bound_index.size()) return false; - if (this->save_semi_variable_upper_bound_value.size()) return false; + if (this->save_non_semi_variable_index.size()) return false; + if (this->save_inconsistent_semi_variable_index.size()) return false; + if (this->save_inconsistent_semi_variable_lower_bound_value.size()) return false; + if (this->save_inconsistent_semi_variable_upper_bound_value.size()) return false; + if (this->save_relaxed_semi_variable_lower_bound_value.size()) return false; + if (this->save_relaxed_semi_variable_lower_bound_value.size()) return false; + if (this->save_tightened_semi_variable_upper_bound_index.size()) return false; + if (this->save_tightened_semi_variable_upper_bound_value.size()) return false; return true; } diff --git a/src/lp_data/HighsLpUtils.cpp b/src/lp_data/HighsLpUtils.cpp index ba553a9fd2..3edc39b249 100644 --- a/src/lp_data/HighsLpUtils.cpp +++ b/src/lp_data/HighsLpUtils.cpp @@ -488,17 +488,18 @@ HighsStatus assessIntegrality(HighsLp& lp, const HighsOptions& options) { HighsInt num_non_semi = 0; HighsInt num_non_continuous_variables = 0; const double kLowerBoundMu = 10.0; - std::vector& upper_bound_index = - lp.mods_.save_semi_variable_upper_bound_index; - std::vector& upper_bound_value = - lp.mods_.save_semi_variable_upper_bound_value; + std::vector& tightened_semi_variable_upper_bound_index = + lp.mods_.save_tightened_semi_variable_upper_bound_index; + std::vector& tightened_semi_variable_upper_bound_value = + lp.mods_.save_tightened_semi_variable_upper_bound_value; for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { if (lp.integrality_[iCol] == HighsVarType::kSemiContinuous || lp.integrality_[iCol] == HighsVarType::kSemiInteger) { - // Semi-variables with zero lower bound aren't semi + // Semi-variables with zero lower bound are not semi if (lp.col_lower_[iCol] == 0) { num_non_semi++; + lp.mods_.save_non_semi_variable_index.push_back(iCol); if (lp.integrality_[iCol] == HighsVarType::kSemiContinuous) { // Semi-continuous become continuous lp.integrality_[iCol] = HighsVarType::kContinuous; @@ -519,9 +520,9 @@ HighsStatus assessIntegrality(HighsLp& lp, const HighsOptions& options) { if (kLowerBoundMu * lp.col_lower_[iCol] > kMaxSemiVariableUpper) { num_illegal_upper++; } else { - // Record the upper bound change - upper_bound_index.push_back(iCol); - upper_bound_value.push_back(kMaxSemiVariableUpper); + // Record the upper bound change to be made later + tightened_semi_variable_upper_bound_index.push_back(iCol); + tightened_semi_variable_upper_bound_value.push_back(kMaxSemiVariableUpper); num_modified_upper++; } } @@ -564,15 +565,15 @@ HighsStatus assessIntegrality(HighsLp& lp, const HighsOptions& options) { if (has_illegal_bounds) { // Don't apply upper bound modifications if there are illegal bounds assert(num_illegal_lower || num_illegal_upper); - upper_bound_index.clear(); - upper_bound_value.clear(); + tightened_semi_variable_upper_bound_index.clear(); + tightened_semi_variable_upper_bound_value.clear(); } else { // Apply the upper bound modifications, saving the over-written // values for (HighsInt k = 0; k < num_modified_upper; k++) { - const double use_upper_bound = upper_bound_value[k]; - const HighsInt iCol = upper_bound_index[k]; - upper_bound_value[k] = lp.col_upper_[iCol]; + const double use_upper_bound = tightened_semi_variable_upper_bound_value[k]; + const HighsInt iCol = tightened_semi_variable_upper_bound_index[k]; + tightened_semi_variable_upper_bound_value[k] = lp.col_upper_[iCol]; lp.col_upper_[iCol] = use_upper_bound; } } @@ -604,16 +605,16 @@ void relaxSemiVariables(HighsLp& lp) { if (!lp.integrality_.size()) return; assert((HighsInt)lp.integrality_.size() == lp.num_col_); HighsInt num_modified_lower = 0; - std::vector& lower_bound_index = - lp.mods_.save_semi_variable_lower_bound_index; - std::vector& lower_bound_value = - lp.mods_.save_semi_variable_lower_bound_value; - assert(lower_bound_index.size() == 0); + std::vector& relaxed_semi_variable_lower_index = + lp.mods_.save_relaxed_semi_variable_lower_bound_index; + std::vector& relaxed_semi_variable_lower_value = + lp.mods_.save_relaxed_semi_variable_lower_bound_value; + assert(relaxed_semi_variable_lower_index.size() == 0); for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { if (lp.integrality_[iCol] == HighsVarType::kSemiContinuous || lp.integrality_[iCol] == HighsVarType::kSemiInteger) { - lower_bound_index.push_back(iCol); - lower_bound_value.push_back(lp.col_lower_[iCol]); + relaxed_semi_variable_lower_index.push_back(iCol); + relaxed_semi_variable_lower_value.push_back(lp.col_lower_[iCol]); lp.col_lower_[iCol] = 0; } } @@ -621,14 +622,14 @@ void relaxSemiVariables(HighsLp& lp) { bool activeModifiedUpperBounds(const HighsOptions& options, const HighsLp& lp, const std::vector col_value) { - const std::vector& upper_bound_index = - lp.mods_.save_semi_variable_upper_bound_index; - const HighsInt num_modified_upper = upper_bound_index.size(); + const std::vector& tightened_semi_variable_upper_bound_index = + lp.mods_.save_tightened_semi_variable_upper_bound_index; + const HighsInt num_modified_upper = tightened_semi_variable_upper_bound_index.size(); HighsInt num_active_modified_upper = 0; double min_semi_variable_margin = kHighsInf; for (HighsInt k = 0; k < num_modified_upper; k++) { - const double value = col_value[upper_bound_index[k]]; - const double upper = lp.col_upper_[upper_bound_index[k]]; + const double value = col_value[tightened_semi_variable_upper_bound_index[k]]; + const double upper = lp.col_upper_[tightened_semi_variable_upper_bound_index[k]]; double semi_variable_margin = upper - value; if (value > upper - options.primal_feasibility_tolerance) { min_semi_variable_margin = 0; From 868198b9bda0166b873ee3d913256708d27e2bf8 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 17 Apr 2023 18:49:30 +0100 Subject: [PATCH 4/5] HiGHS now handles inconsistent bounds on semi-variables --- check/TestSemiVariables.cpp | 2 - src/lp_data/HStruct.h | 1 + src/lp_data/Highs.cpp | 17 +++--- src/lp_data/HighsLp.cpp | 55 ++++++++++++------ src/lp_data/HighsLpUtils.cpp | 109 ++++++++++++++++++++++++++--------- src/lp_data/HighsLpUtils.h | 2 +- 6 files changed, 132 insertions(+), 54 deletions(-) diff --git a/check/TestSemiVariables.cpp b/check/TestSemiVariables.cpp index 2bcb7de372..dc93f571cd 100644 --- a/check/TestSemiVariables.cpp +++ b/check/TestSemiVariables.cpp @@ -234,7 +234,6 @@ TEST_CASE("semi-variable-file", "[highs_test_semi_variables]") { optimal_objective_function_value) < double_equal_tolerance); } -/* TEST_CASE("semi-variable-inconsistent-bounds", "[highs_test_semi_variables]") { HighsLp lp; lp.num_col_ = 1; @@ -265,7 +264,6 @@ TEST_CASE("semi-variable-inconsistent-bounds", "[highs_test_semi_variables]") { highs.run(); REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); } -*/ void semiModel0(HighsLp& lp) { lp.num_col_ = 4; diff --git a/src/lp_data/HStruct.h b/src/lp_data/HStruct.h index fdf46db3fd..341b371246 100644 --- a/src/lp_data/HStruct.h +++ b/src/lp_data/HStruct.h @@ -86,6 +86,7 @@ struct HighsLpMods { std::vector save_inconsistent_semi_variable_index; std::vector save_inconsistent_semi_variable_lower_bound_value; std::vector save_inconsistent_semi_variable_upper_bound_value; + std::vector save_inconsistent_semi_variable_type; std::vector save_relaxed_semi_variable_lower_bound_index; std::vector save_relaxed_semi_variable_lower_bound_value; diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index bd4a91d41e..d1c7775a3c 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -911,14 +911,17 @@ HighsStatus Highs::run() { highsLogDev(options_.log_options, HighsLogType::kVerbose, "Solving model: %s\n", model_.lp_.model_name_.c_str()); - // Check validity of any integrality, keeping a record of any upper - // bound modifications for semi-variables - call_status = assessIntegrality(model_.lp_, options_); - if (call_status == HighsStatus::kError) { - setHighsModelStatusAndClearSolutionAndBasis(HighsModelStatus::kSolveError); - return returnFromRun(HighsStatus::kError); + if (!options_.solve_relaxation) { + // Not solving the relaxation, so check validity of any + // integrality, keeping a record of any bound and type + // modifications for semi-variables + call_status = assessIntegrality(model_.lp_, options_); + if (call_status == HighsStatus::kError) { + setHighsModelStatusAndClearSolutionAndBasis( + HighsModelStatus::kSolveError); + return returnFromRun(HighsStatus::kError); + } } - if (!options_.solver.compare(kHighsChooseString)) { // Leaving HiGHS to choose method according to model class if (model_.isQp()) { diff --git a/src/lp_data/HighsLp.cpp b/src/lp_data/HighsLp.cpp index f325e374ee..d0a267c819 100644 --- a/src/lp_data/HighsLp.cpp +++ b/src/lp_data/HighsLp.cpp @@ -234,46 +234,61 @@ void HighsLp::unapplyMods() { for (HighsInt k = 0; k < num_non_semi; k++) { HighsInt iCol = this->mods_.save_non_semi_variable_index[k]; assert(this->integrality_[iCol] == HighsVarType::kContinuous || - this->integrality_[iCol] == HighsVarType::kInteger); + this->integrality_[iCol] == HighsVarType::kInteger); if (this->integrality_[iCol] == HighsVarType::kContinuous) { this->integrality_[iCol] = HighsVarType::kSemiContinuous; } else { this->integrality_[iCol] = HighsVarType::kSemiInteger; } } + // Restore any inconsistent semi variables + const HighsInt num_inconsistent_semi = + this->mods_.save_inconsistent_semi_variable_index.size(); + if (!num_inconsistent_semi) { + assert( + !this->mods_.save_inconsistent_semi_variable_lower_bound_value.size()); + assert( + !this->mods_.save_inconsistent_semi_variable_upper_bound_value.size()); + assert(!this->mods_.save_inconsistent_semi_variable_type.size()); + } + for (HighsInt k = 0; k < num_inconsistent_semi; k++) { + HighsInt iCol = this->mods_.save_inconsistent_semi_variable_index[k]; + this->col_lower_[iCol] = + this->mods_.save_inconsistent_semi_variable_lower_bound_value[k]; + this->col_upper_[iCol] = + this->mods_.save_inconsistent_semi_variable_upper_bound_value[k]; + this->integrality_[iCol] = + this->mods_.save_inconsistent_semi_variable_type[k]; + } // Restore any relaxed lower bounds std::vector& relaxed_semi_variable_lower_index = this->mods_.save_relaxed_semi_variable_lower_bound_index; std::vector& relaxed_semi_variable_lower_value = this->mods_.save_relaxed_semi_variable_lower_bound_value; const HighsInt num_lower_bound = relaxed_semi_variable_lower_index.size(); - - // Ensure that if there are no indices of modified lower bounds, - // then there are no modified lower bound values - if (!num_lower_bound) assert(!relaxed_semi_variable_lower_value.size()); - + if (!num_lower_bound) { + assert(!relaxed_semi_variable_lower_value.size()); + } for (HighsInt k = 0; k < num_lower_bound; k++) { HighsInt iCol = relaxed_semi_variable_lower_index[k]; assert(this->integrality_[iCol] == HighsVarType::kSemiContinuous || - this->integrality_[iCol] == HighsVarType::kSemiInteger); + this->integrality_[iCol] == HighsVarType::kSemiInteger); this->col_lower_[iCol] = relaxed_semi_variable_lower_value[k]; } - - // Restore any tighteneed upper bounds + // Restore any tightened upper bounds std::vector& tightened_semi_variable_upper_bound_index = this->mods_.save_tightened_semi_variable_upper_bound_index; std::vector& tightened_semi_variable_upper_bound_value = this->mods_.save_tightened_semi_variable_upper_bound_value; - const HighsInt num_upper_bound = tightened_semi_variable_upper_bound_index.size(); - - // Ensure that if there are no indices of modified upper bounds, - // then there are no modified upper bound values - if (!num_upper_bound) assert(!tightened_semi_variable_upper_bound_value.size()); - + const HighsInt num_upper_bound = + tightened_semi_variable_upper_bound_index.size(); + if (!num_upper_bound) { + assert(!tightened_semi_variable_upper_bound_value.size()); + } for (HighsInt k = 0; k < num_upper_bound; k++) { HighsInt iCol = tightened_semi_variable_upper_bound_index[k]; assert(this->integrality_[iCol] == HighsVarType::kSemiContinuous || - this->integrality_[iCol] == HighsVarType::kSemiInteger); + this->integrality_[iCol] == HighsVarType::kSemiInteger); this->col_upper_[iCol] = tightened_semi_variable_upper_bound_value[k]; } @@ -285,6 +300,7 @@ void HighsLpMods::clear() { this->save_inconsistent_semi_variable_index.clear(); this->save_inconsistent_semi_variable_lower_bound_value.clear(); this->save_inconsistent_semi_variable_upper_bound_value.clear(); + this->save_inconsistent_semi_variable_type.clear(); this->save_relaxed_semi_variable_lower_bound_index.clear(); this->save_relaxed_semi_variable_lower_bound_value.clear(); this->save_tightened_semi_variable_upper_bound_index.clear(); @@ -294,8 +310,11 @@ void HighsLpMods::clear() { bool HighsLpMods::isClear() { if (this->save_non_semi_variable_index.size()) return false; if (this->save_inconsistent_semi_variable_index.size()) return false; - if (this->save_inconsistent_semi_variable_lower_bound_value.size()) return false; - if (this->save_inconsistent_semi_variable_upper_bound_value.size()) return false; + if (this->save_inconsistent_semi_variable_lower_bound_value.size()) + return false; + if (this->save_inconsistent_semi_variable_upper_bound_value.size()) + return false; + if (this->save_inconsistent_semi_variable_type.size()) return false; if (this->save_relaxed_semi_variable_lower_bound_value.size()) return false; if (this->save_relaxed_semi_variable_lower_bound_value.size()) return false; if (this->save_tightened_semi_variable_upper_bound_index.size()) return false; diff --git a/src/lp_data/HighsLpUtils.cpp b/src/lp_data/HighsLpUtils.cpp index 3edc39b249..38526a361a 100644 --- a/src/lp_data/HighsLpUtils.cpp +++ b/src/lp_data/HighsLpUtils.cpp @@ -65,7 +65,7 @@ HighsStatus assessLp(HighsLp& lp, const HighsOptions& options) { // Assess the LP column bounds call_status = assessBounds(options, "Col", 0, index_collection, lp.col_lower_, lp.col_upper_, options.infinite_bound, - lp.integrality_.data()); + lp.integrality_.data()); return_status = interpretCallStatus(options.log_options, call_status, return_status, "assessBounds"); if (return_status == HighsStatus::kError) return return_status; @@ -350,7 +350,7 @@ HighsStatus assessBounds(const HighsOptions& options, const char* type, const HighsIndexCollection& index_collection, vector& lower, vector& upper, const double infinite_bound, - const HighsVarType* integrality) { + const HighsVarType* integrality) { HighsStatus return_status = HighsStatus::kOk; assert(ok(index_collection)); HighsInt from_k; @@ -424,8 +424,8 @@ HighsStatus assessBounds(const HighsOptions& options, const char* type, if (integrality) { // Legal for semi-variables to have inconsistent bounds if (integrality[usr_ix] == HighsVarType::kSemiContinuous || - integrality[usr_ix] == HighsVarType::kSemiInteger) - legalLowerUpperBound = true; + integrality[usr_ix] == HighsVarType::kSemiInteger) + legalLowerUpperBound = true; } if (!legalLowerUpperBound) { // Leave inconsistent bounds to be used to deduce infeasibility @@ -488,26 +488,43 @@ HighsStatus assessIntegrality(HighsLp& lp, const HighsOptions& options) { HighsInt num_non_semi = 0; HighsInt num_non_continuous_variables = 0; const double kLowerBoundMu = 10.0; + std::vector& inconsistent_semi_variable_index = + lp.mods_.save_inconsistent_semi_variable_index; + std::vector& inconsistent_semi_variable_lower_bound_value = + lp.mods_.save_inconsistent_semi_variable_lower_bound_value; + std::vector& inconsistent_semi_variable_upper_bound_value = + lp.mods_.save_inconsistent_semi_variable_upper_bound_value; + std::vector& inconsistent_semi_variable_type = + lp.mods_.save_inconsistent_semi_variable_type; + std::vector& tightened_semi_variable_upper_bound_index = lp.mods_.save_tightened_semi_variable_upper_bound_index; std::vector& tightened_semi_variable_upper_bound_value = lp.mods_.save_tightened_semi_variable_upper_bound_value; + assert(int(lp.mods_.save_inconsistent_semi_variable_index.size()) == 0); for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { if (lp.integrality_[iCol] == HighsVarType::kSemiContinuous || lp.integrality_[iCol] == HighsVarType::kSemiInteger) { + if (lp.col_lower_[iCol] > lp.col_upper_[iCol]) { + // Semi-variables with inconsistent bounds become continous + // and fixed at zero + num_inconsistent_semi++; + inconsistent_semi_variable_index.push_back(iCol); + inconsistent_semi_variable_lower_bound_value.push_back( + lp.col_lower_[iCol]); + inconsistent_semi_variable_upper_bound_value.push_back( + lp.col_upper_[iCol]); + inconsistent_semi_variable_type.push_back(lp.integrality_[iCol]); + continue; + } // Semi-variables with zero lower bound are not semi if (lp.col_lower_[iCol] == 0) { num_non_semi++; - lp.mods_.save_non_semi_variable_index.push_back(iCol); - if (lp.integrality_[iCol] == HighsVarType::kSemiContinuous) { - // Semi-continuous become continuous - lp.integrality_[iCol] = HighsVarType::kContinuous; - } else { - // Semi-integer become integer - lp.integrality_[iCol] = HighsVarType::kInteger; + lp.mods_.save_non_semi_variable_index.push_back(iCol); + // Semi-integer become integer so still have a non-continuous variable + if (lp.integrality_[iCol] == HighsVarType::kSemiInteger) num_non_continuous_variables++; - } continue; } if (lp.col_lower_[iCol] < 0) { @@ -522,7 +539,8 @@ HighsStatus assessIntegrality(HighsLp& lp, const HighsOptions& options) { } else { // Record the upper bound change to be made later tightened_semi_variable_upper_bound_index.push_back(iCol); - tightened_semi_variable_upper_bound_value.push_back(kMaxSemiVariableUpper); + tightened_semi_variable_upper_bound_value.push_back( + kMaxSemiVariableUpper); num_modified_upper++; } } @@ -532,11 +550,12 @@ HighsStatus assessIntegrality(HighsLp& lp, const HighsOptions& options) { } } if (num_inconsistent_semi) { - highsLogUser(options.log_options, HighsLogType::kWarning, - "%" HIGHSINT_FORMAT - " semi-continuous/integer variable(s) have inconsistent bounds " - "so are fixed at zero\n", - num_inconsistent_semi); + highsLogUser( + options.log_options, HighsLogType::kWarning, + "%" HIGHSINT_FORMAT + " semi-continuous/integer variable(s) have inconsistent bounds " + "so are fixed at zero\n", + num_inconsistent_semi); return_status = HighsStatus::kWarning; } if (num_non_semi) { @@ -563,21 +582,55 @@ HighsStatus assessIntegrality(HighsLp& lp, const HighsOptions& options) { kMaxSemiVariableUpper, kLowerBoundMu); return_status = HighsStatus::kWarning; if (has_illegal_bounds) { - // Don't apply upper bound modifications if there are illegal bounds + // Don't apply upper bound tightenings if there are illegal bounds assert(num_illegal_lower || num_illegal_upper); tightened_semi_variable_upper_bound_index.clear(); tightened_semi_variable_upper_bound_value.clear(); } else { - // Apply the upper bound modifications, saving the over-written + // Apply the upper bound tightenings, saving the over-written // values for (HighsInt k = 0; k < num_modified_upper; k++) { - const double use_upper_bound = tightened_semi_variable_upper_bound_value[k]; + const double use_upper_bound = + tightened_semi_variable_upper_bound_value[k]; const HighsInt iCol = tightened_semi_variable_upper_bound_index[k]; tightened_semi_variable_upper_bound_value[k] = lp.col_upper_[iCol]; lp.col_upper_[iCol] = use_upper_bound; } } } + if (num_inconsistent_semi) { + if (has_illegal_bounds) { + // Don't apply bound changes if there are illegal bounds + inconsistent_semi_variable_index.clear(); + inconsistent_semi_variable_lower_bound_value.clear(); + inconsistent_semi_variable_upper_bound_value.clear(); + inconsistent_semi_variable_type.clear(); + } else { + for (HighsInt k = 0; k < num_inconsistent_semi; k++) { + const HighsInt iCol = inconsistent_semi_variable_index[k]; + lp.col_lower_[iCol] = 0; + lp.col_upper_[iCol] = 0; + lp.integrality_[iCol] = HighsVarType::kContinuous; + } + } + } + if (num_non_semi) { + if (has_illegal_bounds) { + // Don't apply type changes if there are illegal bounds + lp.mods_.save_non_semi_variable_index.clear(); + } else { + for (HighsInt k = 0; k < num_non_semi; k++) { + const HighsInt iCol = lp.mods_.save_non_semi_variable_index[k]; + if (lp.integrality_[iCol] == HighsVarType::kSemiContinuous) { + // Semi-continuous become continuous + lp.integrality_[iCol] = HighsVarType::kContinuous; + } else { + // Semi-integer become integer + lp.integrality_[iCol] = HighsVarType::kInteger; + } + } + } + } if (num_illegal_lower) { highsLogUser( options.log_options, HighsLogType::kError, @@ -624,12 +677,15 @@ bool activeModifiedUpperBounds(const HighsOptions& options, const HighsLp& lp, const std::vector col_value) { const std::vector& tightened_semi_variable_upper_bound_index = lp.mods_.save_tightened_semi_variable_upper_bound_index; - const HighsInt num_modified_upper = tightened_semi_variable_upper_bound_index.size(); + const HighsInt num_modified_upper = + tightened_semi_variable_upper_bound_index.size(); HighsInt num_active_modified_upper = 0; double min_semi_variable_margin = kHighsInf; for (HighsInt k = 0; k < num_modified_upper; k++) { - const double value = col_value[tightened_semi_variable_upper_bound_index[k]]; - const double upper = lp.col_upper_[tightened_semi_variable_upper_bound_index[k]]; + const double value = + col_value[tightened_semi_variable_upper_bound_index[k]]; + const double upper = + lp.col_upper_[tightened_semi_variable_upper_bound_index[k]]; double semi_variable_margin = upper - value; if (value > upper - options.primal_feasibility_tolerance) { min_semi_variable_margin = 0; @@ -2567,9 +2623,10 @@ bool isBoundInfeasible(const HighsLogOptions& log_options, const HighsLp& lp) { const bool has_integrality = lp.integrality_.size() > 0; for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { if (has_integrality) { - // Semi-variables can have inconsistent bounds + // Semi-variables can have inconsistent bounds if (lp.integrality_[iCol] == HighsVarType::kSemiContinuous || - lp.integrality_[iCol] == HighsVarType::kSemiInteger) continue; + lp.integrality_[iCol] == HighsVarType::kSemiInteger) + continue; } if (lp.col_upper_[iCol] < lp.col_lower_[iCol]) num_bound_infeasible++; } diff --git a/src/lp_data/HighsLpUtils.h b/src/lp_data/HighsLpUtils.h index 953f6961a2..2cd936bd29 100644 --- a/src/lp_data/HighsLpUtils.h +++ b/src/lp_data/HighsLpUtils.h @@ -52,7 +52,7 @@ HighsStatus assessBounds(const HighsOptions& options, const char* type, const HighsIndexCollection& index_collection, vector& lower, vector& upper, const double infinite_bound, - const HighsVarType* integrality = nullptr); + const HighsVarType* integrality = nullptr); HighsStatus cleanBounds(const HighsOptions& options, HighsLp& lp); From 1b0b9f23d0487e9783b3b1246f2e246aa7e9b9f0 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Tue, 18 Apr 2023 09:34:44 +0100 Subject: [PATCH 5/5] Commented out assert-rich unused if structure in ipxSolutionToHighsSolution and added printf of excesive primal values --- src/lp_data/HConst.h | 3 ++ src/lp_data/HighsSolution.cpp | 52 +++++++++++++++++++++-------------- src/simplex/SimplexConst.h | 2 -- 3 files changed, 35 insertions(+), 22 deletions(-) diff --git a/src/lp_data/HConst.h b/src/lp_data/HConst.h index ee5e3b8610..f379bbce47 100644 --- a/src/lp_data/HConst.h +++ b/src/lp_data/HConst.h @@ -249,6 +249,9 @@ const HighsInt kHighsIllegalErrorIndex = -1; // Maximum upper bound on semi-variables const double kMaxSemiVariableUpper = 1e5; +// Limit on primal values being realistic +const double kExcessivePrimalValue = 1e25; + // Tolerance values for highsDoubleToString const double kModelValueToStringTolerance = 1e-15; const double kRangingValueToStringTolerance = 1e-13; diff --git a/src/lp_data/HighsSolution.cpp b/src/lp_data/HighsSolution.cpp index 188fc7d4d6..11812ff14c 100644 --- a/src/lp_data/HighsSolution.cpp +++ b/src/lp_data/HighsSolution.cpp @@ -728,6 +728,7 @@ HighsStatus ipxSolutionToHighsSolution( // of machine precision const double primal_margin = 0; // primal_feasibility_tolerance; const double dual_margin = 0; // dual_feasibility_tolerance; + double max_abs_primal_value = 0; for (HighsInt var = 0; var < lp.num_col_ + lp.num_row_; var++) { if (var == lp.num_col_) { col_primal_truncation_norm = primal_truncation_norm; @@ -780,28 +781,31 @@ HighsStatus ipxSolutionToHighsSolution( } // Continue if no dual infeasibility if (dual_infeasibility <= dual_feasibility_tolerance) continue; + if (residual < dual_infeasibility && !force_dual_feasibility) { - // Residual is less than dual infeasibility, or not forcing - // dual feasibility, so truncate value - if (at_lower) { - assert(10 == 50); - } else if (at_upper) { - assert(11 == 50); - } else { - // Off bound - if (lower <= -kHighsInf) { - if (upper >= kHighsInf) { - // Free shouldn't be possible, as residual would be inf - assert(12 == 50); - } else { - // Upper bounded, so assume dual is negative - if (dual > 0) assert(13 == 50); + /* + // Residual is less than dual infeasibility, or not forcing + // dual feasibility, so truncate value + if (at_lower) { + assert(10 == 50); + } else if (at_upper) { + assert(11 == 50); + } else { + // Off bound + if (lower <= -kHighsInf) { + if (upper >= kHighsInf) { + // Free shouldn't be possible, as residual would be inf + assert(12 == 50); + } else { + // Upper bounded, so assume dual is negative + if (dual > 0) assert(13 == 50); + } + } else if (upper >= kHighsInf) { + // Lower bounded, so assume dual is positive + if (dual < 0) assert(14 == 50); } - } else if (upper >= kHighsInf) { - // Lower bounded, so assume dual is positive - if (dual < 0) assert(14 == 50); } - } + */ num_primal_truncations++; if (dual > 0) { // Put closest to lower @@ -851,6 +855,8 @@ HighsStatus ipxSolutionToHighsSolution( upper, residual, new_value, primal_truncation, dual, new_dual, dual_truncation); */ + max_abs_primal_value = + std::max(std::abs(new_value), max_abs_primal_value); if (is_col) { highs_solution.col_value[col] = new_value; highs_solution.col_dual[col] = new_dual; @@ -909,8 +915,14 @@ HighsStatus ipxSolutionToHighsSolution( "ipxSolutionToHighsSolution: Final norm of dual residual " "values is %10.4g\n", dual_residual_norm); + if (max_abs_primal_value > kExcessivePrimalValue) { + // highsLogUser(options.log_options, HighsLogType::kInfo, + printf( + "ipxSolutionToHighsSolution: " + "Excessive corrected |primal value| is %10.4g\n", + max_abs_primal_value); + } } - assert(ipx_row == ipx_num_row); assert(ipx_slack == ipx_num_col); // Indicate that the primal and dual solution are known diff --git a/src/simplex/SimplexConst.h b/src/simplex/SimplexConst.h index 965f759789..650203d059 100644 --- a/src/simplex/SimplexConst.h +++ b/src/simplex/SimplexConst.h @@ -164,8 +164,6 @@ const double kAcceptDseWeightThreshold = 0.25; const double kMinDualSteepestEdgeWeight = 1e-4; -const double kExcessivePrimalValue = 1e25; - const HighsInt kNoRowSought = -2; const HighsInt kNoRowChosen = -1;