Skip to content

Commit

Permalink
Merge pull request #1261 from ERGO-Code/fix-1258-1259
Browse files Browse the repository at this point in the history
Fix 1258
  • Loading branch information
jajhall committed Apr 18, 2023
2 parents 767478e + 1b0b9f2 commit 2e27ba3
Show file tree
Hide file tree
Showing 10 changed files with 313 additions and 129 deletions.
31 changes: 31 additions & 0 deletions check/TestSemiVariables.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
49 changes: 27 additions & 22 deletions src/io/HMPSIO.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -612,9 +612,8 @@ HighsStatus writeMps(
const vector<HighsVarType>& integrality, const std::string objective_name,
const vector<std::string>& col_names, const vector<std::string>& 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");
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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
Expand All @@ -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);
}
}
Expand Down Expand Up @@ -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;
}
3 changes: 3 additions & 0 deletions src/lp_data/HConst.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
14 changes: 10 additions & 4 deletions src/lp_data/HStruct.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,16 @@ struct HighsScale {
};

struct HighsLpMods {
std::vector<HighsInt> save_semi_variable_lower_bound_index;
std::vector<double> save_semi_variable_lower_bound_value;
std::vector<HighsInt> save_semi_variable_upper_bound_index;
std::vector<double> save_semi_variable_upper_bound_value;
std::vector<HighsInt> save_non_semi_variable_index;
std::vector<HighsInt> save_inconsistent_semi_variable_index;
std::vector<double> save_inconsistent_semi_variable_lower_bound_value;
std::vector<double> save_inconsistent_semi_variable_upper_bound_value;
std::vector<HighsVarType> save_inconsistent_semi_variable_type;

std::vector<HighsInt> save_relaxed_semi_variable_lower_bound_index;
std::vector<double> save_relaxed_semi_variable_lower_bound_value;
std::vector<HighsInt> save_tightened_semi_variable_upper_bound_index;
std::vector<double> save_tightened_semi_variable_upper_bound_value;
void clear();
bool isClear();
};
Expand Down
17 changes: 10 additions & 7 deletions src/lp_data/Highs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
113 changes: 78 additions & 35 deletions src/lp_data/HighsLp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,52 +229,95 @@ void HighsLp::moveBackLpAndUnapplyScaling(HighsLp& lp) {
}

void HighsLp::unapplyMods() {
// Restore any modified lower bounds
std::vector<HighsInt>& lower_bound_index =
this->mods_.save_semi_variable_lower_bound_index;
std::vector<double>& lower_bound_value =
this->mods_.save_semi_variable_lower_bound_value;
const HighsInt num_lower_bound = lower_bound_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());

// 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 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<HighsInt>& relaxed_semi_variable_lower_index =
this->mods_.save_relaxed_semi_variable_lower_bound_index;
std::vector<double>& 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();
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 tightened upper bounds
std::vector<HighsInt>& tightened_semi_variable_upper_bound_index =
this->mods_.save_tightened_semi_variable_upper_bound_index;
std::vector<double>& 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();
if (!num_upper_bound) {
assert(!tightened_semi_variable_upper_bound_value.size());
}

// Restore any modified upper bounds
std::vector<HighsInt>& upper_bound_index =
this->mods_.save_semi_variable_upper_bound_index;
std::vector<double>& upper_bound_value =
this->mods_.save_semi_variable_upper_bound_value;
const HighsInt num_upper_bound = 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());

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_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();
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_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;
if (this->save_tightened_semi_variable_upper_bound_value.size()) return false;
return true;
}
Loading

0 comments on commit 2e27ba3

Please sign in to comment.