Skip to content

Commit b8bd15f

Browse files
committed
Merge branch 'latest' of https://github.com/fwesselm/HiGHS into aggregatedImplications
2 parents da48708 + 6293630 commit b8bd15f

8 files changed

Lines changed: 260 additions & 119 deletions

File tree

‎check/TestPresolve.cpp‎

Lines changed: 101 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1276,14 +1276,100 @@ TEST_CASE("test-non-stop-initial-sweep", "[highs_test_presolve]") {
12761276
h.resetGlobalScheduler(true);
12771277
}
12781278

1279+
TEST_CASE("test-duplicate-row", "[highs_test_presolve]") {
1280+
HighsLp lp;
1281+
lp.num_col_ = 3;
1282+
lp.num_row_ = 2;
1283+
lp.col_cost_ = {2, -1, 1};
1284+
lp.col_lower_ = {0, 0, 0};
1285+
lp.col_upper_ = {kHighsInf, 2, kHighsInf};
1286+
lp.a_matrix_.start_ = {0, 2, 4, 6};
1287+
lp.a_matrix_.index_ = {0, 1, 0, 1, 0, 1};
1288+
Highs h;
1289+
h.setOptionValue("output_flag", dev_run);
1290+
h.setOptionValue("presolve_rule_logging", true);
1291+
h.setOptionValue(kSolverString, kIpxString);
1292+
1293+
lp.row_lower_ = {-kHighsInf, -4};
1294+
lp.row_upper_ = {0, 0};
1295+
for (HighsInt bound_flip = 0; bound_flip < 2; bound_flip++) {
1296+
lp.a_matrix_.value_ = {1, -4, -1, 4, 3, -12};
1297+
for (HighsInt lhs_flip = 0; lhs_flip < 2; lhs_flip++) {
1298+
// Base model has parallel rows
1299+
//
1300+
// r0: x - y + 3z <= 0
1301+
// r1: -4 <= -4x + 4y - 12z <= 0
1302+
//
1303+
// After x is fixed at 0 (dominated column) these parallel rows
1304+
// are deduced to be the doubleton equation
1305+
//
1306+
// -y + 3z = 0
1307+
//
1308+
// with r1 being removed - presumably because the elimination
1309+
// multiplier (-0.25) is then less than 1 in magnitude.
1310+
//
1311+
// This allows z to be substituted and y is then fixed at 2,
1312+
// reducing the problem to empty
1313+
//
1314+
// In substitution, the row dual for the equation is 1/3
1315+
//
1316+
// * r0 is made basic because its lower bound was tightened, and
1317+
// the sign of the dual means that it can't be nonbasic so it
1318+
// can't be nonbasic
1319+
//
1320+
// * r1 is made nonbasic and the dual is now correctly scaled by
1321+
// _multiplying_ by (-0.25) - since the row values are larger,
1322+
// the dual must be reduced in magnitude - and should be viewed
1323+
// as being at its upper bound
1324+
//
1325+
// However, in DuplicateRow::undo, computeRowDualAndStatus was
1326+
// previously only passed "tightened", and no indication of
1327+
// whether that was at its lower or upper bound. Hence it made
1328+
// r1 nonbasic at its lower bound when it tightens the lower
1329+
// bound of r0, due to the negative sign of the scale factor.
1330+
//
1331+
// However, it's the upper bound on r1 that tightens the lower
1332+
// bound on r0. By passing the direction sign -1 (+1) if r0 is
1333+
// tightened at its lower (upper) bound, r1 is now set to be
1334+
// nonbasic at the correct bound.
1335+
//
1336+
// The spurious lower bound on r1 is necessary to expose the
1337+
// consequences of the error since the simplex solver only needs
1338+
// the basic/nonbasic status to set values of variables to
1339+
// bound, unless they are ranged, in which case the
1340+
// HighsBasisStatus being lower or upper is used.
1341+
//
1342+
// The multiple passes ensure code coverage in
1343+
// DuplicateRow::undo - all four cases are passed to
1344+
// computeRowDualAndStatus - and test the correctness of both
1345+
// primal-dual and basis postsolve.
1346+
//
1347+
h.setOptionValue("run_crossover", kHighsOnString);
1348+
for (HighsInt k = 0; k < 2; k++) {
1349+
REQUIRE(h.passModel(lp) == HighsStatus::kOk);
1350+
REQUIRE(h.run() == HighsStatus::kOk);
1351+
1352+
REQUIRE(h.getModelStatus() == HighsModelStatus::kOptimal);
1353+
h.setOptionValue("run_crossover", kHighsOffString);
1354+
}
1355+
lp.a_matrix_.value_ = {-1, 4, 1, -4, -3, 12};
1356+
}
1357+
lp.row_lower_ = {0, 0};
1358+
lp.row_upper_ = {kHighsInf, 4};
1359+
}
1360+
1361+
h.resetGlobalScheduler(true);
1362+
}
1363+
12791364
/*
12801365
TEST_CASE("test-fuzzing", "[highs_test_presolve]") {
12811366
Highs h;
12821367
// h.setOptionValue("output_flag", dev_run);
12831368
// if (dev_run) {
12841369
printf("\n====================\nWithout presolve\n====================\n");
12851370
1286-
const std::string model = "issue-009";
1371+
const std::string model = "issue-002";
1372+
const bool reduces_to_empty = true;
12871373
std::string model_file = std::string(HIGHS_DIR) + "/build/OscarFuzzing/" +
12881374
model + "/" + model + ".mps";
12891375
@@ -1303,23 +1389,23 @@ TEST_CASE("test-fuzzing", "[highs_test_presolve]") {
13031389
std::string options_file =
13041390
std::string(HIGHS_DIR) + "/build/OscarFuzzing/" + model + "/options.txt";
13051391
REQUIRE(h.readOptions(options_file) == HighsStatus::kOk);
1306-
// REQUIRE(h.setOptionValue("presolve_rule_off", 1 <<
1307-
kPresolveRuleColStuffing) == HighsStatus::kOk); HighsOptions options =
1308-
h.getOptions();
1392+
HighsOptions options = h.getOptions();
13091393
1310-
printf("\n====================\nPresolved LP\n====================\n");
1311-
h.presolve();
1394+
if (!reduces_to_empty) {
1395+
printf("\n====================\nPresolved LP\n====================\n");
1396+
h.presolve();
13121397
1313-
HighsLp lp = h.getPresolvedLp();
1398+
HighsLp lp = h.getPresolvedLp();
13141399
1315-
h.clear();
1316-
h.passModel(lp);
1400+
h.clear();
1401+
h.passModel(lp);
13171402
1318-
h.setOptionValue(kPresolveString, kHighsOffString);
1403+
h.setOptionValue(kPresolveString, kHighsOffString);
13191404
1320-
h.run();
1321-
h.writeSolution("", 1);
1322-
h.clear();
1405+
h.run();
1406+
h.writeSolution("", 1);
1407+
h.clear();
1408+
}
13231409
13241410
printf(
13251411
"\n====================\nPresolve no crossover\n====================\n");
@@ -1330,6 +1416,8 @@ h.getOptions();
13301416
h.setOptionValue("log_dev_level", 1);
13311417
h.writeOptions("", true);
13321418
1419+
// h.setOptionValue(kSolverString, kIpxString);
1420+
13331421
h.run();
13341422
h.writeSolution("", 1);
13351423

‎highs/lp_data/HighsOptions.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1500,7 +1500,7 @@ class HighsOptions : public HighsOptionsStruct {
15001500

15011501
record_bool = new OptionRecordBool(
15021502
"use_implied_bounds_from_presolve",
1503-
"Use relaxed implied bounds from presolve", advanced,
1503+
"Use relaxed implied bounds from presolve: redundant option!", advanced,
15041504
&use_implied_bounds_from_presolve, false);
15051505
records.push_back(record_bool);
15061506

‎highs/mip/feasibilityjump.hh‎

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ class Problem {
145145
// If we are relaxing continuous variables, an equality needs to be split
146146
// into Gte and Lte.
147147
if (relax_continuous > 0 && sense == RowType::Equal)
148-
if (std::any_of(rowVarIdxs, rowVarIdxs + numCoeffs, [&](double varIdx) {
148+
if (std::any_of(rowVarIdxs, rowVarIdxs + numCoeffs, [&](int varIdx) {
149149
return vars[varIdx].vartype == VarType::Continuous;
150150
})) {
151151
addConstraint(RowType::Gte, rhs, numCoeffs, rowVarIdxs, rowCoeffs,
@@ -350,7 +350,6 @@ class JumpMove {
350350
else {
351351
constraintBounds.emplace_back(-std::numeric_limits<double>::infinity(),
352352
constraint.rhs);
353-
constraintBounds.emplace_back(constraint.rhs, constraint.rhs);
354353
constraintBounds.emplace_back(constraint.rhs,
355354
std::numeric_limits<double>::infinity());
356355
}
@@ -359,9 +358,17 @@ class JumpMove {
359358
double residualIncumbent =
360359
constraint.incumbentLhs - cell.coeff * varIncumbentValue;
361360

361+
double boundForValidLower, boundForValidUpper;
362+
if (cell.coeff < 0.) {
363+
boundForValidLower = bound.second;
364+
boundForValidUpper = bound.first;
365+
} else {
366+
boundForValidLower = bound.first;
367+
boundForValidUpper = bound.second;
368+
}
362369
std::pair<double, double> validRange = {
363-
((1.0 / cell.coeff) * (bound.first - residualIncumbent)),
364-
((1.0 / cell.coeff) * (bound.second - residualIncumbent)),
370+
((1.0 / cell.coeff) * (boundForValidLower - residualIncumbent)),
371+
((1.0 / cell.coeff) * (boundForValidUpper - residualIncumbent)),
365372
};
366373

367374
if (problem.vars[varIdx].vartype == VarType::Integer)
@@ -370,8 +377,6 @@ class JumpMove {
370377
std::floor(validRange.second + equalityTolerance),
371378
};
372379

373-
if (validRange.first > validRange.second) continue;
374-
375380
if (validRange.first > currentValue) {
376381
currentSlope -= constraint.weight;
377382
if (validRange.first < upper)
@@ -415,6 +420,9 @@ class JumpMove {
415420
if (eq(bestValue, varIncumbentValue, equalityTolerance) ||
416421
(!eq(currentValue, varIncumbentValue, equalityTolerance) &&
417422
currentScore < bestScore)) {
423+
// Different to everywhere else (!)
424+
// Here the score represents infeasibility (not improvement)
425+
// Hence the minimization
418426
bestScore = currentScore;
419427
bestValue = currentValue;
420428
}
@@ -549,7 +557,7 @@ class FeasibilityJumpSolver {
549557
if (problem.vars.size() == 0) break;
550558

551559
uint32_t var = selectVariable();
552-
if (var == UINT_MAX){
560+
if (var == UINT_MAX) {
553561
break;
554562
}
555563
doVariableMove(var);
@@ -653,7 +661,8 @@ class FeasibilityJumpSolver {
653661
dt += problem.vars.size();
654662
for (size_t varIdx = 0; varIdx < problem.vars.size(); varIdx += 1)
655663
forEachMove(varIdx, [&](Move& move) {
656-
move.score += weightUpdateIncrement *
664+
// -= to align objective minimization with score maximization
665+
move.score -= weightUpdateIncrement *
657666
problem.vars[varIdx].objectiveCoeff *
658667
(move.value - problem.incumbentAssignment[varIdx]);
659668
});
@@ -754,7 +763,8 @@ class FeasibilityJumpSolver {
754763

755764
forEachMove(varIdx, [&](Move& move) {
756765
move.score = 0.0;
757-
move.score += objectiveWeight * problem.vars[varIdx].objectiveCoeff *
766+
// -= to align objective minimization with score maximization
767+
move.score -= objectiveWeight * problem.vars[varIdx].objectiveCoeff *
758768
(move.value - problem.incumbentAssignment[varIdx]);
759769

760770
for (auto& cell : problem.vars[varIdx].coeffs) {

0 commit comments

Comments
 (0)