Skip to content

Commit ab8f7f7

Browse files
authored
Merge pull request #2438 from fwesselm/fix-2432
Fix 2432
2 parents 80288cb + 6fdea87 commit ab8f7f7

File tree

3 files changed

+68
-40
lines changed

3 files changed

+68
-40
lines changed

check/TestMipSolver.cpp

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -932,8 +932,6 @@ TEST_CASE("issue-2290", "[highs_test_mip_solver]") {
932932

933933
TEST_CASE("issue-2409", "[highs_test_mip_solver]") {
934934
HighsLp lp;
935-
HighsModelStatus require_model_status;
936-
double optimal_objective;
937935
lp.num_col_ = 2;
938936
lp.num_row_ = 2;
939937
lp.col_cost_ = {-1, 1};
@@ -945,8 +943,8 @@ TEST_CASE("issue-2409", "[highs_test_mip_solver]") {
945943
lp.a_matrix_.index_ = {0, 1, 0, 1};
946944
lp.a_matrix_.value_ = {-1, 1, 1, 1};
947945
lp.integrality_ = {HighsVarType::kContinuous, HighsVarType::kInteger};
948-
require_model_status = HighsModelStatus::kOptimal;
949-
optimal_objective = 0.1;
946+
const HighsModelStatus require_model_status = HighsModelStatus::kOptimal;
947+
const double optimal_objective = 0.1;
950948
Highs highs;
951949
REQUIRE(highs.passModel(lp) == HighsStatus::kOk);
952950
if (dev_run) printf("Testing that presolve reduces the problem to empty\n");
@@ -956,12 +954,48 @@ TEST_CASE("issue-2409", "[highs_test_mip_solver]") {
956954

957955
if (dev_run)
958956
printf(
959-
"\nTesting that with presolve the correct optimal objecive is found\n");
957+
"\nTesting that with presolve the correct optimal objective is "
958+
"found\n");
959+
solve(highs, kHighsOnString, require_model_status, optimal_objective);
960+
highs.clearSolver();
961+
if (dev_run)
962+
printf(
963+
"\nTesting that without presolve the correct optimal objective is "
964+
"found\n");
965+
solve(highs, kHighsOffString, require_model_status, optimal_objective);
966+
}
967+
968+
TEST_CASE("issue-2432", "[highs_test_mip_solver]") {
969+
HighsLp lp;
970+
lp.num_col_ = 3;
971+
lp.num_row_ = 3;
972+
lp.col_cost_ = {-93, 25, 17};
973+
lp.col_lower_ = {-100, -100, -100};
974+
lp.col_upper_ = {120, 10, 0};
975+
lp.row_lower_ = {3994.5, -4878.3, -4930};
976+
lp.row_upper_ = {kHighsInf, kHighsInf, kHighsInf};
977+
lp.a_matrix_.start_ = {0, 3, 6, 9};
978+
lp.a_matrix_.index_ = {0, 1, 2, 0, 1, 2, 0, 1, 2};
979+
lp.a_matrix_.value_ = {-89, -0.1, -8.6, -40.7, 77.2, -6.5, -12, -23.7, 72.78};
980+
lp.integrality_ = {HighsVarType::kInteger, HighsVarType::kContinuous,
981+
HighsVarType::kInteger};
982+
const HighsModelStatus require_model_status = HighsModelStatus::kOptimal;
983+
const double optimal_objective = -3777.57124352;
984+
Highs highs;
985+
REQUIRE(highs.passModel(lp) == HighsStatus::kOk);
986+
if (dev_run) printf("Testing that presolve reduces the problem\n");
987+
REQUIRE(highs.presolve() == HighsStatus::kOk);
988+
REQUIRE(highs.getModelPresolveStatus() == HighsPresolveStatus::kReduced);
989+
990+
if (dev_run)
991+
printf(
992+
"\nTesting that with presolve the correct optimal objective is "
993+
"found\n");
960994
solve(highs, kHighsOnString, require_model_status, optimal_objective);
961995
highs.clearSolver();
962996
if (dev_run)
963997
printf(
964-
"\nTesting that without presolve the correct optimal objecive is "
998+
"\nTesting that without presolve the correct optimal objective is "
965999
"found\n");
9661000
solve(highs, kHighsOffString, require_model_status, optimal_objective);
9671001
}

highs/presolve/HPresolve.cpp

Lines changed: 26 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ bool HPresolve::isImpliedFree(HighsInt col) const {
194194
}
195195

196196
bool HPresolve::isDualImpliedFree(HighsInt row) const {
197-
return model->row_lower_[row] == model->row_upper_[row] ||
197+
return isEquation(row) ||
198198
(model->row_upper_[row] != kHighsInf &&
199199
implRowDualUpper[row] <= options->dual_feasibility_tolerance) ||
200200
(model->row_lower_[row] != -kHighsInf &&
@@ -205,7 +205,7 @@ void HPresolve::dualImpliedFreeGetRhsAndRowType(
205205
HighsInt row, double& rhs, HighsPostsolveStack::RowType& rowType,
206206
bool relaxRowDualBounds) {
207207
assert(isDualImpliedFree(row));
208-
if (model->row_lower_[row] == model->row_upper_[row]) {
208+
if (isEquation(row)) {
209209
rowType = HighsPostsolveStack::RowType::kEq;
210210
rhs = model->row_upper_[row];
211211
} else if (model->row_upper_[row] != kHighsInf &&
@@ -220,6 +220,10 @@ void HPresolve::dualImpliedFreeGetRhsAndRowType(
220220
}
221221
}
222222

223+
bool HPresolve::isEquation(HighsInt row) const {
224+
return (model->row_lower_[row] == model->row_upper_[row]);
225+
}
226+
223227
bool HPresolve::isImpliedEquationAtLower(HighsInt row) const {
224228
// if the implied lower bound on a row dual is strictly positive then the row
225229
// is an implied equation (using its lower bound) due to complementary
@@ -267,9 +271,10 @@ HPresolve::StatusResult HPresolve::isImpliedIntegral(HighsInt col) {
267271
// if there is an equation the dual detection does not need to be tried
268272
runDualDetection = false;
269273
double scale = 1.0 / nz.value();
274+
270275
if (!rowCoefficientsIntegral(nz.index(), scale)) continue;
271276

272-
if (fractionality(model->row_lower_[nz.index()] * scale) > primal_feastol)
277+
if (fractionality(rowLower * scale) > primal_feastol)
273278
return StatusResult(Result::kPrimalInfeasible);
274279

275280
return StatusResult(true);
@@ -344,8 +349,7 @@ HPresolve::StatusResult HPresolve::isImpliedInteger(HighsInt col) const {
344349
runDualDetection = false;
345350
double scale = 1.0 / nz.value();
346351

347-
if (fractionality(model->row_lower_[nz.index()] * scale) > primal_feastol)
348-
return StatusResult(Result::kPrimalInfeasible);
352+
if (fractionality(rowLower * scale) > primal_feastol) continue;
349353

350354
if (!rowCoefficientsIntegral(nz.index(), scale)) continue;
351355

@@ -917,8 +921,7 @@ void HPresolve::shrinkProblem(HighsPostsolveStack& postsolve_stack) {
917921
equations.clear();
918922
eqiters.assign(model->num_row_, equations.end());
919923
for (HighsInt i = 0; i != model->num_row_; ++i) {
920-
if (model->row_lower_[i] == model->row_upper_[i])
921-
eqiters[i] = equations.emplace(rowsize[i], i).first;
924+
if (isEquation(i)) eqiters[i] = equations.emplace(rowsize[i], i).first;
922925
}
923926

924927
if (mipsolver != nullptr) {
@@ -1968,8 +1971,7 @@ void HPresolve::markRowDeleted(HighsInt row) {
19681971
assert(!rowDeleted[row]);
19691972

19701973
// remove equations from set of equations
1971-
if (model->row_lower_[row] == model->row_upper_[row] &&
1972-
eqiters[row] != equations.end()) {
1974+
if (isEquation(row) && eqiters[row] != equations.end()) {
19731975
equations.erase(eqiters[row]);
19741976
eqiters[row] = equations.end();
19751977
}
@@ -2396,8 +2398,7 @@ bool HPresolve::okFromCSC(const std::vector<double>& Aval,
23962398
}
23972399
for (HighsInt i = 0; i != model->num_row_; ++i) {
23982400
// register equation
2399-
if (model->row_lower_[i] == model->row_upper_[i])
2400-
eqiters[i] = equations.emplace(rowsize[i], i).first;
2401+
if (isEquation(i)) eqiters[i] = equations.emplace(rowsize[i], i).first;
24012402
}
24022403
}
24032404
return true;
@@ -2460,8 +2461,7 @@ bool HPresolve::okFromCSR(const std::vector<double>& ARval,
24602461
}
24612462
for (HighsInt i = 0; i != nrow; ++i) {
24622463
// register equation
2463-
if (model->row_lower_[i] == model->row_upper_[i])
2464-
eqiters[i] = equations.emplace(rowsize[i], i).first;
2464+
if (isEquation(i)) eqiters[i] = equations.emplace(rowsize[i], i).first;
24652465
}
24662466
}
24672467
return true;
@@ -2538,8 +2538,8 @@ bool HPresolve::checkFillin(HighsHashTable<HighsInt, HighsInt>& fillinCache,
25382538

25392539
void HPresolve::reinsertEquation(HighsInt row) {
25402540
// check if this is an equation row and it now has a different size
2541-
if (model->row_lower_[row] == model->row_upper_[row] &&
2542-
eqiters[row] != equations.end() && eqiters[row]->first != rowsize[row]) {
2541+
if (isEquation(row) && eqiters[row] != equations.end() &&
2542+
eqiters[row]->first != rowsize[row]) {
25432543
// if that is the case reinsert it into the equation set that is ordered
25442544
// by sparsity
25452545
equations.erase(eqiters[row]);
@@ -2826,7 +2826,7 @@ HPresolve::Result HPresolve::doubletonEq(HighsPostsolveStack& postsolve_stack,
28262826
analysis_.startPresolveRuleLog(kPresolveRuleDoubletonEquation);
28272827
assert(!rowDeleted[row]);
28282828
assert(rowsize[row] == 2);
2829-
assert(model->row_lower_[row] == model->row_upper_[row]);
2829+
assert(isEquation(row));
28302830

28312831
// printf("doubleton equation: ");
28322832
// debugPrintRow(row);
@@ -3276,7 +3276,7 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack,
32763276
double origRowUpper = model->row_upper_[row];
32773277
double origRowLower = model->row_lower_[row];
32783278

3279-
if (model->row_lower_[row] != model->row_upper_[row]) {
3279+
if (!isEquation(row)) {
32803280
if (isImpliedEquationAtLower(row)) {
32813281
// Convert to equality constraint (note that currently postsolve will not
32823282
// know about this conversion)
@@ -4553,7 +4553,7 @@ HPresolve::Result HPresolve::removeSlacks(
45534553
HighsInt iRow = Arow[coliter];
45544554
assert(Acol[coliter] == iCol);
45554555
assert(!rowDeleted[iRow]);
4556-
if (model->row_lower_[iRow] != model->row_upper_[iRow]) continue;
4556+
if (!isEquation(iRow)) continue;
45574557
double lower = model->col_lower_[iCol];
45584558
double upper = model->col_upper_[iCol];
45594559
double cost = model->col_cost_[iCol];
@@ -5405,7 +5405,7 @@ HPresolve::Result HPresolve::removeDoubletonEquations(
54055405
HighsInt eqrow = eq->second;
54065406
assert(!rowDeleted[eqrow]);
54075407
assert(eq->first == rowsize[eqrow]);
5408-
assert(model->row_lower_[eqrow] == model->row_upper_[eqrow]);
5408+
assert(isEquation(eqrow));
54095409
if (rowsize[eqrow] > 2) return Result::kOk;
54105410
HPRESOLVE_CHECKED_CALL(rowPresolve(postsolve_stack, eqrow));
54115411
if (rowDeleted[eqrow])
@@ -6166,8 +6166,7 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols(
61666166

61676167
for (HighsInt i = 0; i != model->num_row_; ++i) {
61686168
if (rowDeleted[i]) continue;
6169-
if (rowsize[i] <= 1 ||
6170-
(rowsize[i] == 2 && model->row_lower_[i] == model->row_upper_[i])) {
6169+
if (rowsize[i] <= 1 || (rowsize[i] == 2 && isEquation(i))) {
61716170
HPRESOLVE_CHECKED_CALL(rowPresolve(postsolve_stack, i));
61726171
continue;
61736172
}
@@ -6210,22 +6209,16 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols(
62106209
// equation. In that case we sparsify the other row by adding the
62116210
// equation and can subsequently solve it as an individual component as
62126211
// it is a row which only contains singletons
6213-
if ((numSingleton != 0 ||
6214-
model->row_lower_[i] != model->row_upper_[i]) &&
6215-
(numSingletonCandidate != 0 ||
6216-
model->row_lower_[parallelRowCand] !=
6217-
model->row_upper_[parallelRowCand]))
6212+
if ((numSingleton != 0 || !isEquation(i)) &&
6213+
(numSingletonCandidate != 0 || !isEquation(parallelRowCand)))
62186214
continue;
62196215
} else if (numSingletonCandidate != numSingleton) {
62206216
// if only one of the two constraints has an extra singleton,
62216217
// we require at least one of the constraints to be an equation
62226218
// if that is the case we can add that equation to the other row
62236219
// and will make it into either a row singleton or a doubleton equation
62246220
// which is removed afterwards
6225-
if (model->row_lower_[i] != model->row_upper_[i] &&
6226-
model->row_lower_[parallelRowCand] !=
6227-
model->row_upper_[parallelRowCand])
6228-
continue;
6221+
if (!isEquation(i) && !isEquation(parallelRowCand)) continue;
62296222
}
62306223

62316224
double rowScale = rowMax[parallelRowCand].first / rowMax[i].first;
@@ -6325,7 +6318,7 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols(
63256318
markRowDeleted(i);
63266319
for (HighsInt rowiter : rowpositions) unlink(rowiter);
63276320
break;
6328-
} else if (model->row_lower_[i] == model->row_upper_[i]) {
6321+
} else if (isEquation(i)) {
63296322
// row i is equation and parallel (except for singletons)
63306323
// add to the row parallelRowCand
63316324
// printf(
@@ -6337,8 +6330,7 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols(
63376330
HPRESOLVE_CHECKED_CALL(equalityRowAddition(
63386331
postsolve_stack, i, parallelRowCand, -rowScale, getStoredRow()));
63396332
delRow = parallelRowCand;
6340-
} else if (model->row_lower_[parallelRowCand] ==
6341-
model->row_upper_[parallelRowCand]) {
6333+
} else if (isEquation(parallelRowCand)) {
63426334
// printf(
63436335
// "nearly parallel case with %" HIGHSINT_FORMAT " singletons in eq
63446336
// row and %" HIGHSINT_FORMAT " " "singletons in other inequality
@@ -6644,7 +6636,7 @@ HPresolve::Result HPresolve::sparsify(HighsPostsolveStack& postsolve_stack) {
66446636
if (rowDeleted[eqrow]) continue;
66456637

66466638
assert(!rowDeleted[eqrow]);
6647-
assert(model->row_lower_[eqrow] == model->row_upper_[eqrow]);
6639+
assert(isEquation(eqrow));
66486640

66496641
storeRow(eqrow);
66506642

highs/presolve/HPresolve.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,8 @@ class HPresolve {
196196
HighsPostsolveStack::RowType& rowType,
197197
bool relaxRowDualBounds = false);
198198

199+
bool isEquation(HighsInt row) const;
200+
199201
bool isImpliedEquationAtLower(HighsInt row) const;
200202

201203
bool isImpliedEquationAtUpper(HighsInt row) const;

0 commit comments

Comments
 (0)