Skip to content

Commit f74f972

Browse files
authored
Merge pull request ERGO-Code#2330 from ERGO-Code/fix-2326
Avoided false claim of optimality when presolve is terminated by `presolve_reduction_limit` when reduced model has no columns but zero row activity is infeasible.
2 parents bef7e2c + 0228337 commit f74f972

File tree

5 files changed

+30
-7
lines changed

5 files changed

+30
-7
lines changed

check/TestMipSolver.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ TEST_CASE("MIP-distillation", "[highs_test_mip_solver]") {
2727
highs.resetGlobalScheduler(true);
2828
}
2929

30-
// Fails but the cases work separately in
31-
// MIP-rowless-1 and
30+
// Fails but the cases work separately in
31+
// MIP-rowless-1 and
3232
// MIP-rowless-2 below
3333
// TEST_CASE("MIP-rowless", "[highs_test_mip_solver]") {
3434
// Highs highs;
@@ -844,7 +844,6 @@ void rowlessMIP1(Highs& highs) {
844844
// solve(highs, kHighsOffString, require_model_status, optimal_objective);
845845
}
846846

847-
848847
void rowlessMIP2(Highs& highs) {
849848
HighsLp lp;
850849
HighsModelStatus require_model_status;

highs/lp_data/HConst.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,8 @@ enum PresolveRuleType : int {
262262
kPresolveRuleDependentFreeCols,
263263
kPresolveRuleAggregator,
264264
kPresolveRuleParallelRowsAndCols,
265-
kPresolveRuleMax = kPresolveRuleParallelRowsAndCols,
265+
kPresolveRuleProbing,
266+
kPresolveRuleMax = kPresolveRuleProbing,
266267
kPresolveRuleLastAllowOff = kPresolveRuleMax,
267268
kPresolveRuleCount,
268269
};

highs/lp_data/HighsModelUtils.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1421,6 +1421,8 @@ std::string utilPresolveRuleTypeToString(const HighsInt rule_type) {
14211421
return "Aggregator";
14221422
} else if (rule_type == kPresolveRuleParallelRowsAndCols) {
14231423
return "Parallel rows and columns";
1424+
} else if (rule_type == kPresolveRuleProbing) {
1425+
return "Probing";
14241426
}
14251427
assert(1 == 0);
14261428
return "????";

highs/presolve/HPresolve.cpp

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4365,7 +4365,8 @@ HPresolve::Result HPresolve::presolve(HighsPostsolveStack& postsolve_stack) {
43654365
bool trySparsify =
43664366
mipsolver != nullptr || !options->lp_presolve_requires_basis_postsolve;
43674367
#endif
4368-
bool tryProbing = mipsolver != nullptr;
4368+
bool tryProbing =
4369+
mipsolver != nullptr && analysis_.allow_rule_[kPresolveRuleProbing];
43694370
HighsInt numCliquesBeforeProbing = -1;
43704371
bool domcolAfterProbingCalled = false;
43714372
bool dependentEquationsCalled = mipsolver != nullptr;
@@ -4744,14 +4745,21 @@ HighsModelStatus HPresolve::run(HighsPostsolveStack& postsolve_stack) {
47444745
}
47454746
mipsolver->mipdata_->lower_bound = 0;
47464747
} else {
4747-
assert(model->num_row_ == 0);
4748+
// An LP with no columns must have no rows, unless the reduction
4749+
// limit has been reached
4750+
assert(model->num_row_ == 0 ||
4751+
postsolve_stack.numReductions() >= reductionLimit);
47484752
if (model->num_row_ != 0) {
47494753
presolve_status_ = HighsPresolveStatus::kNotPresolved;
47504754
return HighsModelStatus::kNotset;
47514755
}
47524756
}
47534757
presolve_status_ = HighsPresolveStatus::kReducedToEmpty;
4754-
return HighsModelStatus::kOptimal;
4758+
// Make sure that zero row activity from the column-less model is
4759+
// consistent with the bounds
4760+
return model->num_row_ == 0 || zeroRowActivityFeasible()
4761+
? HighsModelStatus::kOptimal
4762+
: HighsModelStatus::kInfeasible;
47554763
} else if (postsolve_stack.numReductions() > 0) {
47564764
// Reductions performed
47574765
presolve_status_ = HighsPresolveStatus::kReduced;
@@ -6885,6 +6893,17 @@ HPresolve::Result HPresolve::sparsify(HighsPostsolveStack& postsolve_stack) {
68856893
return Result::kOk;
68866894
}
68876895

6896+
bool HPresolve::zeroRowActivityFeasible() const {
6897+
// Check that zero row activity is feasible - called when reduced model
6898+
// has no columns to assess whether the HighsModelStatus returned is
6899+
// kOptimal or kInfeasible (as was required for 2326)
6900+
for (HighsInt iRow = 0; iRow < model->num_row_; iRow++)
6901+
if (model->row_lower_[iRow] > primal_feastol ||
6902+
model->row_upper_[iRow] < -primal_feastol)
6903+
return false;
6904+
return true;
6905+
}
6906+
68886907
HighsInt HPresolve::debugGetCheckCol() const {
68896908
const std::string check_col_name = ""; // c37";
68906909
HighsInt check_col = -1;

highs/presolve/HPresolve.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,8 @@ class HPresolve {
394394

395395
HighsPresolveStatus getPresolveStatus() const { return presolve_status_; }
396396

397+
bool zeroRowActivityFeasible() const;
398+
397399
HighsInt debugGetCheckCol() const;
398400
HighsInt debugGetCheckRow() const;
399401

0 commit comments

Comments
 (0)