Skip to content

Commit 005710c

Browse files
authored
Merge pull request #3167 from ERGO-Code/fix-3040
Introduces presolve "light"
2 parents 93b040e + 8ffae8f commit 005710c

32 files changed

Lines changed: 1851 additions & 442 deletions

check/TestPresolve.cpp

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -909,6 +909,158 @@ TEST_CASE("presolve-issue-2874", "[highs_test_presolve]") {
909909
highs.readModel(model_file);
910910
REQUIRE(highs.presolve() == HighsStatus::kOk);
911911
REQUIRE(highs.getModelPresolveStatus() == HighsPresolveStatus::kInfeasible);
912+
913+
highs.resetGlobalScheduler(true);
914+
}
915+
916+
TEST_CASE("presolve-light", "[highs_test_presolve]") {
917+
std::string model_file =
918+
std::string(HIGHS_DIR) + "/check/instances/afiro.mps";
919+
Highs highs;
920+
highs.setOptionValue("output_flag", dev_run);
921+
highs.readModel(model_file);
922+
HighsInt presolved_num_col;
923+
HighsInt presolved_num_row;
924+
HighsInt presolved_num_nz;
925+
for (HighsInt k = 0; k < 2; k++) {
926+
REQUIRE(highs.presolve() == HighsStatus::kOk);
927+
REQUIRE(highs.getModelPresolveStatus() == HighsPresolveStatus::kReduced);
928+
const HighsLp& presolved_lp = highs.getPresolvedLp();
929+
if (k == 1) {
930+
REQUIRE(presolved_lp.num_col_ > presolved_num_col);
931+
REQUIRE(presolved_lp.num_row_ > presolved_num_row);
932+
REQUIRE(presolved_lp.numNz() > presolved_num_nz);
933+
}
934+
presolved_num_col = presolved_lp.num_col_;
935+
presolved_num_row = presolved_lp.num_row_;
936+
presolved_num_nz = presolved_lp.numNz();
937+
938+
if (dev_run)
939+
printf("%s presolved LP has %d columns; %d rows and %d nonzeros\n",
940+
k == 0 ? "Fully" : "Lightly", int(presolved_num_col),
941+
int(presolved_num_row), int(presolved_num_nz));
942+
highs.setOptionValue("presolve_light", kHighsOnString);
943+
}
944+
945+
highs.resetGlobalScheduler(true);
946+
}
947+
948+
TEST_CASE("presolve-initial-sweep-postsolve", "[highs_test_presolve]") {
949+
Highs highs;
950+
highs.setOptionValue("output_flag", dev_run);
951+
if (dev_run) {
952+
highs.setOptionValue("log_dev_level", 1);
953+
highs.setOptionValue("presolve_rule_logging", true);
954+
}
955+
HighsLp lp;
956+
lp.num_col_ = 3;
957+
lp.num_row_ = 1;
958+
lp.col_cost_ = {0, 1, 2};
959+
lp.col_lower_ = {1, 2, 0};
960+
lp.col_upper_ = {1, kHighsInf, kHighsInf};
961+
lp.row_lower_ = {-kHighsInf};
962+
lp.row_upper_ = {5};
963+
lp.a_matrix_.start_ = {0, 1, 2, 3};
964+
lp.a_matrix_.index_ = {0, 0, 0};
965+
lp.a_matrix_.value_ = {3, 2, -1};
966+
highs.passModel(lp);
967+
// Presolved to empty, so no simplex iterations if postsolve is correct
968+
highs.run();
969+
REQUIRE(highs.getInfo().simplex_iteration_count == 0);
970+
if (dev_run) highs.writeSolution("", 1);
971+
972+
highs.resetGlobalScheduler(true);
973+
}
974+
975+
TEST_CASE("presolve-initial-sweep", "[highs_test_presolve]") {
976+
Highs highs;
977+
highs.setOptionValue("output_flag", dev_run);
978+
if (dev_run) {
979+
highs.setOptionValue("log_dev_level", 1);
980+
highs.setOptionValue("presolve_rule_logging", true);
981+
}
982+
HighsLp lp;
983+
lp.num_col_ = 2;
984+
lp.num_row_ = 1;
985+
lp.col_cost_ = {-1, 1};
986+
lp.col_lower_ = {1, 0};
987+
lp.col_upper_ = {1, kHighsInf};
988+
lp.row_lower_ = {-kHighsInf};
989+
lp.row_upper_ = {5};
990+
lp.a_matrix_.start_ = {0, 1, 1};
991+
lp.a_matrix_.index_ = {0};
992+
lp.a_matrix_.value_ = {3};
993+
HighsStatus pass_model_status = HighsStatus::kOk;
994+
for (HighsInt k = 0; k < 4; k++) {
995+
if (dev_run) printf("\nPass k = %d\n==========\n", int(k));
996+
REQUIRE(highs.passModel(lp) == pass_model_status);
997+
highs.run();
998+
if (k == 0) {
999+
// Remove the empty column
1000+
lp.num_col_ = 1;
1001+
lp.col_cost_ = {-1};
1002+
lp.col_lower_ = {1};
1003+
lp.col_upper_ = {1};
1004+
lp.a_matrix_.start_ = {0, 1};
1005+
} else if (k == 1) {
1006+
REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal);
1007+
lp.col_upper_ = {0};
1008+
pass_model_status = HighsStatus::kWarning;
1009+
// Can infeasible column bounds even reach presolve?
1010+
} else if (k == 2) {
1011+
REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible);
1012+
lp.col_lower_ = {1e+20};
1013+
lp.col_upper_ = {kHighsInf};
1014+
lp.row_upper_ = {kHighsInf};
1015+
pass_model_status = HighsStatus::kError;
1016+
// Why is this model accepted when error returns?
1017+
} else {
1018+
REQUIRE(highs.getModelStatus() == HighsModelStatus::kUnbounded);
1019+
}
1020+
if (dev_run) highs.writeSolution("", 1);
1021+
}
1022+
highs.resetGlobalScheduler(true);
1023+
}
1024+
1025+
TEST_CASE("presolve-initial-sweep-all", "[highs_test_presolve]") {
1026+
Highs highs;
1027+
highs.setOptionValue("output_flag", dev_run);
1028+
highs.setOptionValue("presolve_light", kHighsOnString);
1029+
if (dev_run) {
1030+
highs.setOptionValue("log_dev_level", 1);
1031+
highs.setOptionValue("presolve_rule_logging", true);
1032+
}
1033+
HighsLp lp;
1034+
lp.num_col_ = 7;
1035+
lp.num_row_ = 4;
1036+
lp.col_cost_ = {1, 1, 1, 1, 1, 1, 1};
1037+
lp.col_lower_ = {0, 1, 0, 1, 1, -kHighsInf, 0};
1038+
lp.col_upper_ = {1, 1, kHighsInf, 3, 1, 1, 1};
1039+
lp.row_lower_ = {2, 8, 10, 13};
1040+
lp.row_upper_ = {4, 9, 16, 26};
1041+
lp.a_matrix_.start_ = {0, 1, 5, 6, 6, 10, 12, 13};
1042+
lp.a_matrix_.index_ = {2, 0, 1, 2, 3, 0, 0, 1, 2, 3, 2, 3, 2};
1043+
lp.a_matrix_.value_ = {6, 1, 4, 7, 11, 2, 3, 5, 8, 12, 9, 13, 10};
1044+
// Cols 1 and 4 fixed at 1; col 3 empty (fixed at LB = 1) then
1045+
//
1046+
// Rows 0 and 3 singletons; row 1 empty
1047+
REQUIRE(highs.passModel(lp) == HighsStatus::kOk);
1048+
1049+
highs.run();
1050+
REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal);
1051+
if (dev_run) highs.writeSolution("", 1);
1052+
1053+
// Add a redundant row
1054+
std::vector<HighsInt> index = {0, 5, 6};
1055+
std::vector<double> value = {1, 1, 1};
1056+
highs.addRow(-kHighsInf, 4, 3, index.data(), value.data());
1057+
1058+
highs.setOptionValue("use_warm_start", false);
1059+
highs.run();
1060+
REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal);
1061+
if (dev_run) highs.writeSolution("", 1);
1062+
1063+
highs.resetGlobalScheduler(true);
9121064
}
9131065

