Skip to content

Commit b174cc9

Browse files
authored
Merge pull request #2865 from ERGO-Code/fix-2860
Add logging and time budget for probing in presolve, and increase visibility of presolve rule switch-off
2 parents 8871c61 + 06309ae commit b174cc9

File tree

8 files changed

+200
-63
lines changed

8 files changed

+200
-63
lines changed

FEATURES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,9 @@ HiPO is now capable of solving convex QP problems. Option
2525
solver="qpasm" selects the previous active-set QP solver, while
2626
solver="hipo" or solver="ipm" selects the HiPO solver.
2727

28+
Following PR [#2865](https://github.com/ERGO-Code/HiGHS/pull/2865),
29+
HiGHS performs logging during probing in MIP presolve and checks for
30+
time-out
31+
2832
## Build changes
2933

check/TestPresolve.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -863,3 +863,40 @@ TEST_CASE("dual-bound-tightening", "[highs_test_presolve]") {
863863
highs.passModel(lp);
864864
REQUIRE(highs.presolve() == HighsStatus::kOk);
865865
}
866+
867+
TEST_CASE("presolve-rule-off", "[highs_test_presolve]") {
868+
std::string model = "afiro";
869+
std::string model_file =
870+
std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps";
871+
Highs h;
872+
h.setOptionValue("output_flag", dev_run);
873+
h.readModel(model_file);
874+
h.setOptionValue("log_dev_level", 1);
875+
h.setOptionValue("presolve_rule_logging", true);
876+
HighsInt full_presolve_num_col, full_presolve_num_row;
877+
// Run presolve with and without aggregator
878+
for (HighsInt k = 0; k < 2; k++) {
879+
h.presolve();
880+
if (k == 0) {
881+
full_presolve_num_col = h.getPresolvedLp().num_col_;
882+
full_presolve_num_row = h.getPresolvedLp().num_row_;
883+
if (dev_run)
884+
printf("Presolved %s has num_col = %d; num_row = %d\n", model.c_str(),
885+
int(full_presolve_num_col), int(full_presolve_num_row));
886+
HighsPresolveLog presolve_log = h.getPresolveLog();
887+
REQUIRE(presolve_log.rule[kPresolveRuleAggregator].col_removed > 0);
888+
HighsInt presolve_rule_off =
889+
std::pow(int(2), int(kPresolveRuleAggregator));
890+
h.setOptionValue("presolve_rule_off", presolve_rule_off);
891+
} else if (k > 0) {
892+
HighsInt presolve_num_col = h.getPresolvedLp().num_col_;
893+
HighsInt presolve_num_row = h.getPresolvedLp().num_row_;
894+
REQUIRE(presolve_num_col > full_presolve_num_col);
895+
REQUIRE(presolve_num_row > full_presolve_num_row);
896+
if (dev_run)
897+
printf("Presolved %s has num_col = %d; num_row = %d\n", model.c_str(),
898+
int(presolve_num_col), int(presolve_num_row));
899+
}
900+
}
901+
h.resetGlobalScheduler(true);
902+
}

highs/io/HighsIO.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,19 @@ const std::string highsBoolToString(const bool b, const HighsInt field_width) {
303303
return b ? " true" : "false";
304304
}
305305

306+
const std::string highsTimeToString(const double time) {
307+
return
308+
#ifndef NDEBUG
309+
std::to_string(time);
310+
#else
311+
std::to_string(static_cast<int>(time));
312+
#endif
313+
}
314+
315+
const std::string highsTimeSecondToString(const double time) {
316+
return highsTimeToString(time) + "s";
317+
}
318+
306319
const std::string highsInsertMdEscapes(const std::string& from_string) {
307320
std::string to_string = "";
308321
const char* underscore = "_";

highs/io/HighsIO.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,5 +108,6 @@ const std::string highsBoolToString(const bool b,
108108

109109
const std::string highsInsertMdEscapes(const std::string& from_string);
110110
const std::string highsInsertMdId(const std::string& from_string);
111-
111+
const std::string highsTimeToString(const double time);
112+
const std::string highsTimeSecondToString(const double time);
112113
#endif

highs/presolve/HPresolve.cpp

Lines changed: 94 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1683,12 +1683,74 @@ HPresolve::Result HPresolve::runProbing(HighsPostsolveStack& postsolve_stack) {
16831683
};
16841684
}
16851685

1686+
// Set up logging for probing
1687+
const bool silent = silentLog();
1688+
HighsInt iBin = -1;
1689+
HighsInt iBin_probed = -1;
1690+
HighsInt num_binary = binaries.size();
1691+
double tt = this->timer->read();
1692+
double tt0 = tt;
1693+
double log_tt = tt0;
1694+
HighsInt log_iBin_probed = iBin_probed;
1695+
auto probingLog = [&]() {
1696+
if (silent || options->timeless_log) return;
1697+
// Ensure that enough time has elapsed, and at least on binary
1698+
// has been probed
1699+
const double log_tt_interval = 5.0;
1700+
if (tt > log_tt + log_tt_interval && iBin_probed > log_iBin_probed) {
1701+
// Get the average rate from the start of probing
1702+
assert(iBin_probed > 0);
1703+
double rate0 = (tt - tt0) / double(iBin_probed);
1704+
// Get the average rate since last logging
1705+
HighsInt dl_iBin_probed = iBin_probed - log_iBin_probed;
1706+
assert(dl_iBin_probed > 0);
1707+
double rate1 = (tt - log_tt) / double(dl_iBin_probed);
1708+
// Assess the time for probing based on the greater rate
1709+
double rate = std::max(rate0, rate1);
1710+
std::string rate_str =
1711+
" (rate " + highsTimeToString(1e3 * rate) + "/ms";
1712+
double expected_probing_finish_time =
1713+
tt + rate * (num_binary - iBin_probed);
1714+
std::string expected_probing_finish_time_str =
1715+
" => expected probing finish time " +
1716+
highsTimeSecondToString(expected_probing_finish_time) + ")";
1717+
std::string time_str = highsTimeSecondToString(tt);
1718+
highsLogUser(options->log_options, HighsLogType::kInfo,
1719+
" Considered %d / %d binaries; %d probed %s%s %s\n",
1720+
int(iBin), int(num_binary), int(iBin_probed),
1721+
rate_str.c_str(), expected_probing_finish_time_str.c_str(),
1722+
time_str.c_str());
1723+
log_tt = tt;
1724+
log_iBin_probed = iBin_probed;
1725+
}
1726+
};
1727+
16861728
for (const auto& binvar : binaries) {
1687-
HighsInt i = std::get<3>(binvar);
1729+
// Count the binaries considered
1730+
iBin++;
16881731

1732+
HighsInt i = std::get<3>(binvar);
16891733
if (cliquetable.getSubstitution(i) != nullptr || !domain.isBinary(i))
16901734
continue;
16911735

1736+
// Count the binaries probed
1737+
iBin_probed++;
1738+
1739+
// Check for timeout
1740+
tt = this->timer->read();
1741+
if (tt > options->time_limit) {
1742+
highsLogUser(options->log_options, HighsLogType::kInfo,
1743+
"Time limit reached in probing: "
1744+
"consider not using probing by setting option "
1745+
"presolve_rule_off to 2^%-d = %d\n",
1746+
int(kPresolveRuleProbing),
1747+
int(std::pow(int(2), int(kPresolveRuleProbing))));
1748+
return Result::kStopped;
1749+
}
1750+
1751+
// Possibly log probing
1752+
probingLog();
1753+
16921754
bool tightenLimits = (numProbed - oldNumProbed) >= 2500;
16931755

16941756
// when a large percentage of columns have been deleted, stop this round
@@ -1761,9 +1823,6 @@ HPresolve::Result HPresolve::runProbing(HighsPostsolveStack& postsolve_stack) {
17611823
if (numLiftOpps >= maxNumLiftOpps)
17621824
implications.storeLiftingOpportunity = nullptr;
17631825

1764-
// printf("nprobed: %" HIGHSINT_FORMAT ", numCliques: %" HIGHSINT_FORMAT
1765-
// "\n", nprobed,
1766-
// cliquetable.numCliques());
17671826
if (domain.infeasible()) {
17681827
mipsolver->analysis_.mipTimerStop(kMipClockProbingPresolve);
17691828
return Result::kPrimalInfeasible;
@@ -5468,31 +5527,29 @@ HPresolve::Result HPresolve::presolve(HighsPostsolveStack& postsolve_stack) {
54685527
model->sense_ = ObjSense::kMinimize;
54695528
}
54705529

5530+
const bool silent = silentLog();
5531+
if (options->presolve != kHighsOffString) {
5532+
if (!silent)
5533+
highsLogUser(options->log_options, HighsLogType::kInfo,
5534+
"Presolving model\n");
5535+
}
54715536
// Set up the logic to allow presolve rules, and logging for their
54725537
// effectiveness
54735538
analysis_.setup(this->model, this->options, this->numDeletedRows,
5474-
this->numDeletedCols);
5539+
this->numDeletedCols, silent);
54755540

54765541
if (options->presolve != kHighsOffString) {
54775542
if (mipsolver) mipsolver->mipdata_->cliquetable.setPresolveFlag(true);
5478-
if (!mipsolver || mipsolver->mipdata_->numRestarts == 0)
5479-
highsLogUser(options->log_options, HighsLogType::kInfo,
5480-
"Presolving model\n");
54815543

54825544
auto report = [&]() {
5483-
if (!mipsolver || mipsolver->mipdata_->numRestarts == 0) {
5545+
if (!silent) {
54845546
HighsInt numCol = model->num_col_ - numDeletedCols;
54855547
HighsInt numRow = model->num_row_ - numDeletedRows;
54865548
HighsInt numNonz =
54875549
static_cast<HighsInt>(Avalue.size() - freeslots.size());
54885550
// Only read the run time if it's to be printed
54895551
const double run_time = options->output_flag ? this->timer->read() : 0;
5490-
#ifndef NDEBUG
5491-
std::string time_str = " " + std::to_string(run_time) + "s";
5492-
#else
5493-
std::string time_str =
5494-
" " + std::to_string(static_cast<int>(run_time)) + "s";
5495-
#endif
5552+
std::string time_str = highsTimeSecondToString(run_time);
54965553
if (options->timeless_log) time_str = "";
54975554
highsLogUser(options->log_options, HighsLogType::kInfo,
54985555
"%" HIGHSINT_FORMAT " rows, %" HIGHSINT_FORMAT
@@ -5972,6 +6029,10 @@ HighsCDouble HPresolve::computeDynamism(
59726029
static_cast<HighsCDouble>(minAbsCoef);
59736030
}
59746031

6032+
bool HPresolve::silentLog() const {
6033+
return mipsolver && mipsolver->mipdata_->numRestarts > 0;
6034+
}
6035+
59756036
HighsModelStatus HPresolve::run(HighsPostsolveStack& postsolve_stack) {
59766037
presolve_status_ = HighsPresolveStatus::kNotSet;
59776038
shrinkProblemEnabled = true;
@@ -6171,28 +6232,31 @@ HPresolve::Result HPresolve::removeDependentEquations(
61716232
const double time_limit =
61726233
std::max(1.0, std::min(0.01 * options->time_limit, 1000.0));
61736234
factor.setTimeLimit(time_limit);
6235+
const bool silent = silentLog();
61746236
// Determine rank deficiency of the equations
6175-
highsLogUser(options->log_options, HighsLogType::kInfo,
6176-
"Dependent equations search running on %d equations with time "
6177-
"limit of %.2fs\n",
6178-
static_cast<int>(matrix.num_col_), time_limit);
6237+
if (!silent)
6238+
highsLogUser(options->log_options, HighsLogType::kInfo,
6239+
"Dependent equations search running on %d equations with time "
6240+
"limit of %.2fs\n",
6241+
static_cast<int>(matrix.num_col_), time_limit);
61796242
double time_taken = -this->timer->read();
61806243
HighsInt build_return = factor.build();
61816244
time_taken += this->timer->read();
61826245
if (build_return == kBuildKernelReturnTimeout) {
61836246
// HFactor::build has timed out, so just return
6184-
highsLogUser(options->log_options, HighsLogType::kInfo,
6185-
"Dependent equations search terminated after %.3gs due to "
6186-
"expected time exceeding limit\n",
6187-
time_taken);
6247+
if (!silent)
6248+
highsLogUser(options->log_options, HighsLogType::kInfo,
6249+
"Dependent equations search terminated after %.3gs due to "
6250+
"expected time exceeding limit\n",
6251+
time_taken);
61886252
analysis_.logging_on_ = logging_on;
61896253
if (logging_on)
61906254
analysis_.stopPresolveRuleLog(kPresolveRuleDependentFreeCols);
61916255
return Result::kOk;
61926256
} else {
61936257
double pct_off_timeout =
61946258
1e2 * std::fabs(time_taken - time_limit) / time_limit;
6195-
if (pct_off_timeout < 1.0)
6259+
if (!silent && pct_off_timeout < 1.0)
61966260
highsLogUser(options->log_options, HighsLogType::kWarning,
61976261
"Dependent equations search finished within %.2f%% of limit "
61986262
"of %.2fs: "
@@ -6217,11 +6281,12 @@ HPresolve::Result HPresolve::removeDependentEquations(
62176281
num_fictitious_rows_skipped++;
62186282
}
62196283
}
6220-
highsLogUser(options->log_options, HighsLogType::kInfo,
6221-
"Dependent equations search removed %d rows and %d nonzeros "
6222-
"in %.2fs (limit = %.2fs)\n",
6223-
static_cast<int>(num_removed_row),
6224-
static_cast<int>(num_removed_nz), time_taken, time_limit);
6284+
if (!silent)
6285+
highsLogUser(options->log_options, HighsLogType::kInfo,
6286+
"Dependent equations search removed %d rows and %d nonzeros "
6287+
"in %.2fs (limit = %.2fs)\n",
6288+
static_cast<int>(num_removed_row),
6289+
static_cast<int>(num_removed_nz), time_taken, time_limit);
62256290
if (num_fictitious_rows_skipped)
62266291
highsLogDev(options->log_options, HighsLogType::kInfo,
62276292
", avoiding %d fictitious rows",

highs/presolve/HPresolve.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,8 @@ class HPresolve {
348348
template <typename storageFormat>
349349
HighsCDouble computeDynamism(const HighsMatrixSlice<storageFormat>& vector);
350350

351+
bool silentLog() const;
352+
351353
public:
352354
// for LP presolve
353355
bool okSetInput(HighsLp& model_, const HighsOptions& options_,

0 commit comments

Comments
 (0)