Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions check/TestMipSolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1605,3 +1605,39 @@ TEST_CASE("issue-3118a", "[highs_test_mip_solver]") {

highs.resetGlobalScheduler(true);
}

TEST_CASE("issue-3173", "[highs_test_mip_solver]") {
// HPresolve::checkColBounds() treats a column as fixed when its bounds are
// equal only within the feasibility tolerance. The condition bounds the
// resulting perturbation of the row activities, but not of the objective, so
// a column that is nearly fixed in the constraints but has a large cost can
// be pinned at the wrong end of its range, losing |cost| * (ub - lb).
//
// On this model the column has bounds differing by 3.4e-8 and a cost of
// -7.3e10, so pinning it at the lower bound costs 2496.5. Presolve reduces
// the model to empty and reports that value as optimal, with default options
// and no branch-and-bound nodes.
//
// The optimal objective was verified independently of the MIP solver by
// enumerating all 2^8 binary assignments and solving the resulting LP for
// each.
const std::string filename =
std::string(HIGHS_DIR) + "/check/instances/3173-1.mps";
const double optimal_objective = -39829.5816585168;
Highs highs;
highs.setOptionValue("output_flag", dev_run);
REQUIRE(highs.readModel(filename) == HighsStatus::kOk);
REQUIRE(highs.run() == HighsStatus::kOk);
REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal);
// The column is still pinned within the feasibility tolerance, so allow a
// small relative error - but nothing like the 6.3% the defect produces.
const double relative_error =
std::fabs(highs.getInfo().objective_function_value - optimal_objective) /
std::max(1.0, std::fabs(optimal_objective));
if (dev_run)
printf("issue-3173: objective %g, require %g, relative error %g\n",
highs.getInfo().objective_function_value, optimal_objective,
relative_error);
REQUIRE(relative_error < 1e-6);
highs.resetGlobalScheduler(true);
}
288 changes: 288 additions & 0 deletions check/instances/3173-1.mps
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
NAME HIGHSBUG
ROWS
N COST
L r0
L r1
L r2
L r3
L r4
L r5
L r6
L r7
L r8
L r9
L r10
L r11
L r12
L r13
L r14
L r15
L r16
L r17
L r18
L r19
L r20
L r21
L r22
L r23
L r24
L r25
L r26
L r27
L r28
L r29
L r30
L r31
L r32
L r33
L r34
L r35
L r36
L r37
L r38
L r39
COLUMNS
MARKER 'MARKER' 'INTORG'
c0 COST -1000000
c0 r0 -128
c0 r7 128
c0 r13 64
c0 r15 10
c0 r17 2.5
c0 r19 -2.5
c0 r22 10
c0 r25 -12
c0 r33 -3
c0 r39 17
c1 COST -1
c1 r0 -20
c1 r2 2
c1 r3 -20
c1 r5 -2
c1 r11 128
c1 r18 -2.5
c1 r21 -1
c1 r23 -16
c1 r30 -40
c1 r34 -1.5
c1 r35 -1
c1 r36 -2.5
c2 COST -10000
c2 r2 -32
c2 r10 3
c2 r22 -8
c2 r29 1
c2 r37 -10
c3 COST -0.25
c3 r4 -1.5
c3 r9 -100
c3 r12 -3
c3 r13 50
c3 r20 -0.5
c3 r22 -10
c3 r29 1
c3 r32 -300
c3 r34 -10
c4 COST -0.5
c4 r1 4
c4 r8 -3
c4 r9 -20
c4 r11 -10
c4 r16 50
c4 r20 1
c4 r21 -16
c4 r34 50
c4 r38 -100
c5 COST -5
c5 r3 100
c5 r5 16
c5 r6 128
c5 r7 -100
c5 r13 -10
c5 r15 5
c5 r18 5
c5 r20 -64
c5 r23 5
c5 r28 1
c5 r36 -62.5
c6 COST -5
c6 r0 -10
c6 r1 -8
c6 r3 -32
c6 r21 64
c6 r23 -20
c6 r26 -4
c6 r28 1
c6 r31 -10
c6 r35 -2.5
c6 r37 20
c6 r39 8
c7 COST -5
c7 r1 1.5
c7 r4 19
c7 r14 64
c7 r24 -200
c7 r27 -2
c7 r29 1
MARKER 'MARKER' 'INTEND'
c8 COST -10000
c8 r0 64
c8 r31 1
c9 COST -2.5
c9 r0 -20
c9 r1 3
c10 COST -2.5
c10 r1 -100
c10 r2 2
c10 r33 0.5
c10 r35 20
c11 COST -0.5
c11 r2 -16
c11 r3 100
c12 COST -1000000
c12 r3 -32
c12 r4 64
c12 r34 -2
c13 COST -0.5
c13 r4 -5
c13 r5 100
c14 COST -100
c14 r5 -16
c14 r6 64
c14 r24 1
c14 r25 1
c14 r26 1
c14 r27 1
c15 COST -10
c15 r6 -10
c15 r7 5
c16 COST -100
c16 r7 -0.5
c16 r8 20
c17 COST -1
c17 r8 -4
c17 r9 32
c17 r32 1
c17 r37 -8
c18 COST -10000
c18 r9 -3
c18 r10 50
c18 r37 128
c19 COST -100
c19 r10 -8
c19 r11 16
c20 COST -0.25
c20 r11 -5
c20 r12 50
c20 r35 1
c21 COST -100
c21 r12 -128
c21 r13 16
c22 COST -2.5
c22 r13 -64
c22 r14 64
c23 COST -10
c23 r14 -100
c23 r15 50
c24 COST -1
c24 r15 -3
c24 r16 4
c24 r34 20
c24 r35 -128
c25 COST -100
c25 r16 -50
c25 r17 1
c25 r30 1
c26 COST -10
c26 r17 -16
c26 r18 128
c26 r36 32
c27 COST -1
c27 r18 -0.5
c27 r19 8
c28 COST -100
c28 r19 -64
c28 r20 20
c29 COST -10000
c29 r20 -10
c29 r21 10
c29 r38 100
c30 COST -0.5
c30 r21 -1
c30 r22 100
c31 COST -2.5
c31 r22 -2.5
c31 r23 1.5
c32 COST -9.9999999999999995e-07
c32 r23 -8
RHS
RHS r0 12
RHS r1 20
RHS r2 5
RHS r5 1
RHS r7 12
RHS r8 33
RHS r9 7
RHS r10 9
RHS r11 20
RHS r12 5
RHS r13 5
RHS r14 2
RHS r15 7
RHS r16 7
RHS r17 3
RHS r20 7
RHS r22 2
RHS r23 20
RHS r24 40
RHS r25 14
RHS r26 9.0999999999999996
RHS r27 5.915
RHS r28 1
RHS r29 1
RHS r30 10
RHS r31 2
RHS r32 10
RHS r33 2
RHS r34 2
RHS r35 2
RHS r36 9
RHS r37 20
RHS r38 2
RHS r39 5
RANGES
BOUNDS
UP BND c0 1
UP BND c1 1
UP BND c2 1
UP BND c3 1
UP BND c4 1
UP BND c5 1
UP BND c6 1
UP BND c7 1
UP BND c8 20
UP BND c9 1000
UP BND c10 1000
UP BND c11 20
UP BND c12 50
UP BND c13 20
UP BND c14 200
UP BND c15 100
UP BND c16 100
UP BND c17 200
UP BND c18 100
UP BND c19 500
UP BND c20 100
UP BND c21 1000
UP BND c22 1000
UP BND c23 50
UP BND c24 100
UP BND c25 50
UP BND c26 500
UP BND c27 500
UP BND c28 50
UP BND c29 500
UP BND c30 200
UP BND c31 200
UP BND c32 20
ENDATA
13 changes: 12 additions & 1 deletion highs/presolve/HPresolve.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2226,9 +2226,20 @@ HPresolve::Result HPresolve::checkColBounds(HighsInt col, bool* isFixed) {
assert(!colDeleted[col]);
double boundDiff = model->col_upper_[col] - model->col_lower_[col];
if (isFixed != nullptr) *isFixed = false;
// The bounds are only equal to within the feasibility tolerance, so fixing
// the column perturbs the row activities by at most
// getMaxAbsColVal(col) * boundDiff and the objective by at most
// |cost| * boundDiff. Both have to be negligible: a column can be nearly
// fixed in the constraints while still being worth a great deal in the
// objective, and fixing it at the wrong end of its range then loses that
// amount. Note that for an empty column the row activity term is zero
// whatever boundDiff is, so without the objective term such a column would
// always be treated as fixed here, pre-empting emptyCol() which fixes it at
// the bound that is best for the objective.
if (boundDiff <= primal_feastol &&
(boundDiff <= options->small_matrix_value ||
getMaxAbsColVal(col) * boundDiff <= primal_feastol)) {
(getMaxAbsColVal(col) * boundDiff <= primal_feastol &&
std::fabs(model->col_cost_[col]) * boundDiff <= primal_feastol))) {
// check for primal infeasibility
if (boundDiff < -primal_feastol) return Result::kPrimalInfeasible;
// check for unboundedness
Expand Down
Loading