9141066
TEST_CASE("bound_implied", "[highs_test_presolve]") {

cmake/sources.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,7 @@ set(highs_headers
570570
presolve/ICrashUtil.h
571571
presolve/ICrashX.h
572572
presolve/PresolveComponent.h
573+
presolve/PresolveTimer.h
573574
qpsolver/a_asm.hpp
574575
qpsolver/a_quass.hpp
575576
qpsolver/basis.hpp

highs/Highs.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -781,7 +781,7 @@ class Highs {
781781
* @brief Get the number of (constraint matrix) nonzeros in the incumbent
782782
* model
783783
*/
784-
HighsInt getNumNz() const { return model_.lp_.a_matrix_.numNz(); }
784+
HighsInt getNumNz() const { return model_.lp_.numNz(); }
785785

786786
/**
787787
* @brief Get the number of Hessian matrix nonzeros in the incumbent model

highs/interfaces/highs_c_api.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1189,7 +1189,7 @@ HighsInt Highs_getPresolvedNumRow(const void* highs) {
11891189
}
11901190

11911191
HighsInt Highs_getPresolvedNumNz(const void* highs) {
1192-
return ((Highs*)highs)->getPresolvedLp().a_matrix_.numNz();
1192+
return ((Highs*)highs)->getPresolvedLp().numNz();
11931193
}
11941194

11951195
// Gets pointers to all the public data members of HighsLp: avoids
@@ -1236,7 +1236,7 @@ static HighsInt Highs_getHighsLpData(const HighsLp& lp, const HighsInt a_format,
12361236
(desired_a_format == MatrixFormat::kRowwise &&
12371237
lp.a_matrix_.isRowwise())) {
12381238
// Incumbent format is OK
1239-
*num_nz = lp.a_matrix_.numNz();
1239+
*num_nz = lp.numNz();
12401240
if (a_start)
12411241
memcpy(a_start, lp.a_matrix_.start_.data(),
12421242
num_start_entries * sizeof(HighsInt));

highs/io/FilereaderLp.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,8 +251,8 @@ FilereaderRetcode FilereaderLp::readModelFromFile(const HighsOptions& options,
251251
"coefficient%s in row %d (name \"%s\")\n",
252252
int(iCol), lp.col_names_[iCol].c_str(),
253253
int(zero_count[iRow]),
254-
zero_count[iRow] > 1 ? "s" : "", int(iRow),
255-
lp.row_names_[iRow].c_str());
254+
highsIntToPlural(zero_count[iRow]).c_str(),
255+
int(iRow), lp.row_names_[iRow].c_str());
256256
num_report++;
257257
}
258258
sum_num_duplicate += (num_ocurrence - 1);

highs/io/HighsIO.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,11 @@ const std::string highsBoolToString(const bool b, const HighsInt field_width) {
310310
return b ? " true" : "false";
311311
}
312312

313+
const std::string highsIntToPlural(const HighsInt i, const bool y) {
314+
if (y) return i == 1 ? "y" : "ies";
315+
return i == 1 ? "" : "s";
316+
}
317+
313318
const std::string highsTimeToString(const double time) {
314319
return
315320
#ifndef NDEBUG

highs/io/HighsIO.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ std::string highsFormatToString(const char* format, ...);
111111
const std::string highsBoolToString(const bool b,
112112
const HighsInt field_width = 2);
113113

114+
const std::string highsIntToPlural(const HighsInt i, const bool y = false);
114115
const std::string highsInsertMdEscapes(const std::string& from_string);
115116
const std::string highsInsertMdId(const std::string& from_string);
116117
const std::string highsTimeToString(const double time);

highs/lp_data/Highs.cpp

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1587,7 +1587,7 @@ HighsStatus Highs::calledOptimizeModel() {
15871587
time += timer_.read(timer_.solve_clock);
15881588
};
15891589

1590-
const bool unconstrained_lp = incumbent_lp.a_matrix_.numNz() == 0;
1590+
const bool unconstrained_lp = incumbent_lp.numNz() == 0;
15911591
assert(incumbent_lp.num_row_ || unconstrained_lp);
15921592
const bool has_basis = basis_.useful;
15931593
if (has_basis) {
@@ -3968,8 +3968,8 @@ HighsPresolveStatus Highs::runPresolve(const bool force_lp_presolve,
39683968
original_lp.num_col_ - reduced_lp.num_col_;
39693969
presolve_.info_.n_rows_removed =
39703970
original_lp.num_row_ - reduced_lp.num_row_;
3971-
presolve_.info_.n_nnz_removed = (HighsInt)original_lp.a_matrix_.numNz() -
3972-
(HighsInt)reduced_lp.a_matrix_.numNz();
3971+
presolve_.info_.n_nnz_removed =
3972+
(HighsInt)original_lp.numNz() - (HighsInt)reduced_lp.numNz();
39733973
// Clear any scaling information inherited by the reduced LP
39743974
reduced_lp.clearScale();
39753975
assert(lpDimensionsOk("RunPresolve: reduced_lp", reduced_lp,
@@ -3979,7 +3979,7 @@ HighsPresolveStatus Highs::runPresolve(const bool force_lp_presolve,
39793979
case HighsPresolveStatus::kReducedToEmpty: {
39803980
presolve_.info_.n_cols_removed = original_lp.num_col_;
39813981
presolve_.info_.n_rows_removed = original_lp.num_row_;
3982-
presolve_.info_.n_nnz_removed = (HighsInt)original_lp.a_matrix_.numNz();
3982+
presolve_.info_.n_nnz_removed = (HighsInt)original_lp.numNz();
39833983
break;
39843984
}
39853985
default:
@@ -4000,9 +4000,10 @@ HighsPostsolveStatus Highs::runPostsolve() {
40004000
return HighsPostsolveStatus::kNoPrimalSolutionError;
40014001
const bool have_dual_solution =
40024002
presolve_.data_.recovered_solution_.dual_valid;
4003-
presolve_.data_.postSolveStack.undo(options_,
4004-
presolve_.data_.recovered_solution_,
4005-
presolve_.data_.recovered_basis_);
4003+
const HighsInt report_3040_col = -21792;
4004+
presolve_.data_.postSolveStack.undo(
4005+
options_, presolve_.data_.recovered_solution_,
4006+
presolve_.data_.recovered_basis_, report_3040_col);
40064007
// Compute the row activities
40074008
assert(model_.lp_.a_matrix_.isColwise());
40084009
calculateRowValuesQuad(model_.lp_, presolve_.data_.recovered_solution_);

highs/lp_data/HighsInterface.cpp

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -66,23 +66,20 @@ void Highs::reportModelStats() const {
6666
const HighsInt a_num_nz = lp.a_matrix_.numNz();
6767
const HighsInt q_num_nz = hessian.dim_ > 0 ? hessian.numNz() : 0;
6868
if (*log_options.log_dev_level) {
69-
highsLogDev(log_options, HighsLogType::kInfo, "%4s : %s\n",
69+
highsLogDev(log_options, HighsLogType::kInfo, "%-4s : %s\n",
7070
problem_type.c_str(), lp.model_name_.c_str());
7171
highsLogDev(log_options, HighsLogType::kInfo,
72-
"Row%s : %" HIGHSINT_FORMAT "\n",
73-
lp.num_row_ == 1 ? "" : "s", lp.num_row_);
72+
"Rows : %" HIGHSINT_FORMAT "\n", lp.num_row_);
7473
highsLogDev(log_options, HighsLogType::kInfo,
75-
"Col%s : %" HIGHSINT_FORMAT "\n",
76-
lp.num_col_ == 1 ? "" : "s", lp.num_col_);
74+
"Cols : %" HIGHSINT_FORMAT "\n", lp.num_col_);
7775
if (q_num_nz) {
7876
highsLogDev(log_options, HighsLogType::kInfo,
7977
"Matrix Nz : %" HIGHSINT_FORMAT "\n", a_num_nz);
8078
highsLogDev(log_options, HighsLogType::kInfo,
8179
"Hessian Nz: %" HIGHSINT_FORMAT "\n", q_num_nz);
8280
} else {
8381
highsLogDev(log_options, HighsLogType::kInfo,
84-
"Nonzero%s : %" HIGHSINT_FORMAT "\n",
85-
a_num_nz == 1 ? "" : "s", a_num_nz);
82+
"Nonzeros : %" HIGHSINT_FORMAT "\n", a_num_nz);
8683
}
8784
if (num_integer)
8885
highsLogDev(log_options, HighsLogType::kInfo,
@@ -100,26 +97,28 @@ void Highs::reportModelStats() const {
10097
stats_line << problem_type;
10198
if (lp.model_name_.length()) stats_line << " " << lp.model_name_;
10299
stats_line << " has " << lp.num_row_ << " row"
103-
<< (lp.num_row_ == 1 ? "" : "s") << "; " << lp.num_col_ << " col"
104-
<< (lp.num_col_ == 1 ? "" : "s");
100+
<< highsIntToPlural(lp.num_row_) << "; " << lp.num_col_ << " col"
101+
<< highsIntToPlural(lp.num_col_);
105102
if (q_num_nz) {
106103
stats_line << "; " << a_num_nz << " matrix nonzero"
107-
<< (a_num_nz == 1 ? "" : "s");
104+
<< highsIntToPlural(a_num_nz);
108105
stats_line << "; " << q_num_nz << " Hessian nonzero"
109-
<< (q_num_nz == 1 ? "" : "s");
106+
<< highsIntToPlural(q_num_nz);
110107
} else {
111108
stats_line << "; " << a_num_nz << " nonzero"
112-
<< (a_num_nz == 1 ? "" : "s");
109+
<< highsIntToPlural(a_num_nz);
113110
}
114111
if (hessian.isOracle()) stats_line << "; Hessian as oracle";
115112
if (num_integer)
116113
stats_line << "; " << num_integer << " integer variable"
117-
<< (a_num_nz == 1 ? "" : "s") << " (" << num_binary
114+
<< highsIntToPlural(num_integer) << " (" << num_binary
118115
<< " binary)";
119116
if (num_semi_continuous)
120-
stats_line << "; " << num_semi_continuous << " semi-continuous variables";
117+
stats_line << "; " << num_semi_continuous << " semi-continuous variable"
118+
<< highsIntToPlural(num_semi_continuous);
121119
if (num_semi_integer)
122-
stats_line << "; " << num_semi_integer << " semi-integer variables";
120+
stats_line << "; " << num_semi_integer << " semi-integer variable"
121+
<< highsIntToPlural(num_semi_integer);
123122
highsLogUser(log_options, HighsLogType::kInfo, "%s\n",
124123
stats_line.str().c_str());
125124
}

highs/lp_data/HighsLp.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ class HighsLp {
6464
bool equalNames(const HighsLp& lp) const;
6565
bool equalScaling(const HighsLp& lp) const;
6666
bool isMip() const;
67+
HighsInt numNz() const { return this->a_matrix_.numNz(); }
6768
bool hasSemiVariables() const;
6869
bool hasInfiniteCost(const double infinite_cost) const;
6970
bool hasMods() const;

0 commit comments

Comments
 (0)